package edu.kit.pse.ws2013.routekit.map;
import java.awt.geom.Line2D;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
import edu.kit.pse.ws2013.routekit.util.Coordinates;
import edu.kit.pse.ws2013.routekit.util.PointOnEdge;
public class ArrayGraphIndex implements GraphIndex {
private final Graph graph;
private final GraphView graphView;
private final int[] contents;
/**
* 3-4 ints per node:
* <ol>
* <li>0 means splitLon, 1 means splitLat, -1 means direct content</li>
* <li>if direct: start (index in contents), else left (index in nodes)</li>
* <li>if direct: length (in contents), else right (index in nodes)</li>
* <li>if indirect: threshold ({@link Float#floatToIntBits(float)}), else
* absent</li>
* </ol>
*/
private final int[] nodes;
public ArrayGraphIndex(Graph graph, GraphView graphView, int[] contents,
int[] nodes) {
this.graph = graph;
this.graphView = graphView;
this.contents = contents;
this.nodes = nodes;
}
@Override
public PointOnEdge findNearestPointOnEdge(Coordinates coords) {
final Coordinates left = new Coordinates(coords.getLatitude() - 0.1f,
coords.getLongitude() - 0.1f);
final Coordinates right = new Coordinates(coords.getLatitude() + 0.1f,
coords.getLongitude() + 0.1f);
double nearest = Double.POSITIVE_INFINITY;
int emax = -1;
final double cx = coords.getSmtX(19);
final double cy = coords.getSmtY(19);
for (Integer e : getEdgesInRectangle(left, right)) {
final Coordinates start = graph.getCoordinates(graph
.getStartNode(e));
final Coordinates end = graph
.getCoordinates(graph.getTargetNode(e));
final double sx = start.getSmtX(19);
final double sy = start.getSmtY(19);
final double ex = end.getSmtX(19);
final double ey = end.getSmtY(19);
double l = Line2D.ptSegDistSq(sx, sy, ex, ey, cx, cy);
if (l < nearest) {
nearest = l;
emax = e;
}
}
if (emax == -1) {
return null;
}
final Coordinates start = graph
.getCoordinates(graph.getStartNode(emax));
final Coordinates end = graph.getCoordinates(graph.getTargetNode(emax));
final double sx = start.getSmtX(19);
final double sy = start.getSmtY(19);
final double ex = end.getSmtX(19);
final double ey = end.getSmtY(19);
double path = (cx - sx) * (ex - sx) + (cy - sy) * (ey - sy);
path /= (ex - sx) * (ex - sx) + (ey - sy) * (ey - sy);
return new PointOnEdge(emax, path < 0 ? 0 : path > 1 ? 1 : (float) path);
}
@Override
public Set<Integer> getEdgesInRectangle(Coordinates leftTop,
Coordinates rightBottom) {
Set<Integer> ints = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
int a = Integer.compare(graph.getEdgeProperties(o1).getType()
.ordinal(), graph.getEdgeProperties(o2).getType()
.ordinal());
if (a != 0) {
return -a;
}
return o1.compareTo(o2);
}
});
addAll(0, graph, leftTop, rightBottom, ints);
return ints;
}
/**
* Adds all coordinates reachable from the specified node which lie in the
* specified rectangle to the given set.
*/
private void addAll(int nodeIndex, Graph g, Coordinates leftTop,
Coordinates rightBottom, Set<Integer> target) {
switch (nodes[nodeIndex]) {
case -1: {
// direct
for (Integer edg : new IntArraySet(nodes[nodeIndex + 1],
nodes[nodeIndex + 2], contents)) {
int edge = edg;
Coordinates from = g.getCoordinates(graphView
.getStartNode(edge));
Coordinates to = g
.getCoordinates(graphView.getTargetNode(edge));
float minA = Math.min(from.getLatitude(), to.getLatitude());
float minO = Math.min(from.getLongitude(), to.getLongitude());
float maxA = Math.max(from.getLatitude(), to.getLatitude());
float maxO = Math.max(from.getLongitude(), to.getLongitude());
if (minA <= rightBottom.getLatitude()
&& minO <= rightBottom.getLongitude()
&& maxA >= leftTop.getLatitude()
&& maxO >= leftTop.getLongitude()) {
target.add(edg);
}
}
break;
}
case 0: {
// split by lon
float threshold = Float.intBitsToFloat(nodes[nodeIndex + 3]);
if (leftTop.getLongitude() <= threshold
|| rightBottom.getLongitude() <= threshold) {
addAll(nodes[nodeIndex + 1], g, leftTop, rightBottom, target);
}
if (leftTop.getLongitude() >= threshold
|| rightBottom.getLongitude() >= threshold) {
addAll(nodes[nodeIndex + 2], g, leftTop, rightBottom, target);
}
break;
}
case 1: {
// split by lat
float threshold = Float.intBitsToFloat(nodes[nodeIndex + 3]);
if (leftTop.getLatitude() <= threshold
|| rightBottom.getLatitude() <= threshold) {
addAll(nodes[nodeIndex + 1], g, leftTop, rightBottom, target);
}
if (leftTop.getLatitude() >= threshold
|| rightBottom.getLatitude() >= threshold) {
addAll(nodes[nodeIndex + 2], g, leftTop, rightBottom, target);
}
break;
}
}
}
@Override
public GraphView getView() {
return graphView;
}
public void save(File file, File viewFile) throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(file, "rw");
FileChannel fc = raf.getChannel()) {
MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0,
(2 + contents.length + nodes.length) * 4);
mbb.order(ByteOrder.BIG_ENDIAN).asIntBuffer().put(contents.length)
.put(nodes.length).put(contents).put(nodes);
mbb.force();
graphView.save(viewFile);
}
}
public static ArrayGraphIndex load(Graph graph, File file, File viewFile)
throws IOException {
try (FileInputStream fis = new FileInputStream(file);
DataInputStream dis = new DataInputStream(fis);
FileChannel fc = fis.getChannel()) {
int contentLength = dis.readInt();
int nodesLength = dis.readInt();
MappedByteBuffer mbb = fc.map(MapMode.READ_ONLY, 8,
(contentLength + nodesLength) * 4);
int[] content = new int[contentLength];
int[] nodes = new int[nodesLength];
mbb.order(ByteOrder.BIG_ENDIAN).asIntBuffer().get(content)
.get(nodes);
return new ArrayGraphIndex(graph, GraphView.load(graph, viewFile),
content, nodes);
}
}
}