Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Michael Zilske <michael.zilske@tu-berlin.de>
- Loading branch information
Showing
12 changed files
with
1,248 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
|
||
<modelVersion>4.0.0</modelVersion> | ||
<groupId>com.graphhopper</groupId> | ||
<artifactId>graphhopper-isochrone</artifactId> | ||
<packaging>jar</packaging> | ||
<version>0.11-SNAPSHOT</version> | ||
<name>GraphHopper Isochrone</name> | ||
<description>Isochrone calculation with GraphHopper</description> | ||
|
||
<parent> | ||
<groupId>com.graphhopper</groupId> | ||
<artifactId>graphhopper-parent</artifactId> | ||
<version>0.11-SNAPSHOT</version> | ||
</parent> | ||
|
||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.graphhopper</groupId> | ||
<artifactId>graphhopper-reader-osm</artifactId> | ||
<version>${project.parent.version}</version> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<version>4.12</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> | ||
|
||
|
157 changes: 157 additions & 0 deletions
157
isochrone/src/main/java/com/graphhopper/isochrone/algorithm/ContourBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
/* This program is free software: you can redistribute it and/or | ||
modify it under the terms of the GNU Lesser General Public License | ||
as published by the Free Software Foundation, either version 3 of | ||
the License, or (at your option) any later version. | ||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with this program. If not, see <http://www.gnu.org/licenses/>. */ | ||
|
||
package com.graphhopper.isochrone.algorithm; | ||
|
||
import com.vividsolutions.jts.algorithm.CGAlgorithms; | ||
import com.vividsolutions.jts.geom.*; | ||
import com.vividsolutions.jts.triangulate.quadedge.QuadEdge; | ||
import com.vividsolutions.jts.triangulate.quadedge.QuadEdgeSubdivision; | ||
|
||
import java.util.*; | ||
|
||
/** | ||
* | ||
* Adapted from org.opentripplanner.common.geometry.DelaunayIsolineBuilder, | ||
* which is under LGPL. | ||
* | ||
* @author laurent | ||
* @author michaz | ||
* | ||
*/ | ||
public class ContourBuilder { | ||
|
||
private static final double EPSILON = 0.000001; | ||
private QuadEdgeSubdivision triangulation; | ||
|
||
private GeometryFactory geometryFactory = new GeometryFactory(); | ||
|
||
public ContourBuilder(QuadEdgeSubdivision triangulation) { | ||
this.triangulation = triangulation; | ||
} | ||
|
||
public Geometry computeIsoline(double z0) { | ||
Set<QuadEdge> processed = new HashSet<QuadEdge>(); | ||
Queue<QuadEdge> processQ = new ArrayDeque<QuadEdge>(); | ||
List<LinearRing> rings = new ArrayList<LinearRing>(); | ||
|
||
for (QuadEdge e : ((Collection<QuadEdge>) triangulation.getPrimaryEdges(true))) { | ||
processQ.add(e); | ||
} | ||
while (!processQ.isEmpty()) { | ||
QuadEdge e = processQ.remove(); | ||
if (processed.contains(e)) | ||
continue; | ||
processed.add(e); | ||
int cut = cut(e.orig().getZ(), e.dest().getZ(), z0); | ||
if (cut == 0) { | ||
continue; // While, next edge | ||
} | ||
List<Coordinate> polyPoints = new ArrayList<Coordinate>(); | ||
boolean ccw = cut > 0; | ||
while (true) { | ||
// Add a point to polyline | ||
Coordinate cC; | ||
if (triangulation.isFrameVertex(e.orig())) { | ||
cC = moveEpsilonTowards(e.dest().getCoordinate(), e.orig().getCoordinate()); | ||
} else if (triangulation.isFrameVertex(e.dest())) { | ||
cC = moveEpsilonTowards(e.orig().getCoordinate(), e.dest().getCoordinate()); | ||
} else { | ||
cC = e.orig().midPoint(e.dest()).getCoordinate(); | ||
} | ||
polyPoints.add(cC); | ||
processed.add(e); | ||
QuadEdge E1 = ccw ? e.oNext().getPrimary() : e.oPrev().getPrimary(); | ||
QuadEdge E2 = ccw ? e.dPrev().getPrimary() : e.dNext().getPrimary(); | ||
int cut1 = E1 == null ? 0 : cut(E1.orig().getZ(), E1.dest().getZ(), z0); | ||
int cut2 = E2 == null ? 0 : cut(E2.orig().getZ(), E2.dest().getZ(), z0); | ||
boolean ok1 = cut1 != 0 && !processed.contains(E1); | ||
boolean ok2 = cut2 != 0 && !processed.contains(E2); | ||
if (ok1) { | ||
e = E1; | ||
ccw = cut1 > 0; | ||
} else if (ok2) { | ||
e = E2; | ||
ccw = cut2 > 0; | ||
} else { | ||
// This must be the end of the polyline... | ||
break; | ||
} | ||
} | ||
// Close the polyline | ||
polyPoints.add(polyPoints.get(0)); | ||
if (polyPoints.size() >= 4) { | ||
LinearRing ring = geometryFactory.createLinearRing(polyPoints | ||
.toArray(new Coordinate[polyPoints.size()])); | ||
rings.add(ring); | ||
} | ||
} | ||
List<Polygon> retval = punchHoles(rings); | ||
return geometryFactory.createMultiPolygon(retval.toArray(new Polygon[retval.size()])); | ||
} | ||
|
||
private Coordinate moveEpsilonTowards(Coordinate coordinate, Coordinate distantFrameCoordinate) { | ||
return new Coordinate(coordinate.x + EPSILON * (distantFrameCoordinate.x - coordinate.x), coordinate.y + EPSILON * (distantFrameCoordinate.y - coordinate.y)); | ||
} | ||
|
||
private int cut(double za, double zb, double z0) { | ||
if (za < z0 && zb > z0) return 1; | ||
if (za > z0 && zb < z0) return -1; | ||
return 0; | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
private List<Polygon> punchHoles(List<LinearRing> rings) { | ||
List<Polygon> shells = new ArrayList<Polygon>(rings.size()); | ||
List<LinearRing> holes = new ArrayList<LinearRing>(rings.size() / 2); | ||
// 1. Split the polygon list in two: shells and holes (CCW and CW) | ||
for (LinearRing ring : rings) { | ||
if (CGAlgorithms.signedArea(ring.getCoordinateSequence()) > 0.0) | ||
holes.add(ring); | ||
else | ||
shells.add(geometryFactory.createPolygon(ring)); | ||
} | ||
// 2. Sort the shells based on number of points to optimize step 3. | ||
Collections.sort(shells, new Comparator<Polygon>() { | ||
@Override | ||
public int compare(Polygon o1, Polygon o2) { | ||
return o2.getNumPoints() - o1.getNumPoints(); | ||
} | ||
}); | ||
for (Polygon shell : shells) { | ||
shell.setUserData(new ArrayList<LinearRing>()); | ||
} | ||
// 3. For each hole, determine which shell it fits in. | ||
int nHolesFailed = 0; | ||
for (LinearRing hole : holes) { | ||
outer: { | ||
// Probably most of the time, the first shell will be the one | ||
for (Polygon shell : shells) { | ||
if (shell.contains(hole)) { | ||
((List<LinearRing>) shell.getUserData()).add(hole); | ||
break outer; | ||
} | ||
} | ||
throw new RuntimeException("Found a hole without a shell."); | ||
} | ||
} | ||
// 4. Build the list of punched polygons | ||
List<Polygon> punched = new ArrayList<Polygon>(shells.size()); | ||
for (Polygon shell : shells) { | ||
List<LinearRing> shellHoles = ((List<LinearRing>) shell.getUserData()); | ||
punched.add(geometryFactory.createPolygon((LinearRing) (shell.getExteriorRing()), | ||
shellHoles.toArray(new LinearRing[shellHoles.size()]))); | ||
} | ||
return punched; | ||
} | ||
} |
Oops, something went wrong.