package geo;
import gl.Renderable;
import gl.scenegraph.MeshComponent;
import java.util.Arrays;
import javax.microedition.khronos.opengles.GL10;
import system.Container;
import system.EventManager;
import util.EfficientList;
import util.EfficientListQualified;
import util.Log;
import worldData.AbstractObj;
import worldData.Updateable;
import worldData.Visitor;
import worldData.World;
/**
* A {@link GeoGraph} is a simple graph which holds {@link GeoObj}s and supports
* shortest path search eg. it extends {@link AbstractObj} and can be added to a
* virtual {@link World} directly
*
* @author Spobo
*
*/
public class GeoGraph extends AbstractObj implements Container<GeoObj> {
private static final boolean DEBUG1 = false;
private static final boolean DEBUG2 = false;
private static final String LOG_TAG = "GeoGraph";
private EfficientListQualified<GeoObj> myNodes;
private EfficientList<Edge> myEdges;
private boolean isPath;
private boolean nonDirectional = true;
private boolean useEdges;
/**
* this constructor will automatically enable edges!
*/
public GeoGraph() {
useEdges = true;
}
public GeoGraph(boolean usesEdges) {
this.useEdges = usesEdges;
}
public boolean isNonDirectional() {
return nonDirectional;
}
// public GeoGraph(GeoGraph graphToClone) {
// setUseEdges(graphToClone.isUsingItsEdges());
// setIsPath(graphToClone.isPath());
// setNonDirectional(graphToClone.isNonDirectional());
// try {
// if (graphToClone.getMyEdges() != null)
// myEdges = graphToClone.getMyEdges().clone();
//
// } catch (CloneNotSupportedException e) {
// myEdges = new EfficientList<Edge>();
// e.printStackTrace();
// }
// try {
// if (graphToClone.getMyItems() != null)
// myGeoObjects = graphToClone.getMyItems().clone();
// } catch (CloneNotSupportedException e) {
// myGeoObjects = new EfficientListQualified<GeoObj>();
// e.printStackTrace();
// }
//
// }
public GeoGraph dijkstra(GeoObj startPoint, GeoObj target) {
Log.d("GeoGraph", "Running Dijkstra-algo from " + startPoint + " to "
+ target);
if (startPoint == null || target == null) {
Log.e("GeoGraph",
"Dijkstra-algo error: startPoint or target were null!");
return null;
}
if (startPoint == target) {
Log.w("GeoGraph",
"Dijkstra-algo warning: startPoint and target were the same points.");
GeoGraph g = new GeoGraph();
g.add(startPoint);
return g;
}
// first move source item to first position because dijstra uses first
// item as startPoint
if (!moveObjToFirstPosition(startPoint)) {
return null;
}
int nodesArrayLength = myNodes.myLength;
// init edgeWeight Array:
int[][] edgeWeight = new int[nodesArrayLength][nodesArrayLength];
for (int i = 0; i < nodesArrayLength; i++) {
// init GeoObj id's (from 0 to n):
myNodes.get(i).dijkstraId = i;
// and init edgeWeight array:
Arrays.fill(edgeWeight[i], Integer.MAX_VALUE);
}
final int edgesLength = myEdges.myLength;
if (DEBUG1) {
System.out.println(myNodes);
Log.d("GeoGraph", "EdgesArrayLength=" + edgesLength);
System.out.println(myEdges);
}
for (int i = 0; i < edgesLength; i++) {
final Edge e = myEdges.get(i);
edgeWeight[e.from.dijkstraId][e.to.dijkstraId] = e.weight;
if (nonDirectional)
edgeWeight[e.to.dijkstraId][e.from.dijkstraId] = e.weight;
}
if (DEBUG2)
debugShowDist(edgeWeight);
// initialize:
int[] specialPathLength = new int[nodesArrayLength];
GeoObj[] previousNodeInPath = new GeoObj[nodesArrayLength];
EfficientList<GeoObj> canidateSet = new EfficientList<GeoObj>();
// init arrays:
for (int i = 0; i < nodesArrayLength; i++) {
canidateSet.add(myNodes.get(i));
specialPathLength[i] = edgeWeight[0][i];
if (specialPathLength[i] != Integer.MAX_VALUE) {
previousNodeInPath[i] = myNodes.get(0);
}
}
if (DEBUG2) {
// debugShowGeoObjArray("canidateSet.myArray", canidateSet);
// debugShowGeoObjArray("previousNodeInPath", previousNodeInPath);
debugShowIntArray("specialPathLenth", specialPathLength);
Log.d("GeoGraph", "nodesArrayLength=" + nodesArrayLength);
Log.d("GeoGraph", "Dijkstra-init ok, starting crawl:");
}
// crawl the graph
for (int i = 0; i < nodesArrayLength - 1; i++) {
// find the lightest Edge among the candidates
int lightest = Integer.MAX_VALUE;
GeoObj n = myNodes.get(0);
int candLength = canidateSet.myLength;
for (int i2 = 0; i2 < candLength; i2++) {
GeoObj g = canidateSet.get(i2);
if (specialPathLength[g.dijkstraId] < lightest) {
n = g;
lightest = specialPathLength[g.dijkstraId];
}
}
canidateSet.remove(n);
if (DEBUG2) {
if (n != null)
Log.d("GeoGraph", i + ": removed " + n + " (id="
+ n.dijkstraId + ") from canidateSet");
else
Log.d("GeoGraph", i
+ ": removed null (no id) from canidateSet");
// debugShowGeoObjArray(i + ": canidateSet",
// canidateSet.myArray);
Log.d("GeoGraph", i + ": lightest=" + lightest);
}
// see if any Edges from this GeoObj yield a shorter path than from
// source->that GeoObj n
for (int j = 0; j < nodesArrayLength; j++) {
if (specialPathLength[n.dijkstraId] != Integer.MAX_VALUE
&& edgeWeight[n.dijkstraId][j] != Integer.MAX_VALUE
&& specialPathLength[n.dijkstraId]
+ edgeWeight[n.dijkstraId][j] < specialPathLength[j]) {
// found one, update the path
specialPathLength[j] = specialPathLength[n.dijkstraId]
+ edgeWeight[n.dijkstraId][j];
if (DEBUG2) {
debugShowIntArray(i + "," + j + ": specialPathLength",
specialPathLength);
}
previousNodeInPath[j] = n;
}
}
}
GeoGraph result = new GeoGraph();
result.setIsPath(true);
int loc = target.dijkstraId;
result.add(target);
// backtrack from the target by P(revious), adding to the result list
while (previousNodeInPath[loc] != myNodes.get(0)) {
if (previousNodeInPath[loc] == null) {
Log.d("GeoGraph", " -> No path found :(");
return null;
}
result.insert(0, previousNodeInPath[loc]);
loc = previousNodeInPath[loc].dijkstraId;
}
result.insert(0, myNodes.get(0));
result.addEdgesToCreatePath();
Log.d("GeoGraph", " -> Resulting path has length "
+ result.myNodes.myLength);
return result;
}
public void addEdgesToCreatePath() {
addEdgesToCreatePath(null);
}
public void addEdgesToCreatePath(EdgeListener li) {
GeoObj lastPos = null;
final int l = myNodes.myLength;
GeoObj p;
for (int i = 0; i < l; i++) {
p = myNodes.get(i);
if (lastPos != null) {
if (li != null) {
li.addEdgeToGraph(this, lastPos, p);
} else {
// add default mesh:
this.addEdge(lastPos, p, null);
}
}
lastPos = p;
}
setIsPath(true);
}
@Override
public boolean accept(Visitor visitor) {
return visitor.default_visit(this);
}
private void debugShowIntArray(String string, int[] dist) {
Log.d("GeoGraph", string);
String line = " = ";
for (int i = 0; i < dist.length; i++) {
if (dist[i] == Integer.MAX_VALUE) {
line += "inf, ";
} else {
line += dist[i] + ", ";
}
}
Log.d("GeoGraph", line);
}
private void debugShowDist(int[][] dist) {
Log.d("GeoGraph", "Distance Matrix:");
String line = "";
for (int i = 0; i < dist.length; i++) {
line = " " + i + ": ";
for (int j = 0; j < dist[i].length; j++) {
if (dist[i][j] != Integer.MAX_VALUE) {
line += dist[i][j] + ",";
} else {
line += "inf ,";
}
}
Log.d("GeoGraph", line);
}
}
private boolean moveObjToFirstPosition(GeoObj obj) {
if ((myNodes == null))
return false;
if (myNodes.remove(obj)) {
return myNodes.insert(0, obj);
}
return false;
}
@Override
public boolean insert(int pos, GeoObj geoObj) {
if (myNodes == null)
myNodes = new EfficientListQualified<GeoObj>();
return myNodes.insert(pos, geoObj);
}
/**
* @param geoObj
* @return true if the {@link GeoObj} was added and false if it already
* existed or was NULL
*/
@Override
public boolean add(GeoObj geoObj) {
if (myNodes == null)
myNodes = new EfficientListQualified<GeoObj>();
if (myNodes.contains(geoObj) == -1) {
myNodes.add(geoObj);
return true;
}
return false;
}
/**
* if this is set to true the visualization will connect all elements with
* edges. now the order of the nodes is important and findPathTo() shouldnt
* be used anymore
*
* TODO create edges so that order of nodes is unimportant again??
*
* @param isPath
*/
public void setIsPath(boolean isPath) {
this.isPath = isPath;
}
public boolean isPath() {
return isPath;
}
// public Obj toObj(GeoObj relativeNullPoint) {
// Obj o = new Obj();
// MeshGroup m = GLFactory.f().newMeshGroup(null, null);
// Object[] a = myGeoObjects.myArray;
// int size = myGeoObjects.myLength;
// GeoObj lastPos = null;
// final boolean isPath = isPath();
// for (int i = 0; i < size; i++) {
// GeoObj g = ((GeoObj) a[i]);
// /*
// * if the position of an geoObject isn't calculated automatically
// * then calculate it now:
// */
// if (!GeoObj.autoCalcTheVirtualPosOfGeoObjects)
// g.calcVirtualPosition(relativeNullPoint);
// /*
// * then add the default selection commands set for the graph to the
// * objects:
// */
// g.setSelectionCommands(this);
// m.add(g);
// if (isPath) {
// if (lastPos != null) {
// m.add(createNewPath(lastPos, g, relativeNullPoint));
// }
// lastPos = g;
// }
// }
//
// o.set(Consts.COMP_GRAPHICS, m);
// return o;
// }
public GeoObj findBestPointFor(String searchTerm) {
Log.d("GeoGraph", "Searching graph for " + searchTerm);
GeoGraph searchResults = findGeoObjects(searchTerm);
if (searchResults == null) {
Log.d("GeoGraph", " -> Nothing found for '" + searchTerm + "'");
return null;
}
Log.d("GeoGraph", " -> Found item that matches");
return searchResults.getAllItems().get(0);
}
/**
* @param start
* can be null, then the current device position will be used as
* the start point automatically
* @param target
* @return
*/
public GeoGraph findPath(GeoObj start, GeoObj target) {
if (start == null) {
start = getClosesedObjTo(EventManager.getInstance()
.getCurrentLocationObject());
}
return dijkstra(start, target);
}
/**
* @param pos
* @return the {@link GeoObj} contained in this {@link GeoGraph} which has
* the smallest distance to the specified pos-{@link GeoObj}
*/
public GeoObj getClosesedObjTo(GeoObj pos) {
GeoObj result = null;
if (myNodes == null)
return null;
double minDistance;
GeoObj a = myNodes.get(0);
if (a != null) {
minDistance = a.getDistance(pos);
result = a;
} else {
return null;
}
for (int i = 1; i < myNodes.myLength; i++) {
GeoObj o = myNodes.get(i);
double distance = o.getDistance(pos);
if (distance < minDistance) {
minDistance = distance;
result = o;
}
}
return result;
}
public GeoGraph findGeoObjects(String searchTerm) {
if (myNodes == null)
return null;
GeoGraph searchResults = null;
for (int i = 0; i < myNodes.myLength; i++) {
GeoObj o = myNodes.get(i);
float matchQuality = o.matchesSearchTerm(searchTerm);
if (matchQuality > 0) {
if (searchResults == null) {
searchResults = new GeoGraph();
}
searchResults.insertWithDefinedQuality(matchQuality, o);
}
}
return searchResults;
}
private void insertWithDefinedQuality(float matchQuality, GeoObj o) {
if (myNodes == null)
myNodes = new EfficientListQualified<GeoObj>();
myNodes.add(o, matchQuality);
}
/**
* @param from
* @param to
* @param edgeMeshComp
* if this is null a default mesh from {@link Edge}
* .getDefaultMesh() will be used
* @return the added edge or null if edge already existed in the graph
*/
public Edge addEdge(GeoObj from, GeoObj to, MeshComponent edgeMeshComp) {
if (myEdges == null)
myEdges = new EfficientList<Edge>();
if (hasEdge(from, to) == -1) {
if (edgeMeshComp == null)
edgeMeshComp = Edge.getDefaultMesh(this, from, to, this
.getInfoObject().getColor());
Edge e = new Edge(from, to, edgeMeshComp);
myEdges.add(e);
return e;
} else {
Log.e(LOG_TAG, "Tried to add new edge but edge from " + from
+ " to " + to + " already existed!");
}
return null;
}
/**
* @param from
* @param to
* @return the position of the edge in the list or -1 if its not in the list
*/
public int hasEdge(GeoObj from, GeoObj to) {
if (myEdges == null)
return -1;
// TODO move this method to efficient list and Edge.compare(edge) ?
for (int i = 0; i < myEdges.myLength; i++) {
Edge e = myEdges.get(i);
if ((e.from == from && e.to == to)
|| (e.from == to && e.to == from)) {
return i;
}
}
return -1;
}
/**
* if this is disabled, all edges have a direction and an edge from A to B
* cant be used to get from B to A. Default value is true
*
* @param nonDirectional
*/
public void setNonDirectional(boolean nonDirectional) {
this.nonDirectional = nonDirectional;
}
@Override
public String toString() {
if (myNodes != null) {
if (HasInfoObject())
return "GeoGraph '" + getInfoObject().getShortDescr()
+ "' (size=" + myNodes.myLength + ")";
return "GeoGraph <noname>(size=" + myNodes.myLength + ")";
} else {
if (HasInfoObject())
return "GeoGraph '" + getInfoObject().getShortDescr()
+ "' (size=no objects in graph)";
return "GeoGraph <noname>(size=no objects in graph)";
}
}
@Override
public void render(GL10 gl, Renderable parent) {
if (myNodes == null)
return;
{
for (int i = 0; i < myNodes.myLength; i++) {
myNodes.get(i).render(gl, this);
}
}
{
if ((isPath || useEdges) && myEdges != null) {
for (int i = 0; i < myEdges.myLength; i++) {
myEdges.get(i).render(gl, this);
}
}
}
}
@Override
public boolean update(float timeDelta, Updateable parent) {
if (myNodes == null)
return true;
setMyParent(parent);
{
for (int i = 0; i < myNodes.myLength; i++) {
if (!myNodes.get(i).update(timeDelta, this)) {
// remove node if no longer needed (it returned false)
remove(myNodes.get(i));
}
}
}
if (useEdges && myEdges != null) {
for (int i = 0; i < myEdges.myLength; i++) {
if (!myEdges.get(i).update(timeDelta, this)) {
remove(myEdges.get(i));
}
}
}
return true;
}
/**
* tries to remove a {@link GeoObj} from this graph, this can be a node or
* an edge too!
*
* @param x
* @return
*/
@Override
public boolean remove(GeoObj x) {
// first try to remove item from the nodes
if (myNodes.remove(x)) {
x.setRemoved();
return true;
}
// then fom the edges
if (myEdges.remove(x)) {
x.setRemoved();
return true;
}
return false;
}
public EfficientList<Edge> getEdges() {
if (myEdges == null)
myEdges = new EfficientList<Edge>();
return myEdges;
}
/**
* this flag is necessary to not automatically remove new created
* {@link GeoGraph}s. This way .isEmpty() will only return true if the
* {@link GeoGraph} was cleared at least one time
*/
private boolean isClearedAtLeastOneTime = false;
@Override
public void clear() {
isClearedAtLeastOneTime = true;
if (myNodes != null)
myNodes.clear();
if (myEdges != null)
myEdges.clear();
}
@Override
public void removeEmptyItems() {
/*
* TODO there is a flag isDeleted in GeoObj so search for these imtems
* here?
*/
}
public boolean isEmpty() {
return getAllItems().myLength == 0;
}
@Override
public boolean isCleared() {
if (getAllItems().myLength == 0 && isClearedAtLeastOneTime) {
return true;
}
return false;
}
@Override
public EfficientListQualified<GeoObj> getAllItems() {
if (myNodes == null)
myNodes = new EfficientListQualified<GeoObj>();
return myNodes;
}
@Override
public int length() {
return getAllItems().myLength;
}
/**
* enable this if all edges that are available should be displayed too
*
* @param useThem
*/
public void setUseEdges(boolean useThem) {
useEdges = useThem;
}
public boolean isUsingItsEdges() {
return useEdges;
}
public boolean hasEdges() {
if (myEdges != null && myEdges.myLength > 0)
return true;
return false;
}
public EfficientList<GeoObj> getConnectedNodesOf(GeoObj obj) {
EfficientList<GeoObj> result = new EfficientList<GeoObj>();
for (int i = 0; i < myEdges.myLength; i++) {
if (myEdges.get(i).from.equals(obj)) {
result.add(myEdges.get(i).to);
} else if (myEdges.get(i).to.equals(obj)) {
result.add(myEdges.get(i).from);
}
}
return result;
}
public EfficientList<GeoObj> getFollowingNodesOf(GeoObj obj) {
if (isNonDirectional()) {
return getConnectedNodesOf(obj);
}
EfficientList<GeoObj> result = new EfficientList<GeoObj>();
if (myEdges != null) {
for (int i = 0; i < myEdges.myLength; i++) {
if (myEdges.get(i).from.equals(obj)) {
result.add(myEdges.get(i).to);
}
}
}
return result;
}
public static GeoGraph convertToGeoGraph(EfficientList<GeoObj> list,
boolean directional, SimpleNodeEdgeListener l) {
return convertToGeoGraph(list, directional, l, l);
}
public static GeoGraph convertToGeoGraph(EfficientList<GeoObj> list,
boolean directional, NodeListener nl, EdgeListener el) {
GeoGraph result = new GeoGraph();
result.setNonDirectional(!directional);
for (int i = 0; i < list.myLength; i++) {
if (i == 0) {
nl.addFirstNodeToGraph(result, list.get(0));
} else if (i == list.myLength - 1) {
nl.addLastNodeToGraph(result, list.get(list.myLength - 1));
} else {
nl.addNodeToGraph(result, list.get(i));
}
if (i < list.myLength - 1) {
el.addEdgeToGraph(result, list.get(i), list.get(i + 1));
}
}
return result;
}
/**
* @param from
* @param to
* @return null of there is no edge for these nodes
*/
public Edge getEdge(GeoObj from, GeoObj to) {
int pos = hasEdge(from, to);
if (pos == -1)
return null;
return getEdges().get(pos);
}
}