org.opentripplanner.graph_builder.module.osm.Area Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otp Show documentation
Show all versions of otp Show documentation
The OpenTripPlanner multimodal journey planning system
package org.opentripplanner.graph_builder.module.osm;
import com.google.common.collect.ArrayListMultimap;
import gnu.trove.list.TLongList;
import gnu.trove.list.array.TLongArrayList;
import gnu.trove.map.TLongObjectMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.locationtech.jts.geom.MultiPolygon;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.TopologyException;
import org.opentripplanner.openstreetmap.model.OSMNode;
import org.opentripplanner.openstreetmap.model.OSMWay;
import org.opentripplanner.openstreetmap.model.OSMWithTags;
import org.opentripplanner.util.geometry.GeometryUtils;
/**
* Stores information about an OSM area needed for visibility graph construction. Algorithm based on
* http://wiki.openstreetmap.org/wiki/Relation:multipolygon/Algorithm but generally done in a
* quick/dirty way.
*/
class Area {
final List outermostRings;
// This is the way or relation that has the relevant tags for the area
OSMWithTags parent;
public MultiPolygon jtsMultiPolygon;
Area(
OSMWithTags parent,
List outerRingWays,
List innerRingWays,
TLongObjectMap nodes
) {
this.parent = parent;
// ring assignment
List innerRingNodes = constructRings(innerRingWays);
List outerRingNodes = constructRings(outerRingWays);
if (innerRingNodes == null || outerRingNodes == null) {
throw new AreaConstructionException();
}
ArrayList allRings = new ArrayList<>(innerRingNodes);
allRings.addAll(outerRingNodes);
List innerRings = new ArrayList<>();
List outerRings = new ArrayList<>();
for (TLongList ring : innerRingNodes) {
innerRings.add(new Ring(ring, nodes));
}
for (TLongList ring : outerRingNodes) {
outerRings.add(new Ring(ring, nodes));
}
List outermostRings = new ArrayList<>();
try {
// now, ring grouping
// first, find outermost rings
OUTER:for (Ring outer : outerRings) {
for (Ring possibleContainer : outerRings) {
if (outer != possibleContainer && outer.jtsPolygon.within(possibleContainer.jtsPolygon)) {
continue OUTER;
}
}
outermostRings.add(outer);
// find holes in this ring
for (Ring possibleHole : innerRings) {
if (possibleHole.jtsPolygon.within(outer.jtsPolygon)) {
outer.addHole(possibleHole);
}
}
}
} catch (TopologyException ex) {
throw new AreaConstructionException();
}
// Make outermostRings immutable
this.outermostRings = List.copyOf(outermostRings);
// run this at end of ctor so that exception
// can be caught in the right place
jtsMultiPolygon = calculateJTSMultiPolygon();
}
public List constructRings(List ways) {
if (ways.size() == 0) {
// no rings is no rings
return Collections.emptyList();
}
List closedRings = new ArrayList<>();
ArrayListMultimap waysByEndpoint = ArrayListMultimap.create();
for (OSMWay way : ways) {
TLongList refs = way.getNodeRefs();
long start = refs.get(0);
long end = refs.get(refs.size() - 1);
if (start == end) {
TLongList ring = new TLongArrayList(refs);
closedRings.add(ring);
} else {
waysByEndpoint.put(start, way);
waysByEndpoint.put(end, way);
}
}
// Precheck for impossible situations, and remove those.
TLongList endpointsToRemove = new TLongArrayList();
for (Long endpoint : waysByEndpoint.keySet()) {
Collection list = waysByEndpoint.get(endpoint);
if (list.size() % 2 == 1) {
endpointsToRemove.add(endpoint);
}
}
endpointsToRemove.forEach(endpoint -> {
waysByEndpoint.removeAll(endpoint);
return true;
});
TLongList partialRing = new TLongArrayList();
if (waysByEndpoint.size() == 0) {
return closedRings;
}
long firstEndpoint = 0, otherEndpoint = 0;
OSMWay firstWay = null;
for (Long endpoint : waysByEndpoint.keySet()) {
List list = waysByEndpoint.get(endpoint);
firstWay = list.get(0);
TLongList nodeRefs = firstWay.getNodeRefs();
partialRing.addAll(nodeRefs);
firstEndpoint = nodeRefs.get(0);
otherEndpoint = nodeRefs.get(nodeRefs.size() - 1);
break;
}
waysByEndpoint.get(firstEndpoint).remove(firstWay);
waysByEndpoint.get(otherEndpoint).remove(firstWay);
if (constructRingsRecursive(waysByEndpoint, partialRing, closedRings, firstEndpoint)) {
return closedRings;
} else {
return null;
}
}
private MultiPolygon calculateJTSMultiPolygon() {
List polygons = new ArrayList<>();
for (Ring ring : outermostRings) {
polygons.add(ring.jtsPolygon);
}
MultiPolygon jtsMultiPolygon = GeometryUtils
.getGeometryFactory()
.createMultiPolygon(polygons.toArray(new Polygon[0]));
if (!jtsMultiPolygon.isValid()) {
throw new AreaConstructionException();
}
return jtsMultiPolygon;
}
private boolean constructRingsRecursive(
ArrayListMultimap waysByEndpoint,
TLongList ring,
List closedRings,
long endpoint
) {
List ways = new ArrayList<>(waysByEndpoint.get(endpoint));
for (OSMWay way : ways) {
// remove this way from the map
TLongList nodeRefs = way.getNodeRefs();
long firstEndpoint = nodeRefs.get(0);
long otherEndpoint = nodeRefs.get(nodeRefs.size() - 1);
waysByEndpoint.remove(firstEndpoint, way);
waysByEndpoint.remove(otherEndpoint, way);
TLongList newRing = new TLongArrayList(ring.size() + nodeRefs.size());
long newFirstEndpoint;
if (firstEndpoint == endpoint) {
for (int j = nodeRefs.size() - 1; j >= 1; --j) {
newRing.add(nodeRefs.get(j));
}
newRing.addAll(ring);
newFirstEndpoint = otherEndpoint;
} else {
newRing.addAll(nodeRefs.subList(0, nodeRefs.size() - 1));
newRing.addAll(ring);
newFirstEndpoint = firstEndpoint;
}
if (newRing.get(newRing.size() - 1) == (newRing.get(0))) {
// ring closure
closedRings.add(newRing);
// if we're out of endpoints, then we have succeeded
if (waysByEndpoint.size() == 0) {
return true; // success
}
// otherwise, we need to start a new partial ring
newRing = new TLongArrayList();
OSMWay firstWay = null;
for (Long entry : waysByEndpoint.keySet()) {
List list = waysByEndpoint.get(entry);
firstWay = list.get(0);
nodeRefs = firstWay.getNodeRefs();
newRing.addAll(nodeRefs);
firstEndpoint = nodeRefs.get(0);
otherEndpoint = nodeRefs.get(nodeRefs.size() - 1);
break;
}
waysByEndpoint.remove(firstEndpoint, firstWay);
waysByEndpoint.remove(otherEndpoint, firstWay);
if (constructRingsRecursive(waysByEndpoint, newRing, closedRings, firstEndpoint)) {
return true;
}
waysByEndpoint.remove(firstEndpoint, firstWay);
waysByEndpoint.remove(otherEndpoint, firstWay);
} else {
// continue with this ring
if (waysByEndpoint.get(newFirstEndpoint) != null) {
if (constructRingsRecursive(waysByEndpoint, newRing, closedRings, newFirstEndpoint)) {
return true;
}
}
}
if (firstEndpoint == endpoint) {
waysByEndpoint.put(otherEndpoint, way);
} else {
waysByEndpoint.put(firstEndpoint, way);
}
}
return false;
}
public static class AreaConstructionException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy