Skip to content

Commit

Permalink
factor out nondestructive splitter function. geometry fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
abyrd committed Aug 23, 2015
1 parent f915013 commit 38d693b
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 108 deletions.
44 changes: 31 additions & 13 deletions src/main/java/org/opentripplanner/streets/EdgeStore.java
Expand Up @@ -226,9 +226,10 @@ public void setGeometry (List<Node> nodes) {
}

/**
* Always iterates forward, whether or not we are on a forward or backward edge.
* Call a function on every segment in this edges's geometry.
* Always iterates forward over the geometry, whether we are on a forward or backward edge.
*/
public void iterateGeometry (SegmentConsumer segmentConsumer) {
public void forEachSegment (SegmentConsumer segmentConsumer) {
VertexStore.Vertex vertex = vertexStore.getCursor(fromVertices.get(pairIndex));
int prevFixedLat = vertex.getFixedLat();
int prevFixedLon = vertex.getFixedLon();
Expand All @@ -247,21 +248,30 @@ public void iterateGeometry (SegmentConsumer segmentConsumer) {
segmentConsumer.consumeSegment(s, prevFixedLat, prevFixedLon, vertex.getFixedLat(), vertex.getFixedLon());
}

/** @return an envelope around the whole edge geometry. */
public Envelope getEnvelope() {
Envelope envelope = new Envelope();
VertexStore.Vertex vertex = vertexStore.getCursor();
vertex.seek(fromVertices.get(pairIndex));
envelope.expandToInclude(vertex.getFixedLon(), vertex.getFixedLat());

/**
* Call a function for every point on this edge's geometry, including the beginning end end points.
* Always iterates forward over the geometry, whether we are on a forward or backward edge.
*/
public void forEachPoint (PointConsumer pointConsumer) {
VertexStore.Vertex vertex = vertexStore.getCursor(fromVertices.get(pairIndex));
int p = 0;
pointConsumer.consumePoint(p++, vertex.getFixedLat(), vertex.getFixedLon());
int[] intermediates = geometries.get(pairIndex);
int i = 0;
while (i < intermediates.length) {
int fixedLat = intermediates[i++];
int fixedLon = intermediates[i++];
envelope.expandToInclude(fixedLon, fixedLat);
pointConsumer.consumePoint(p++, intermediates[i++], intermediates[i++]);
}
vertex.seek(toVertices.get(pairIndex));
envelope.expandToInclude(vertex.getFixedLon(), vertex.getFixedLat());
pointConsumer.consumePoint(p, vertex.getFixedLat(), vertex.getFixedLon());
}

/** @return an envelope around the whole edge geometry. */
public Envelope getEnvelope() {
Envelope envelope = new Envelope();
forEachPoint((p, fixedLat, fixedLon) -> {
envelope.expandToInclude(fixedLon, fixedLat);
});
return envelope;
}

Expand All @@ -271,8 +281,10 @@ public Envelope getEnvelope() {
public int nSegments () {
int[] geom = geometries.get(pairIndex);
if (geom != null) {
return geom.length + 1;
// Number of packed lat-lon pairs plus the final segment.
return (geom.length / 2) + 1;
} else {
// A single segment from the initial vertex to the final vertex.
return 1;
}
}
Expand Down Expand Up @@ -308,6 +320,12 @@ public static interface SegmentConsumer {
public void consumeSegment (int index, int fixedLat0, int fixedLon0, int fixedLat1, int fixedLon1);
}

/** A functional interface that consumes the points in a street geometry one by one. */
@FunctionalInterface
public static interface PointConsumer {
public void consumePoint (int index, int fixedLat, int fixedLon);
}

public void dump () {
Edge edge = getCursor();
for (int e = 0; e < nEdges; e++) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/opentripplanner/streets/IntHashGrid.java
Expand Up @@ -62,7 +62,7 @@ public class IntHashGrid {
private int nEntries = 0;

public IntHashGrid(double binSizeDegrees) {
yBinSize = VertexStore.degreesToFixedInt(binSizeDegrees);
yBinSize = VertexStore.floatingDegreesToFixed(binSizeDegrees);
xBinSize = (int)(yBinSize / 0.7); // Assume about 45 degrees latitude for now, cos(45deg)
if (binSizeDegrees <= 0) {
throw new IllegalStateException("bin size must be positive.");
Expand Down
108 changes: 104 additions & 4 deletions src/main/java/org/opentripplanner/streets/Split.java
@@ -1,28 +1,128 @@
package org.opentripplanner.streets;

import com.vividsolutions.jts.geom.Envelope;
import gnu.trove.iterator.TIntIterator;
import org.apache.commons.math3.util.FastMath;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Represents a potential split point along an existing edge, retaining some geometric calculation state so that
* once the best candidate is found more detailed calculations can continue.
*/
public class Split {

double distSquared = Double.POSITIVE_INFINITY;
private static final Logger LOG = LoggerFactory.getLogger(Split.class);

int edge = -1;
int seg = 0; // the segment within the edge that is closest to the search point
double frac = 0; // the fraction along that segment where a link should occur
double fLon; // the x coordinate of the link point along the edge
double fLat; // the y coordinate of the link point along the edge
// by virtue of having twice as many digits, a long could always hold the square of an int, but would it be faster than float math?
double distSquared = Double.POSITIVE_INFINITY;

// The following fields require more calculations and are only set once a best edge is found.
double lengthBefore = 0; // the accumulated distance along the edge geometry up to the split point
int lengthBefore_mm = 0; // the accumulated distance along the edge geometry up to the split point
int lengthAfter_mm = 0; // the accumulated distance along the edge geometry up to the split point
int distance_mm = 0; // the distance from the search point to the split point on the street

public void setFrom(Split other) {
distSquared = other.distSquared;
public void setFrom (Split other) {
edge = other.edge;
seg = other.seg;
frac = other.frac;
fLon = other.fLon;
fLat = other.fLat;
distSquared = other.distSquared;
}

/**
* @return a new Split object, or null if no edge was found in range.
*/
public static Split find (double lat, double lon, double radiusMeters, StreetLayer streetLayer) {
// NOTE THIS ENTIRE GEOMETRIC CALCULATION IS HAPPENING IN FIXED PRECISION INT DEGREES
int fLat = VertexStore.floatingDegreesToFixed(lat);
int fLon = VertexStore.floatingDegreesToFixed(lon);

// We won't worry about the perpendicular walks yet.
// Just insert or find a vertex on the nearest road and return that vertex.

final double metersPerDegreeLat = 111111.111;
double cosLat = FastMath.cos(FastMath.toRadians(lat)); // The projection factor, Earth is a "sphere"
double radiusFixedLat = VertexStore.floatingDegreesToFixed(radiusMeters / metersPerDegreeLat);
double radiusFixedLon = radiusFixedLat / cosLat; // Expand the X search space, don't shrink it.
Envelope envelope = new Envelope(fLon, fLon, fLat, fLat);
envelope.expandBy(radiusFixedLon, radiusFixedLat);
double squaredRadiusFixedLat = radiusFixedLat * radiusFixedLat; // May overflow, don't use an int
EdgeStore.Edge edge = streetLayer.edgeStore.getCursor();
// Iterate over the set of forward (even) edges that may be near the given coordinate.
TIntIterator edgeIterator = streetLayer.spatialIndex.query(envelope).iterator();
// The split location currently being examined and the best one seen so far.
Split curr = new Split();
Split best = new Split();
while (edgeIterator.hasNext()) {
curr.edge = edgeIterator.next();
edge.seek(curr.edge);
edge.forEachSegment((seg, fLat0, fLon0, fLat1, fLon1) -> {
// Find the fraction along the current segment
curr.seg = seg;
curr.frac = GeometryUtils.segmentFraction(fLon0, fLat0, fLon1, fLat1, fLon, fLat, cosLat);
// Project to get the closest point on the segment.
// Note: the fraction is scaleless, xScale is accounted for in the segmentFraction function.
curr.fLon = fLon0 + curr.frac * (fLon1 - fLon0);
curr.fLat = fLat0 + curr.frac * (fLat1 - fLat0);
// Find squared distance to edge (avoid taking root)
double dx = (curr.fLon - fLon) * cosLat;
double dy = (curr.fLat - fLat);
curr.distSquared = dx * dx + dy * dy;
// Ignore segments that are too far away (filter false positives).
// Replace the best segment if we've found something closer.
if (curr.distSquared < squaredRadiusFixedLat && curr.distSquared < best.distSquared) {
best.setFrom(curr);
}
}); // end loop over segments
} // end loop over edges

if (best.edge < 0) {
// No edge found nearby.
return null;
}
edge.seek(best.edge);

// We found an edge. Iterate over its segments again, accumulating distances along its geometry.
// The distance calculations involve square roots so are deferred to happen here, only on the selected edge.
// TODO accumulate before/after geoms. Split point can be passed over since it's not an intermediate.
// The length is are stored in one-element array to dodge Java's "effectively final" BS.
double[] lengthBefore_fixedDeg = new double[1];
edge.forEachSegment((seg, fLat0, fLon0, fLat1, fLon1) -> {
// Sum lengths only up to the split point.
// lengthAfter should be total length minus lengthBefore, which ensures splits do not change total lengths.
if (seg <= best.seg) {
double dx = (fLon1 - fLon0) * cosLat;
double dy = (fLat1 - fLat0);
double length = FastMath.sqrt(dx * dx + dy * dy);
if (seg == best.seg) {
length *= best.frac;
}
lengthBefore_fixedDeg[0] += length;
}
});
// Convert the fixed-precision degree measurements into (milli)meters
double lengthBefore_floatDeg = VertexStore.fixedDegreesToFloating((int)lengthBefore_fixedDeg[0]);
best.lengthBefore_mm = (int)(lengthBefore_floatDeg * metersPerDegreeLat * 1000);
// FIXME perhaps we should be using the sphericalDistanceLibrary here, or the other way around.
// The initial edge lengths are set using that library on OSM node coordinates, and they are slightly different.
// We are using a single cosLat value at the linking point, instead of a different value at each segment.
if (best.lengthBefore_mm < 0) {
best.lengthBefore_mm = 0;
LOG.error("Length of first street segment was not positive.");
}
if (best.lengthBefore_mm > edge.getLengthMm()) {
best.lengthBefore_mm = edge.getLengthMm();
LOG.error("Length of first street segment was greater than the whole edge.");
}
best.lengthAfter_mm = edge.getLengthMm() - best.lengthBefore_mm;
return best;
}
}

0 comments on commit 38d693b

Please sign in to comment.