From 31422cf63e5115b844ab6af64fc4d926aaca63a1 Mon Sep 17 00:00:00 2001 From: Rafael Troilo Date: Wed, 31 Jan 2024 16:21:10 +0100 Subject: [PATCH 01/13] refactor: extract out common code to `AbstractIsochroneMapBuilder` --- CHANGELOG.md | 1 + .../builders/AbstractIsochroneMapBuilder.java | 159 +++++++++++++ .../ConcaveBallsIsochroneMapBuilder.java | 141 +---------- .../concaveballs/PointItemVisitor.java | 63 ----- .../fast/FastIsochroneMapBuilder.java | 220 ++++-------------- 5 files changed, 207 insertions(+), 377 deletions(-) create mode 100644 ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java delete mode 100644 ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/PointItemVisitor.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b9b13f1ec..28a3b7de57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,7 @@ RELEASING: - do not apply speed penalty to roads tagged with "access=destination" when computing isochrones ([#1682](https://github.com/GIScience/openrouteservice/pull/1682)) - backend documentation overhaul ([#1651](https://github.com/GIScience/openrouteservice/pull/1651)) - separate docker image build from test workflow. Build nightly on schedule if there are changes to main ([#1689](https://github.com/GIScience/openrouteservice/pull/1689)) +- refactor isochrone builder classes ([#1699](https://github.com/GIScience/openrouteservice/pull/1699)) ### Deprecated - JSON configuration and related classes ([#1506](https://github.com/GIScience/openrouteservice/pull/1506)) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java new file mode 100644 index 0000000000..0092f3a92c --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java @@ -0,0 +1,159 @@ +package org.heigit.ors.isochrones.builders; + +import com.graphhopper.routing.util.FlagEncoder; +import com.graphhopper.routing.util.HikeFlagEncoder; +import com.graphhopper.util.DistanceCalc; +import com.graphhopper.util.DistancePlaneProjection; +import com.graphhopper.util.PointList; +import org.heigit.ors.routing.RouteSearchContext; +import org.heigit.ors.routing.graphhopper.extensions.flagencoders.FootFlagEncoder; +import org.heigit.ors.routing.graphhopper.extensions.flagencoders.ORSAbstractFlagEncoder; +import org.heigit.ors.routing.graphhopper.extensions.flagencoders.WheelchairFlagEncoder; +import org.heigit.ors.routing.graphhopper.extensions.flagencoders.bike.CommonBikeFlagEncoder; +import org.heigit.ors.util.GeomUtility; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.GeometryFactory; + +import java.util.List; + +public abstract class AbstractIsochroneMapBuilder implements IsochroneMapBuilder { + protected static final boolean BUFFERED_OUTPUT = true; + protected static final DistanceCalc dcFast = new DistancePlaneProjection(); + protected GeometryFactory geometryFactory; + protected RouteSearchContext searchContext; + protected double defaultSmoothingDistance = 0.012;// Use a default length of ~1333m + + public void initialize(RouteSearchContext searchContext) { + this.searchContext = searchContext; + this.geometryFactory = new GeometryFactory(); + } + + /** + * Converts the smoothing factor into a distance (which can be used in algorithms for generating isochrone polygons). + * The distance value returned is dependent on the radius and smoothing factor. + * + * @param smoothingFactor A factor that should be used in the smoothing process. Lower numbers produce a smaller + * distance (and so likely a more detailed polygon) + * @param maxRadius The maximum radius of the isochrone (in metres) + * @return + */ + protected double convertSmoothingFactorToDistance(float smoothingFactor, double maxRadius) { + final double MINIMUM_DISTANCE = 0.006; + + if (smoothingFactor == -1) { + // No user defined smoothing factor, so use defaults + + // For shorter isochrones, we want to use a smaller minimum distance else we get inaccurate results + if (maxRadius < 5000) + return MINIMUM_DISTANCE; + + return defaultSmoothingDistance; + } + + double intervalDegrees = GeomUtility.metresToDegrees(maxRadius); + double maxLength = (intervalDegrees / 100f) * smoothingFactor; + + if (maxLength < MINIMUM_DISTANCE) + maxLength = MINIMUM_DISTANCE; + return maxLength; + } + + protected void addPoint(List points, double lon, double lat) { + Coordinate p = new Coordinate(lon, lat); + points.add(p); + } + + protected void addBufferPoints(List points, double lon0, double lat0, double lon1, + double lat1, boolean addLast, double bufferSize) { + double dx = (lon0 - lon1); + double dy = (lat0 - lat1); + double normLength = Math.sqrt((dx * dx) + (dy * dy)); + double scale = bufferSize / normLength; + + double dx2 = -dy * scale; + double dy2 = dx * scale; + + addPoint(points, lon0 + dx2, lat0 + dy2); + addPoint(points, lon0 - dx2, lat0 - dy2); + + // add a middle point if two points are too far from each other + if (normLength > 2 * bufferSize) { + addPoint(points, (lon0 + lon1) / 2.0 + dx2, (lat0 + lat1) / 2.0 + dy2); + addPoint(points, (lon0 + lon1) / 2.0 - dx2, (lat0 + lat1) / 2.0 - dy2); + } + + if (addLast) { + addPoint(points, lon1 + dx2, lat1 + dy2); + addPoint(points, lon1 - dx2, lat1 - dy2); + } + } + + protected void addPointsFromEdge(List points, double isolineCost, float maxCost, float minCost, double bufferSize, double edgeDist, PointList pl) { + double edgeCost = maxCost - minCost; + double costPerMeter = edgeCost / edgeDist; + double distPolyline = 0.0; + + double lat0 = pl.getLat(0); + double lon0 = pl.getLon(0); + double lat1; + double lon1; + + for (int i = 1; i < pl.size(); ++i) { + lat1 = pl.getLat(i); + lon1 = pl.getLon(i); + + distPolyline += dcFast.calcDist(lat0, lon0, lat1, lon1); + + if (BUFFERED_OUTPUT) { + double distCost = minCost + distPolyline * costPerMeter; + if (distCost >= isolineCost) { + double segLength = (1 - (distCost - isolineCost) / edgeCost); + double lon2 = lon0 + segLength * (lon1 - lon0); + double lat2 = lat0 + segLength * (lat1 - lat0); + + addBufferPoints(points, lon0, lat0, lon2, lat2, true, bufferSize); + + break; + } else { + addBufferPoints(points, lon0, lat0, lon1, lat1, false, bufferSize); + } + } else { + addPoint(points, lon0, lat0); + } + + lat0 = lat1; + lon0 = lon1; + } + } + + protected double determineMaxSpeed() { + FlagEncoder encoder = searchContext.getEncoder(); + double maxSpeed = encoder.getMaxSpeed(); + + if (encoder instanceof FootFlagEncoder || encoder instanceof HikeFlagEncoder) { + // in the GH FootFlagEncoder, the maximum speed is set to 15km/h which is way too high + maxSpeed = 4; + } + + if (encoder instanceof WheelchairFlagEncoder) { + maxSpeed = WheelchairFlagEncoder.MEAN_SPEED; + } + + return maxSpeed; + } + + protected double determineMeanSpeed(double maxSpeed) { + double meanSpeed = maxSpeed; + FlagEncoder encoder = searchContext.getEncoder(); + + if (encoder instanceof ORSAbstractFlagEncoder flagEncoder) { + meanSpeed = flagEncoder.getMeanSpeed(); + } + + if (encoder instanceof CommonBikeFlagEncoder flagEncoder) { + meanSpeed = flagEncoder.getMeanSpeed(); + } + + return meanSpeed; + } +} diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index 05597d4f33..3f500fef22 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -17,8 +17,6 @@ import com.carrotsearch.hppc.cursors.IntObjectCursor; import com.graphhopper.coll.GHIntObjectHashMap; import com.graphhopper.routing.SPTEntry; -import com.graphhopper.routing.util.FlagEncoder; -import com.graphhopper.routing.util.HikeFlagEncoder; import com.graphhopper.storage.GraphHopperStorage; import com.graphhopper.storage.NodeAccess; import com.graphhopper.util.*; @@ -29,13 +27,8 @@ import org.heigit.ors.isochrones.Isochrone; import org.heigit.ors.isochrones.IsochroneMap; import org.heigit.ors.isochrones.IsochroneSearchParameters; -import org.heigit.ors.isochrones.builders.IsochroneMapBuilder; -import org.heigit.ors.routing.RouteSearchContext; +import org.heigit.ors.isochrones.builders.AbstractIsochroneMapBuilder; import org.heigit.ors.routing.graphhopper.extensions.AccessibilityMap; -import org.heigit.ors.routing.graphhopper.extensions.flagencoders.FootFlagEncoder; -import org.heigit.ors.routing.graphhopper.extensions.flagencoders.ORSAbstractFlagEncoder; -import org.heigit.ors.routing.graphhopper.extensions.flagencoders.WheelchairFlagEncoder; -import org.heigit.ors.routing.graphhopper.extensions.flagencoders.bike.CommonBikeFlagEncoder; import org.heigit.ors.util.GeomUtility; import org.locationtech.jts.geom.*; @@ -44,21 +37,12 @@ import static org.locationtech.jts.algorithm.hull.ConcaveHull.concaveHullByLength; -public class ConcaveBallsIsochroneMapBuilder implements IsochroneMapBuilder { +public class ConcaveBallsIsochroneMapBuilder extends AbstractIsochroneMapBuilder { private static final Logger LOGGER = Logger.getLogger(ConcaveBallsIsochroneMapBuilder.class.getName()); - private static final boolean BUFFERED_OUTPUT = true; private static final double MAX_SPLIT_LENGTH = 20000.0; private static final DistanceCalc dcFast = new DistancePlaneProjection(); - private GeometryFactory geometryFactory; private List prevIsoPoints = null; - private RouteSearchContext searchContext; - - public void initialize(RouteSearchContext searchContext) { - geometryFactory = new GeometryFactory(); - this.searchContext = searchContext; - } - public IsochroneMap compute(IsochroneSearchParameters parameters) throws Exception { StopWatch swTotal = null; StopWatch sw = null; @@ -72,26 +56,8 @@ public IsochroneMap compute(IsochroneSearchParameters parameters) throws Excepti GraphHopperStorage graph = searchContext.getGraphHopper().getGraphHopperStorage(); String graphdate = graph.getProperties().get("datareader.import.date"); - // 1. Find all graph edges for a given cost. - FlagEncoder encoder = searchContext.getEncoder(); - double maxSpeed = encoder.getMaxSpeed(); - - if (encoder instanceof FootFlagEncoder || encoder instanceof HikeFlagEncoder) { - // in the GH FootFlagEncoder, the maximum speed is set to 15km/h which is way too high - maxSpeed = 4; - } - - if (encoder instanceof WheelchairFlagEncoder) { - maxSpeed = WheelchairFlagEncoder.MEAN_SPEED; - } - - double meanSpeed = maxSpeed; - if (encoder instanceof ORSAbstractFlagEncoder flagEncoder) { - meanSpeed = flagEncoder.getMeanSpeed(); - } - if (encoder instanceof CommonBikeFlagEncoder flagEncoder) { - meanSpeed = flagEncoder.getMeanSpeed(); - } + double maxSpeed = determineMaxSpeed(); + double meanSpeed = determineMeanSpeed(maxSpeed); AccessibilityMap edgeMap = GraphEdgeMapFinder.findEdgeMap(searchContext, parameters); @@ -185,37 +151,6 @@ public IsochroneMap compute(IsochroneSearchParameters parameters) throws Excepti return isochroneMap; } - /** - * Converts the smoothing factor into a distance (which can be used in algorithms for generating isochrone polygons). - * The distance value returned is dependent on the radius and smoothing factor. - * - * @param smoothingFactor A factor that should be used in the smoothing process. Lower numbers produce a smaller - * distance (and so likely a more detailed polygon) - * @param maxRadius The maximum radius of the isochrone (in metres) - * @return - */ - private double convertSmoothingFactorToDistance(float smoothingFactor, double maxRadius) { - final double MINIMUM_DISTANCE = 0.006; - - if (smoothingFactor == -1) { - // No user defined smoothing factor, so use defaults - - // For shorter isochrones, we want to use a smaller minimum distance else we get inaccurate results - if (maxRadius < 5000) - return MINIMUM_DISTANCE; - - // Use a default length (~1333m) - return 0.012; - } - - double intervalDegrees = GeomUtility.metresToDegrees(maxRadius); - double maxLength = (intervalDegrees / 100f) * smoothingFactor; - - if (maxLength < MINIMUM_DISTANCE) - maxLength = MINIMUM_DISTANCE; - return maxLength; - } - private void addIsochrone(IsochroneMap isochroneMap, Coordinate[] points, double isoValue, double meanRadius, double smoothingDistance) { Geometry[] geometries = new Geometry[points.length]; for (int i = 0; i < points.length; ++i) { @@ -280,36 +215,6 @@ private void markDeadEndEdges(AccessibilityMap edgeMap) { } } - public void addPoint(List points, double lon, double lat) { - Coordinate p = new Coordinate(lon, lat); - points.add(p); - } - - private void addBufferPoints(List points, double lon0, double lat0, double lon1, - double lat1, boolean addLast, double bufferSize) { - double dx = (lon0 - lon1); - double dy = (lat0 - lat1); - double normLength = Math.sqrt((dx * dx) + (dy * dy)); - double scale = bufferSize / normLength; - - double dx2 = -dy * scale; - double dy2 = dx * scale; - - addPoint(points, lon0 + dx2, lat0 + dy2); - addPoint(points, lon0 - dx2, lat0 - dy2); - - // add a middle point if two points are too far from each other - if (normLength > 2 * bufferSize) { - addPoint(points, (lon0 + lon1) / 2.0 + dx2, (lat0 + lat1) / 2.0 + dy2); - addPoint(points, (lon0 + lon1) / 2.0 - dx2, (lat0 + lat1) / 2.0 - dy2); - } - - if (addLast) { - addPoint(points, lon1 + dx2, lat1 + dy2); - addPoint(points, lon1 - dx2, lat1 - dy2); - } - } - private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List points, double isolineCost, double prevCost, double isochronesDifference, double detailedGeomFactor, @@ -418,43 +323,7 @@ private void cutEdge(List points, double isolineCost, double minSpli if (pl.isEmpty()) { return; } - - double edgeDist = iter.getDistance(); - double edgeCost = maxCost - minCost; - double costPerMeter = edgeCost / edgeDist; - double distPolyline = 0.0; - - double lat0 = pl.getLat(0); - double lon0 = pl.getLon(0); - double lat1; - double lon1; - - for (int i = 1; i < pl.size(); ++i) { - lat1 = pl.getLat(i); - lon1 = pl.getLon(i); - - distPolyline += dcFast.calcDist(lat0, lon0, lat1, lon1); - - if (BUFFERED_OUTPUT) { - double distCost = minCost + distPolyline * costPerMeter; - if (distCost >= isolineCost) { - double segLength = (1 - (distCost - isolineCost) / edgeCost); - double lon2 = lon0 + segLength * (lon1 - lon0); - double lat2 = lat0 + segLength * (lat1 - lat0); - - addBufferPoints(points, lon0, lat0, lon2, lat2, true, bufferSize); - - break; - } else { - addBufferPoints(points, lon0, lat0, lon1, lat1, false, bufferSize); - } - } else { - addPoint(points, lon0, lat0); - } - - lat0 = lat1; - lon0 = lon1; - } + addPointsFromEdge(points, isolineCost, maxCost, minCost, bufferSize, iter.getDistance(), pl); } private void detailedShape(List points, double minSplitLength, EdgeIteratorState iter, boolean detailedShape, SPTEntry goalEdge, double bufferSize) { diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/PointItemVisitor.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/PointItemVisitor.java deleted file mode 100644 index cdef602dc7..0000000000 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/PointItemVisitor.java +++ /dev/null @@ -1,63 +0,0 @@ -/* This file is part of Openrouteservice. - * - * Openrouteservice 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 2.1 - * of the License, or (at your option) any later version. - - * This library 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 Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public License along with this library; - * if not, see . - */ -package org.heigit.ors.isochrones.builders.concaveballs; - -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.index.ItemVisitor; - -public class PointItemVisitor implements ItemVisitor { - private double threshold; - private boolean bFound; - private double lat; - private double lon; - - public PointItemVisitor(double lon, double lat, double threshold) { - this.lat = lat; - this.lon = lon; - this.threshold = threshold; - } - - public void setThreshold(double value) { - threshold = value; - } - - public void setPoint(double lon, double lat) { - this.lat = lat; - this.lon = lon; - bFound = false; - } - - public void visitItem(Object item) { - if (!bFound) { - Coordinate p = (Coordinate) item; - - double dx = p.x - lon; - if (dx > threshold) - return; - - double dy = p.y - lat; - if (Math.abs(dy) > threshold) - return; - - double dist = Math.sqrt(dx * dx + dy * dy); - if (dist < threshold) - bFound = true; - } - } - - public boolean isNeighbourFound() { - return bFound; - } -} - diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java index e77546bdb2..c771145ba8 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java @@ -28,6 +28,7 @@ import com.graphhopper.storage.index.Snap; import com.graphhopper.util.*; import com.graphhopper.util.shapes.GHPoint3D; +import org.heigit.ors.isochrones.builders.AbstractIsochroneMapBuilder; import org.heigit.ors.util.ProfileTools; import org.locationtech.jts.geom.*; import org.locationtech.jts.operation.union.UnaryUnionOp; @@ -41,7 +42,6 @@ import org.heigit.ors.isochrones.IsochroneMap; import org.heigit.ors.isochrones.IsochroneSearchParameters; import org.heigit.ors.isochrones.IsochronesErrorCodes; -import org.heigit.ors.isochrones.builders.IsochroneMapBuilder; import org.heigit.ors.routing.AvoidFeatureFlags; import org.heigit.ors.routing.RouteSearchContext; import org.heigit.ors.routing.graphhopper.extensions.AccessibilityMap; @@ -50,9 +50,6 @@ import org.heigit.ors.routing.graphhopper.extensions.ORSWeightingFactory; import org.heigit.ors.routing.graphhopper.extensions.edgefilters.AvoidFeaturesEdgeFilter; import org.heigit.ors.routing.graphhopper.extensions.edgefilters.EdgeFilterSequence; -import org.heigit.ors.routing.graphhopper.extensions.flagencoders.FootFlagEncoder; -import org.heigit.ors.routing.graphhopper.extensions.flagencoders.ORSAbstractFlagEncoder; -import org.heigit.ors.routing.graphhopper.extensions.flagencoders.WheelchairFlagEncoder; import org.heigit.ors.util.DebugUtility; import org.heigit.ors.util.GeomUtility; @@ -67,17 +64,13 @@ * * @author Hendrik Leuschner */ -public class FastIsochroneMapBuilder implements IsochroneMapBuilder { - private GeometryFactory geomFactory; +public class FastIsochroneMapBuilder extends AbstractIsochroneMapBuilder { private Polygon previousIsochronePolygon = null; - private RouteSearchContext searchcontext; private CellStorage cellStorage; private IsochroneNodeStorage isochroneNodeStorage; private QueryGraph queryGraph; private static final int MAX_EDGE_LENGTH_LIMIT = Integer.MAX_VALUE; - private static final boolean BUFFERED_OUTPUT = true; private static final double ACTIVE_CELL_APPROXIMATION_FACTOR = 0.99; - private static final DistanceCalc dcFast = new DistancePlaneProjection(); private static final Logger LOGGER = Logger.getLogger(FastIsochroneMapBuilder.class.getName()); /* @@ -101,10 +94,11 @@ public static double distance(double lat1, double lat2, double lon1, } public void initialize(RouteSearchContext searchContext) { - geomFactory = new GeometryFactory(); - searchcontext = searchContext; - cellStorage = ((ORSGraphHopper) searchcontext.getGraphHopper()).getFastIsochroneFactory().getCellStorage(); - isochroneNodeStorage = ((ORSGraphHopper) searchcontext.getGraphHopper()).getFastIsochroneFactory().getIsochroneNodeStorage(); + super.initialize(searchContext); + defaultSmoothingDistance = 0.010;// Use a default length of ~1000m + var fastIsochroneFactory = ((ORSGraphHopper) searchContext.getGraphHopper()).getFastIsochroneFactory(); + this.cellStorage = fastIsochroneFactory.getCellStorage(); + this.isochroneNodeStorage = fastIsochroneFactory.getIsochroneNodeStorage(); } public IsochroneMap compute(IsochroneSearchParameters parameters) throws Exception { @@ -124,19 +118,19 @@ public IsochroneMap compute(IsochroneSearchParameters parameters) throws Excepti // only needed for reachfactor property double meanMetersPerSecond = meanSpeed / 3.6; - Weighting weighting = ORSWeightingFactory.createIsochroneWeighting(searchcontext, parameters.getRangeType()); + Weighting weighting = ORSWeightingFactory.createIsochroneWeighting(searchContext, parameters.getRangeType()); Coordinate loc = parameters.getLocation(); - FlagEncoder encoder = searchcontext.getEncoder(); + FlagEncoder encoder = searchContext.getEncoder(); String profileName = ProfileTools.makeProfileName(encoder.toString(), weighting.getName(), false); - GraphHopper gh = searchcontext.getGraphHopper(); + GraphHopper gh = searchContext.getGraphHopper(); GraphHopperStorage graphHopperStorage = gh.getGraphHopperStorage(); EdgeFilter defaultSnapFilter = new DefaultSnapFilter(weighting, graphHopperStorage.getEncodingManager().getBooleanEncodedValue(Subnetwork.key(profileName))); ORSEdgeFilterFactory edgeFilterFactory = new ORSEdgeFilterFactory(); EdgeFilterSequence edgeFilterSequence = getEdgeFilterSequence(edgeFilterFactory, defaultSnapFilter); - Snap res = searchcontext.getGraphHopper().getLocationIndex().findClosest(loc.y, loc.x, edgeFilterSequence); + Snap res = searchContext.getGraphHopper().getLocationIndex().findClosest(loc.y, loc.x, edgeFilterSequence); List snaps = new ArrayList<>(1); snaps.add(res); //Needed to get the cell of the start point (preprocessed information, so no info on virtual nodes) @@ -144,13 +138,13 @@ public IsochroneMap compute(IsochroneSearchParameters parameters) throws Excepti if (nonvirtualClosestNode == -1) throw new InternalServerException(IsochronesErrorCodes.UNKNOWN, "The closest node is null."); - Graph graph = searchcontext.getGraphHopper().getGraphHopperStorage().getBaseGraph(); + Graph graph = searchContext.getGraphHopper().getGraphHopperStorage().getBaseGraph(); queryGraph = QueryGraph.create(graph, snaps); int from = res.getClosestNode(); //This calculates the nodes that are within the limit //Currently only support for Node based - if (!(searchcontext.getGraphHopper() instanceof ORSGraphHopper)) + if (!(searchContext.getGraphHopper() instanceof ORSGraphHopper)) throw new IllegalStateException("Unable to run fast isochrones without ORSGraphhopper"); int nRanges = parameters.getRanges().length; @@ -163,8 +157,8 @@ public IsochroneMap compute(IsochroneSearchParameters parameters) throws Excepti TraversalMode.NODE_BASED, cellStorage, isochroneNodeStorage, - ((ORSGraphHopper) searchcontext.getGraphHopper()).getEccentricity().getEccentricityStorage(weighting), - ((ORSGraphHopper) searchcontext.getGraphHopper()).getEccentricity().getBorderNodeDistanceStorage(weighting), + ((ORSGraphHopper) searchContext.getGraphHopper()).getEccentricity().getEccentricityStorage(weighting), + ((ORSGraphHopper) searchContext.getGraphHopper()).getEccentricity().getBorderNodeDistanceStorage(weighting), edgeFilterSequence); //Account for snapping distance double isolimit = parameters.getRanges()[i] - weighting.getMinWeight(res.getQueryDistance()); @@ -257,35 +251,12 @@ public IsochroneMap compute(IsochroneSearchParameters parameters) throws Excepti private EdgeFilterSequence getEdgeFilterSequence(ORSEdgeFilterFactory edgeFilterFactory, EdgeFilter prependFilter) throws Exception { EdgeFilterSequence edgeFilterSequence = new EdgeFilterSequence(); - EdgeFilter edgeFilter = edgeFilterFactory.createEdgeFilter(searchcontext.getProperties(), searchcontext.getEncoder(), searchcontext.getGraphHopper().getGraphHopperStorage(), prependFilter); + EdgeFilter edgeFilter = edgeFilterFactory.createEdgeFilter(searchContext.getProperties(), searchContext.getEncoder(), searchContext.getGraphHopper().getGraphHopperStorage(), prependFilter); edgeFilterSequence.add(edgeFilter); - edgeFilterSequence.add(new AvoidFeaturesEdgeFilter(AvoidFeatureFlags.FERRIES, searchcontext.getGraphHopper().getGraphHopperStorage())); + edgeFilterSequence.add(new AvoidFeaturesEdgeFilter(AvoidFeatureFlags.FERRIES, searchContext.getGraphHopper().getGraphHopperStorage())); return edgeFilterSequence; } - private double determineMeanSpeed(double maxSpeed) { - double meanSpeed = maxSpeed; - if (searchcontext.getEncoder() instanceof ORSAbstractFlagEncoder) { - meanSpeed = ((ORSAbstractFlagEncoder) searchcontext.getEncoder()).getMeanSpeed(); - } - return meanSpeed; - } - - private double determineMaxSpeed() { - // 1. Find all graph edges for a given cost. - double maxSpeed = searchcontext.getEncoder().getMaxSpeed(); - - if (searchcontext.getEncoder() instanceof FootFlagEncoder || searchcontext.getEncoder() instanceof HikeFlagEncoder) { - // in the GH FootFlagEncoder, the maximum speed is set to 15km/h which is way too high - maxSpeed = 4; - } - - if (searchcontext.getEncoder() instanceof WheelchairFlagEncoder) { - maxSpeed = WheelchairFlagEncoder.MEAN_SPEED; - } - return maxSpeed; - } - private void buildActiveCellsConcaveHulls(FastIsochroneAlgorithm fastIsochroneAlgorithm, Set isochroneGeometries, Coordinate snappedLoc, GHPoint3D snappedPosition, double isoValue, double smoothingDistance, double minSplitLength) { //Build concave hulls of all active cells individually StopWatch swActiveCellSeparate = new StopWatch(); @@ -361,37 +332,6 @@ private void addPreviousIsochronePolygon(Set isochroneGeometries) { isochroneGeometries.add(previousIsochronePolygon); } - /** - * Converts the smoothing factor into a distance (which can be used in algorithms for generating isochrone polygons). - * The distance value returned is dependent on the radius and smoothing factor. - * - * @param smoothingFactor A factor that should be used in the smoothing process. Lower numbers produce a smaller - * distance (and so likely a more detailed polygon) - * @param maxRadius The maximum radius of the isochrone (in metres) - * @return - */ - private double convertSmoothingFactorToDistance(float smoothingFactor, double maxRadius) { - double minimumDistance = 0.006; - - if (smoothingFactor == -1) { - // No user defined smoothing factor, so use defaults - - // For shorter isochrones, we want to use a smaller minimum distance else we get inaccurate results - if (maxRadius < 5000) - return minimumDistance; - - // Use a default length (~1000m) - return 0.010; - } - - double intervalDegrees = GeomUtility.metresToDegrees(maxRadius); - double maxLength = (intervalDegrees / 100f) * smoothingFactor; - - if (maxLength < minimumDistance) - maxLength = minimumDistance; - return maxLength; - } - private void createPolyFromPoints(Set isochroneGeometries, GeometryCollection points, double smoothingDistance, double minSplitLength) { if (points.isEmpty()) return; @@ -426,40 +366,11 @@ private void addIsochrone(IsochroneMap isochroneMap, GeometryCollection points, isochroneMap.addIsochrone(new Isochrone(poly, isoValue, meanRadius)); } - public void addPoint(List points, double lon, double lat) { - points.add(new Coordinate(lon, lat)); - } - - private void addBufferPoints(List points, double lon0, double lat0, double lon1, - double lat1, boolean addLast, double bufferSize) { - double dx = (lon0 - lon1); - double dy = (lat0 - lat1); - double normLength = Math.sqrt((dx * dx) + (dy * dy)); - double scale = bufferSize / normLength; - - double dx2 = -dy * scale; - double dy2 = dx * scale; - - addPoint(points, lon0 + dx2, lat0 + dy2); - addPoint(points, lon0 - dx2, lat0 - dy2); - - // add a middle point if two points are too far from each other - if (normLength > 2 * bufferSize) { - addPoint(points, (lon0 + lon1) / 2.0 + dx2, (lat0 + lat1) / 2.0 + dy2); - addPoint(points, (lon0 + lon1) / 2.0 - dx2, (lat0 + lat1) / 2.0 - dy2); - } - - if (addLast) { - addPoint(points, lon1 + dx2, lat1 + dy2); - addPoint(points, lon1 - dx2, lat1 - dy2); - } - } - private GeometryCollection buildIsochrone(AccessibilityMap edgeMap, List contourCoordinates, List points, double isolineCost, double minSplitLength) { IntObjectMap map = edgeMap.getMap(); - GraphHopperStorage graphHopperStorage = searchcontext.getGraphHopper().getGraphHopperStorage(); + GraphHopperStorage graphHopperStorage = searchContext.getGraphHopper().getGraphHopperStorage(); int maxNodeId = graphHopperStorage.getNodes() - 1; int maxEdgeId = graphHopperStorage.getEdges() - 1; @@ -505,10 +416,10 @@ private GeometryCollection buildIsochrone(AccessibilityMap edgeMap, List for (int i = 0; i < points.size(); ++i) { Coordinate c = points.get(i); - geometries[i] = geomFactory.createPoint(c); + geometries[i] = geometryFactory.createPoint(c); } - return new GeometryCollection(geometries, geomFactory); + return new GeometryCollection(geometries, geometryFactory); } private void addContourCoordinates(List contourCoordinates, List points) { @@ -524,69 +435,33 @@ private void addContourCoordinates(List contourCoordinates, List points, double bufferSize, float maxCost, float minCost, double isolineCost) { PointList pl = iter.fetchWayGeometry(FetchMode.ALL); - int size = pl.size(); - if (size > 0) { - double edgeCost = maxCost - minCost; - double edgeDist = iter.getDistance(); - double costPerMeter = edgeCost / edgeDist; - double distPolyline = 0.0; - - double lat0 = pl.getLat(0); - double lon0 = pl.getLon(0); - double lat1; - double lon1; - - for (int i = 1; i < size; ++i) { - lat1 = pl.getLat(i); - lon1 = pl.getLon(i); - - distPolyline += dcFast.calcDist(lat0, lon0, lat1, lon1); - - if (BUFFERED_OUTPUT) { - double distCost = minCost + distPolyline * costPerMeter; - if (distCost >= isolineCost) { - double segLength = (1 - (distCost - isolineCost) / edgeCost); - double lon2 = lon0 + segLength * (lon1 - lon0); - double lat2 = lat0 + segLength * (lat1 - lat0); - - addBufferPoints(points, lon0, lat0, lon2, lat2, true, bufferSize); - - break; - } else { - addBufferPoints(points, lon0, lat0, lon1, lat1, false, bufferSize); - } - } else { - addPoint(points, lon0, lat0); - } - - lat0 = lat1; - lon0 = lon1; - } + if (pl.isEmpty()) { + return; } + addPointsFromEdge(points, isolineCost, maxCost, minCost, bufferSize, iter.getDistance(), pl); } private void addBufferedWayGeometry(List points, double bufferSize, EdgeIteratorState iter, double minSplitLength) { // always use mode=3, since other ones do not provide correct results - PointList pl = iter.fetchWayGeometry(FetchMode.ALL); - // Always buffer geometry - pl = expandAndBufferPointList(pl, bufferSize, minSplitLength, MAX_EDGE_LENGTH_LIMIT); + var pl = expandAndBufferPointList(iter.fetchWayGeometry(FetchMode.ALL), bufferSize, minSplitLength, MAX_EDGE_LENGTH_LIMIT); + if(pl.isEmpty()){ + return; + } int size = pl.size(); - if (size > 0) { - double lat0 = pl.getLat(0); - double lon0 = pl.getLon(0); - double lat1; - double lon1; - for (int i = 1; i < size; ++i) { - lat1 = pl.getLat(i); - lon1 = pl.getLon(i); - - addPoint(points, lon0, lat0); - if (i == size - 1) - addPoint(points, lon1, lat1); - - lon0 = lon1; - lat0 = lat1; - } + double lat0 = pl.getLat(0); + double lon0 = pl.getLon(0); + double lat1; + double lon1; + for (int i = 1; i < size; ++i) { + lat1 = pl.getLat(i); + lon1 = pl.getLon(i); + + addPoint(points, lon0, lat0); + if (i == size - 1) + addPoint(points, lon1, lat1); + + lon0 = lon1; + lat0 = lat1; } } @@ -647,7 +522,7 @@ private void addCellPolygon(int cellId, Set isochronePolygons) { for (int n = cArray.length - 1; n >= 0; n--) { cArray[cArray.length - 1 - n] = new Coordinate(coordinates.get(2 * n + 1).floatValue(), coordinates.get(2 * n).floatValue()); } - Polygon polygon = geomFactory.createPolygon(cArray); + Polygon polygon = geometryFactory.createPolygon(cArray); if (polygon.isValid() && !polygon.isEmpty()) { isochronePolygons.add(polygon); } else @@ -671,17 +546,6 @@ private String printCell(List coordinates, int cellId) { return statement.toString(); } - private void splitEdgeToCoordinates(double lat0, double lat1, double lon0, double lon1, List coordinates, double minlim, double maxlim) { - double dist = distance(lat0, lat1, lon0, lon1); - - if (dist > minlim && dist < maxlim) { - int n = (int) Math.ceil(dist / minlim); - for (int i = 1; i < n; i++) { - coordinates.add(new Coordinate((lon0 + i * (lon1 - lon0) / n), (lat0 + i * (lat1 - lat0) / n))); - } - } - } - private void splitEdgeToDoubles(double lat0, double lat1, double lon0, double lon1, List coordinates, double minlim, double maxlim) { double dist = distance(lat0, lat1, lon0, lon1); From bd3d12d8cee1539d46f66b47ebb31ee47fd3c7ae Mon Sep 17 00:00:00 2001 From: aoles Date: Mon, 26 Feb 2024 09:36:30 +0100 Subject: [PATCH 02/13] refactor: simplify loops --- .../ConcaveBallsIsochroneMapBuilder.java | 11 ++--------- .../builders/fast/FastIsochroneMapBuilder.java | 17 ++--------------- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index 3f500fef22..159e0f2e56 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -350,16 +350,9 @@ private void detailedShape(List points, double minSplitLength, EdgeI lat0 = lat1; } } else { - for (int i = 1; i < size; ++i) { - lat1 = pl.getLat(i); - lon1 = pl.getLon(i); - - addPoint(points, lon0, lat0); - - lon0 = lon1; - lat0 = lat1; + for (int i = 0; i < pl.size(); ++i) { + addPoint(points, pl.getLon(i), pl.getLat(i)); } - addPoint(points, lon0, lat0); } } diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java index c771145ba8..bf732d0f1d 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java @@ -447,21 +447,8 @@ private void addBufferedWayGeometry(List points, double bufferSize, if(pl.isEmpty()){ return; } - int size = pl.size(); - double lat0 = pl.getLat(0); - double lon0 = pl.getLon(0); - double lat1; - double lon1; - for (int i = 1; i < size; ++i) { - lat1 = pl.getLat(i); - lon1 = pl.getLon(i); - - addPoint(points, lon0, lat0); - if (i == size - 1) - addPoint(points, lon1, lat1); - - lon0 = lon1; - lat0 = lat1; + for (int i = 0; i < pl.size(); ++i) { + addPoint(points, pl.getLon(i), pl.getLat(i)); } } From ab83d2540171b36dcce7f66220e06eb19ad53a8e Mon Sep 17 00:00:00 2001 From: aoles Date: Mon, 26 Feb 2024 13:21:07 +0100 Subject: [PATCH 03/13] refactor: the same method for cutting edges across isochrone builders --- .../builders/AbstractIsochroneMapBuilder.java | 57 ++++++++++++++++++- .../ConcaveBallsIsochroneMapBuilder.java | 54 +----------------- .../fast/FastIsochroneMapBuilder.java | 10 +--- 3 files changed, 56 insertions(+), 65 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java index 0092f3a92c..87af95be6b 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java @@ -2,9 +2,8 @@ import com.graphhopper.routing.util.FlagEncoder; import com.graphhopper.routing.util.HikeFlagEncoder; -import com.graphhopper.util.DistanceCalc; -import com.graphhopper.util.DistancePlaneProjection; -import com.graphhopper.util.PointList; +import com.graphhopper.util.*; +import com.graphhopper.util.shapes.GHPoint3D; import org.heigit.ors.routing.RouteSearchContext; import org.heigit.ors.routing.graphhopper.extensions.flagencoders.FootFlagEncoder; import org.heigit.ors.routing.graphhopper.extensions.flagencoders.ORSAbstractFlagEncoder; @@ -18,6 +17,7 @@ public abstract class AbstractIsochroneMapBuilder implements IsochroneMapBuilder { protected static final boolean BUFFERED_OUTPUT = true; + private static final double MAX_SPLIT_LENGTH = 20000.0; protected static final DistanceCalc dcFast = new DistancePlaneProjection(); protected GeometryFactory geometryFactory; protected RouteSearchContext searchContext; @@ -126,6 +126,57 @@ protected void addPointsFromEdge(List points, double isolineCost, fl } } + /** + * Splits a line between two points of the distance is longer than a limit + * + * @param point0 point0 of line + * @param point1 point1 of line + * @param minlim limit above which the edge will be split (in meters) + * @param maxlim limit above which the edge will NOT be split anymore (in meters) + */ + private void splitEdge(GHPoint3D point0, GHPoint3D point1, PointList pointList, double minlim, double maxlim) { + //No need to consider elevation + double lat0 = point0.getLat(); + double lon0 = point0.getLon(); + double lat1 = point1.getLat(); + double lon1 = point1.getLon(); + double dist = dcFast.calcDist(lat0, lon0, lat1, lon1); + boolean is3D = pointList.is3D(); + + if (dist > minlim && dist < maxlim) { + int n = (int) Math.ceil(dist / minlim); + for (int i = 1; i < n; i++) { + if (is3D) + pointList.add(lat0 + i * (lat1 - lat0) / n, lon0 + i * (lon1 - lon0) / n, 0); + else + pointList.add(lat0 + i * (lat1 - lat0) / n, lon0 + i * (lon1 - lon0) / n); + } + } + } + + protected PointList edgeToPoints(EdgeIteratorState iter, double minSplitLength) { + // always use mode=3, since other ones do not provide correct results + PointList pl = iter.fetchWayGeometry(FetchMode.ALL); + + double edgeDist = iter.getDistance(); + if (edgeDist > minSplitLength) { + PointList expandedPoints = new PointList(pl.size(), pl.is3D()); + for (int i = 0; i < pl.size() - 1; i++) + splitEdge(pl.get(i), pl.get(i + 1), expandedPoints, minSplitLength, MAX_SPLIT_LENGTH); + pl.add(expandedPoints); + } + + return pl; + } + + protected void addCutEdgeGeometry(List points, double isolineCost, double minSplitLength, EdgeIteratorState iter, float maxCost, float minCost, double bufferSize) { + PointList pl = edgeToPoints(iter, minSplitLength); + if (pl.isEmpty()) { + return; + } + addPointsFromEdge(points, isolineCost, maxCost, minCost, bufferSize, iter.getDistance(), pl); + } + protected double determineMaxSpeed() { FlagEncoder encoder = searchContext.getEncoder(); double maxSpeed = encoder.getMaxSpeed(); diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index 159e0f2e56..01dca80974 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -39,7 +39,6 @@ public class ConcaveBallsIsochroneMapBuilder extends AbstractIsochroneMapBuilder { private static final Logger LOGGER = Logger.getLogger(ConcaveBallsIsochroneMapBuilder.class.getName()); - private static final double MAX_SPLIT_LENGTH = 20000.0; private static final DistanceCalc dcFast = new DistancePlaneProjection(); private List prevIsoPoints = null; @@ -285,7 +284,7 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p if ((minCost < isolineCost && maxCost >= isolineCost)) { if (LOGGER.isDebugEnabled()) sw.start(); - cutEdge(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize); + addCutEdgeGeometry(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize); if (LOGGER.isDebugEnabled()) sw.stop(); } @@ -303,29 +302,6 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p return coordinates; } - private PointList edgeToPoints(EdgeIteratorState iter, double minSplitLength) { - // always use mode=3, since other ones do not provide correct results - PointList pl = iter.fetchWayGeometry(FetchMode.ALL); - - double edgeDist = iter.getDistance(); - if (edgeDist > minSplitLength) { - PointList expandedPoints = new PointList(pl.size(), pl.is3D()); - for (int i = 0; i < pl.size() - 1; i++) - splitEdge(pl.get(i), pl.get(i + 1), expandedPoints, minSplitLength, MAX_SPLIT_LENGTH); - pl.add(expandedPoints); - } - - return pl; - } - - private void cutEdge(List points, double isolineCost, double minSplitLength, EdgeIteratorState iter, float maxCost, float minCost, double bufferSize) { - PointList pl = edgeToPoints(iter, minSplitLength); - if (pl.isEmpty()) { - return; - } - addPointsFromEdge(points, isolineCost, maxCost, minCost, bufferSize, iter.getDistance(), pl); - } - private void detailedShape(List points, double minSplitLength, EdgeIteratorState iter, boolean detailedShape, SPTEntry goalEdge, double bufferSize) { PointList pl = edgeToPoints(iter, minSplitLength); if (pl.isEmpty()) { @@ -367,32 +343,4 @@ private void copyConvexHullPoints(Polygon poly) { prevIsoPoints.add(new Coordinate(p.getX(), p.getY())); } } - - /** - * Splits a line between two points of the distance is longer than a limit - * - * @param point0 point0 of line - * @param point1 point1 of line - * @param minlim limit above which the edge will be split (in meters) - * @param maxlim limit above which the edge will NOT be split anymore (in meters) - */ - private void splitEdge(GHPoint3D point0, GHPoint3D point1, PointList pointList, double minlim, double maxlim) { - //No need to consider elevation - double lat0 = point0.getLat(); - double lon0 = point0.getLon(); - double lat1 = point1.getLat(); - double lon1 = point1.getLon(); - double dist = dcFast.calcDist(lat0, lon0, lat1, lon1); - boolean is3D = pointList.is3D(); - - if (dist > minlim && dist < maxlim) { - int n = (int) Math.ceil(dist / minlim); - for (int i = 1; i < n; i++) { - if (is3D) - pointList.add(lat0 + i * (lat1 - lat0) / n, lon0 + i * (lon1 - lon0) / n, 0); - else - pointList.add(lat0 + i * (lat1 - lat0) / n, lon0 + i * (lon1 - lon0) / n); - } - } - } } diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java index bf732d0f1d..0ea7b7b1fc 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java @@ -407,7 +407,7 @@ private GeometryCollection buildIsochrone(AccessibilityMap edgeMap, List } } else { if ((minCost < isolineCost && maxCost >= isolineCost)) { - addEdgeCaseGeometry(iter, points, bufferSize, maxCost, minCost, isolineCost); + addCutEdgeGeometry(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize); } } } @@ -433,14 +433,6 @@ private void addContourCoordinates(List contourCoordinates, List points, double bufferSize, float maxCost, float minCost, double isolineCost) { - PointList pl = iter.fetchWayGeometry(FetchMode.ALL); - if (pl.isEmpty()) { - return; - } - addPointsFromEdge(points, isolineCost, maxCost, minCost, bufferSize, iter.getDistance(), pl); - } - private void addBufferedWayGeometry(List points, double bufferSize, EdgeIteratorState iter, double minSplitLength) { // always use mode=3, since other ones do not provide correct results var pl = expandAndBufferPointList(iter.fetchWayGeometry(FetchMode.ALL), bufferSize, minSplitLength, MAX_EDGE_LENGTH_LIMIT); From dd7e3df99b4fdab5d0456979fe111a66490f1a79 Mon Sep 17 00:00:00 2001 From: aoles Date: Mon, 26 Feb 2024 13:22:27 +0100 Subject: [PATCH 04/13] refactor: remove spurious flag --- .../builders/AbstractIsochroneMapBuilder.java | 21 +++++++------------ .../ConcaveBallsIsochroneMapBuilder.java | 2 +- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java index 87af95be6b..0065308841 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java @@ -16,7 +16,6 @@ import java.util.List; public abstract class AbstractIsochroneMapBuilder implements IsochroneMapBuilder { - protected static final boolean BUFFERED_OUTPUT = true; private static final double MAX_SPLIT_LENGTH = 20000.0; protected static final DistanceCalc dcFast = new DistancePlaneProjection(); protected GeometryFactory geometryFactory; @@ -104,21 +103,17 @@ protected void addPointsFromEdge(List points, double isolineCost, fl distPolyline += dcFast.calcDist(lat0, lon0, lat1, lon1); - if (BUFFERED_OUTPUT) { - double distCost = minCost + distPolyline * costPerMeter; - if (distCost >= isolineCost) { - double segLength = (1 - (distCost - isolineCost) / edgeCost); - double lon2 = lon0 + segLength * (lon1 - lon0); - double lat2 = lat0 + segLength * (lat1 - lat0); + double distCost = minCost + distPolyline * costPerMeter; + if (distCost >= isolineCost) { + double segLength = (1 - (distCost - isolineCost) / edgeCost); + double lon2 = lon0 + segLength * (lon1 - lon0); + double lat2 = lat0 + segLength * (lat1 - lat0); - addBufferPoints(points, lon0, lat0, lon2, lat2, true, bufferSize); + addBufferPoints(points, lon0, lat0, lon2, lat2, true, bufferSize); - break; - } else { - addBufferPoints(points, lon0, lat0, lon1, lat1, false, bufferSize); - } + break; } else { - addPoint(points, lon0, lat0); + addBufferPoints(points, lon0, lat0, lon1, lat1, false, bufferSize); } lat0 = lat1; diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index 01dca80974..14b24f797b 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -315,7 +315,7 @@ private void detailedShape(List points, double minSplitLength, EdgeI double lat1; double lon1; - if (detailedShape && BUFFERED_OUTPUT) { + if (detailedShape) { for (int i = 1; i < size; ++i) { lat1 = pl.getLat(i); lon1 = pl.getLon(i); From 098871e7aa9cbff24180c75e40744283261afa10 Mon Sep 17 00:00:00 2001 From: aoles Date: Mon, 26 Feb 2024 14:10:01 +0100 Subject: [PATCH 05/13] refactor: remove obsolete condition --- .../builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index 14b24f797b..03058039b2 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -270,7 +270,7 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p if (goalEdge.edge != -2 || useHighDetail) { double edgeDist = iter.getDistance(); boolean detailedShape = (edgeDist > 200); - if (((maxCost >= detailedZone && maxCost <= isolineCost) || detailedShape)) { + if (maxCost >= detailedZone || detailedShape) { if (LOGGER.isDebugEnabled()) sw.start(); detailedShape(points, minSplitLength, iter, detailedShape, goalEdge, bufferSize); From cc22be960f4f81d24da1837efc13eef698c5a8f5 Mon Sep 17 00:00:00 2001 From: aoles Date: Mon, 26 Feb 2024 17:29:17 +0100 Subject: [PATCH 06/13] refactor: unified across isochrone builders the way of buffering edges --- .../builders/AbstractIsochroneMapBuilder.java | 33 ++++++++++++- .../ConcaveBallsIsochroneMapBuilder.java | 34 +------------ .../fast/FastIsochroneMapBuilder.java | 48 +------------------ 3 files changed, 36 insertions(+), 79 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java index 0065308841..ab5012c124 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java @@ -1,5 +1,6 @@ package org.heigit.ors.isochrones.builders; +import com.graphhopper.routing.SPTEntry; import com.graphhopper.routing.util.FlagEncoder; import com.graphhopper.routing.util.HikeFlagEncoder; import com.graphhopper.util.*; @@ -164,7 +165,37 @@ protected PointList edgeToPoints(EdgeIteratorState iter, double minSplitLength) return pl; } - protected void addCutEdgeGeometry(List points, double isolineCost, double minSplitLength, EdgeIteratorState iter, float maxCost, float minCost, double bufferSize) { + protected void addBufferedEdgeGeometry(List points, double minSplitLength, EdgeIteratorState iter, boolean detailedShape, SPTEntry goalEdge, double bufferSize) { + PointList pl = edgeToPoints(iter, minSplitLength); + if (pl.isEmpty()) { + return; + } + + int size = pl.size(); + + double lat0 = pl.getLat(0); + double lon0 = pl.getLon(0); + double lat1; + double lon1; + + if (detailedShape) { + for (int i = 1; i < size; ++i) { + lat1 = pl.getLat(i); + lon1 = pl.getLon(i); + + addBufferPoints(points, lon0, lat0, lon1, lat1, goalEdge.edge < 0 && i == size - 1, bufferSize); + + lon0 = lon1; + lat0 = lat1; + } + } else { + for (int i = 0; i < size; ++i) { + addPoint(points, pl.getLon(i), pl.getLat(i)); + } + } + } + + protected void addCuttedEdgeGeometry(List points, double isolineCost, double minSplitLength, EdgeIteratorState iter, float maxCost, float minCost, double bufferSize) { PointList pl = edgeToPoints(iter, minSplitLength); if (pl.isEmpty()) { return; diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index 03058039b2..64b0b7f3c6 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -273,7 +273,7 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p if (maxCost >= detailedZone || detailedShape) { if (LOGGER.isDebugEnabled()) sw.start(); - detailedShape(points, minSplitLength, iter, detailedShape, goalEdge, bufferSize); + addBufferedEdgeGeometry(points, minSplitLength, iter, detailedShape, goalEdge, bufferSize); if (LOGGER.isDebugEnabled()) sw.stop(); } else { @@ -284,7 +284,7 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p if ((minCost < isolineCost && maxCost >= isolineCost)) { if (LOGGER.isDebugEnabled()) sw.start(); - addCutEdgeGeometry(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize); + addCuttedEdgeGeometry(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize); if (LOGGER.isDebugEnabled()) sw.stop(); } @@ -302,36 +302,6 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p return coordinates; } - private void detailedShape(List points, double minSplitLength, EdgeIteratorState iter, boolean detailedShape, SPTEntry goalEdge, double bufferSize) { - PointList pl = edgeToPoints(iter, minSplitLength); - if (pl.isEmpty()) { - return; - } - - int size = pl.size(); - - double lat0 = pl.getLat(0); - double lon0 = pl.getLon(0); - double lat1; - double lon1; - - if (detailedShape) { - for (int i = 1; i < size; ++i) { - lat1 = pl.getLat(i); - lon1 = pl.getLon(i); - - addBufferPoints(points, lon0, lat0, lon1, lat1, goalEdge.edge < 0 && i == size - 1, bufferSize); - - lon0 = lon1; - lat0 = lat1; - } - } else { - for (int i = 0; i < pl.size(); ++i) { - addPoint(points, pl.getLon(i), pl.getLat(i)); - } - } - } - private void copyConvexHullPoints(Polygon poly) { LineString ring = poly.getExteriorRing(); if (prevIsoPoints == null) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java index 0ea7b7b1fc..ca6ed14903 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java @@ -403,11 +403,11 @@ private GeometryCollection buildIsochrone(AccessibilityMap edgeMap, List // This checks for dead end edges, but we need to include those in small areas to provide realistic // results if (goalEdge.edge != -2 || useHighDetail) { - addBufferedWayGeometry(points,bufferSize, iter, minSplitLength); + addBufferedEdgeGeometry(points, minSplitLength, iter, true, goalEdge, bufferSize); } } else { if ((minCost < isolineCost && maxCost >= isolineCost)) { - addCutEdgeGeometry(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize); + addCuttedEdgeGeometry(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize); } } } @@ -433,17 +433,6 @@ private void addContourCoordinates(List contourCoordinates, List points, double bufferSize, EdgeIteratorState iter, double minSplitLength) { - // always use mode=3, since other ones do not provide correct results - var pl = expandAndBufferPointList(iter.fetchWayGeometry(FetchMode.ALL), bufferSize, minSplitLength, MAX_EDGE_LENGTH_LIMIT); - if(pl.isEmpty()){ - return; - } - for (int i = 0; i < pl.size(); ++i) { - addPoint(points, pl.getLon(i), pl.getLat(i)); - } - } - private void handleFullyReachableCells(Set isochroneGeometries, Set fullyReachableCells) { //printing for debug // StringBuilder cellsPrintStatement = new StringBuilder(); @@ -537,39 +526,6 @@ private void splitEdgeToDoubles(double lat0, double lat1, double lon0, double lo } } - private PointList expandAndBufferPointList(PointList list, double bufferSize, double minlim, double maxlim) { - PointList extendedList = new PointList(); - for (int i = 0; i < list.size() - 1; i++) { - double lat0 = list.getLat(i); - double lon0 = list.getLon(i); - double lat1 = list.getLat(i + 1); - double lon1 = list.getLon(i + 1); - double dist = distance(lat0, lat1, lon0, lon1); - double dx = (lon0 - lon1); - double dy = (lat0 - lat1); - double normLength = Math.sqrt((dx * dx) + (dy * dy)); - - int n = (int) Math.ceil(dist / minlim); - double scale = bufferSize / normLength; - - double dx2 = -dy * scale; - double dy2 = dx * scale; - extendedList.add(lat0 + dy2, lon0 + dx2); - extendedList.add(lat0 - dy2, lon0 - dx2); - if (i == list.size() - 2) { - extendedList.add(lat1 + dy2, lon1 + dx2); - extendedList.add(lat1 - dy2, lon1 - dx2); - } - if (dist > minlim && dist < maxlim) { - for (int j = 1; j < n; j++) { - extendedList.add(lat0 + j * (lat1 - lat0) / n + dy2, lon0 + j * (lon1 - lon0) / n + dx2); - extendedList.add(lat0 + j * (lat1 - lat0) / n - dy2, lon0 + j * (lon1 - lon0) / n - dx2); - } - } - } - return extendedList; - } - private List> separateDisconnected(IntObjectMap map) { List> disconnectedCells = new ArrayList<>(); EdgeExplorer edgeExplorer = queryGraph.createEdgeExplorer(); From 51e6ad79c78cd1de003fe7eb33ea1bda4641bcaf Mon Sep 17 00:00:00 2001 From: aoles Date: Tue, 27 Feb 2024 15:04:29 +0100 Subject: [PATCH 07/13] refactor: simplify contour splitting Remove unnecessary conditional and adding of the first contour point twice. Use the more direct `getCoordinateN` accessor in favour of `getPointN` accessor. --- .../fast/FastIsochroneMapBuilder.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java index ca6ed14903..b41bb203b8 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java @@ -297,21 +297,17 @@ private List createCoordinateListFromGeometry(Geometry preprocessedGeome } LinearRing ring = ((Polygon) preprocessedGeometry.getGeometryN(j)).getExteriorRing(); - for (int i = 0; i < ring.getNumPoints(); i++) { + for (int i = 0; i < ring.getNumPoints() - 1; i++) { contourCoordinates.add(ring.getCoordinateN(i).y); contourCoordinates.add(ring.getCoordinateN(i).x); - if (i < ring.getNumPoints() - 1) { - splitEdgeToDoubles(ring.getPointN(i).getY(), - ring.getPointN(i + 1).getY(), - ring.getPointN(i).getX(), - ring.getPointN(i + 1).getX(), - contourCoordinates, - minSplitLength/2, - MAX_EDGE_LENGTH_LIMIT); - } + splitEdgeToDoubles(ring.getCoordinateN(i).y, + ring.getCoordinateN(i + 1).y, + ring.getCoordinateN(i).x, + ring.getCoordinateN(i + 1).x, + contourCoordinates, + minSplitLength/2, + MAX_EDGE_LENGTH_LIMIT); } - contourCoordinates.add(ring.getCoordinateN(0).y); - contourCoordinates.add(ring.getCoordinateN(0).x); } return contourCoordinates; } From 24b553081650c13a87c0f5601239f6d01c8057f2 Mon Sep 17 00:00:00 2001 From: aoles Date: Tue, 27 Feb 2024 15:31:41 +0100 Subject: [PATCH 08/13] refactor: use GH's method to calculate distance --- .../ConcaveBallsIsochroneMapBuilder.java | 1 - .../fast/FastIsochroneMapBuilder.java | 22 +------------------ 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index 64b0b7f3c6..007cf7c9d4 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -39,7 +39,6 @@ public class ConcaveBallsIsochroneMapBuilder extends AbstractIsochroneMapBuilder { private static final Logger LOGGER = Logger.getLogger(ConcaveBallsIsochroneMapBuilder.class.getName()); - private static final DistanceCalc dcFast = new DistancePlaneProjection(); private List prevIsoPoints = null; public IsochroneMap compute(IsochroneSearchParameters parameters) throws Exception { diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java index b41bb203b8..c2695a160c 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java @@ -73,26 +73,6 @@ public class FastIsochroneMapBuilder extends AbstractIsochroneMapBuilder { private static final double ACTIVE_CELL_APPROXIMATION_FACTOR = 0.99; private static final Logger LOGGER = Logger.getLogger(FastIsochroneMapBuilder.class.getName()); - /* - Calculates the distance between two coordinates in meters - */ - public static double distance(double lat1, double lat2, double lon1, - double lon2) { - final int R = 6371; // Radius of the earth - - double latDistance = Math.toRadians(lat2 - lat1); - double lonDistance = Math.toRadians(lon2 - lon1); - double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) - + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) - * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2); - double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - double distance = R * c * 1000; // convert to meters - - distance = Math.pow(distance, 2); - - return Math.sqrt(distance); - } - public void initialize(RouteSearchContext searchContext) { super.initialize(searchContext); defaultSmoothingDistance = 0.010;// Use a default length of ~1000m @@ -511,7 +491,7 @@ private String printCell(List coordinates, int cellId) { } private void splitEdgeToDoubles(double lat0, double lat1, double lon0, double lon1, List coordinates, double minlim, double maxlim) { - double dist = distance(lat0, lat1, lon0, lon1); + double dist = dcFast.calcDist(lat0, lon0, lat1, lon1); if (dist > minlim && dist < maxlim) { int n = (int) Math.ceil(dist / minlim); From a285f4d524d951dcd79b36cff90c8f9122c40107 Mon Sep 17 00:00:00 2001 From: aoles Date: Wed, 28 Feb 2024 00:36:22 +0100 Subject: [PATCH 09/13] refactor: simplify method for extracting contour coordinates --- .../ConcaveBallsIsochroneMapBuilder.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index 007cf7c9d4..733350e17d 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -33,6 +33,7 @@ import org.locationtech.jts.geom.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static org.locationtech.jts.algorithm.hull.ConcaveHull.concaveHullByLength; @@ -171,7 +172,7 @@ private void addIsochrone(IsochroneMap isochroneMap, Coordinate[] points, double return; } Polygon polyShell = (Polygon) shellGeometry; - copyConvexHullPoints(polyShell); + copyConcaveHullPoints(polyShell); if (LOGGER.isDebugEnabled()) { sw.stop(); @@ -301,15 +302,8 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p return coordinates; } - private void copyConvexHullPoints(Polygon poly) { + private void copyConcaveHullPoints(Polygon poly) { LineString ring = poly.getExteriorRing(); - if (prevIsoPoints == null) - prevIsoPoints = new ArrayList<>(ring.getNumPoints()); - else - prevIsoPoints.clear(); - for (int i = 0; i < ring.getNumPoints(); ++i) { - Point p = ring.getPointN(i); - prevIsoPoints.add(new Coordinate(p.getX(), p.getY())); - } + prevIsoPoints = new ArrayList<>(Arrays.asList(ring.getCoordinates())); } } From 83fe034fbdebb5aa6ca4654edb6d3c9c938d363b Mon Sep 17 00:00:00 2001 From: aoles Date: Wed, 28 Feb 2024 00:41:00 +0100 Subject: [PATCH 10/13] refactor: remove spurious code --- .../concaveballs/ConcaveBallsIsochroneMapBuilder.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index 733350e17d..1897ab08ab 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -177,19 +177,8 @@ private void addIsochrone(IsochroneMap isochroneMap, Coordinate[] points, double if (LOGGER.isDebugEnabled()) { sw.stop(); LOGGER.debug("Build shell concave hull " + sw.getSeconds()); - - sw = new StopWatch(); - sw.start(); } isochroneMap.addIsochrone(new Isochrone(polyShell, isoValue, meanRadius)); - - if (LOGGER.isDebugEnabled()) { - sw.stop(); - LOGGER.debug("Adding holes " + sw.getSeconds()); - - sw = new StopWatch(); - sw.start(); - } } private void markDeadEndEdges(AccessibilityMap edgeMap) { From 6ab00a0bd9491f719128fa1193d14a5cf73e0121 Mon Sep 17 00:00:00 2001 From: aoles Date: Wed, 28 Feb 2024 12:01:05 +0100 Subject: [PATCH 11/13] refactor: unify edge splitting pipeline across isochrone builders --- .../builders/AbstractIsochroneMapBuilder.java | 85 +++++++++++-------- .../ConcaveBallsIsochroneMapBuilder.java | 11 +-- .../fast/FastIsochroneMapBuilder.java | 83 +++++++----------- 3 files changed, 81 insertions(+), 98 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java index ab5012c124..a3f5668db3 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java @@ -4,7 +4,6 @@ import com.graphhopper.routing.util.FlagEncoder; import com.graphhopper.routing.util.HikeFlagEncoder; import com.graphhopper.util.*; -import com.graphhopper.util.shapes.GHPoint3D; import org.heigit.ors.routing.RouteSearchContext; import org.heigit.ors.routing.graphhopper.extensions.flagencoders.FootFlagEncoder; import org.heigit.ors.routing.graphhopper.extensions.flagencoders.ORSAbstractFlagEncoder; @@ -13,7 +12,10 @@ import org.heigit.ors.util.GeomUtility; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.Polygon; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public abstract class AbstractIsochroneMapBuilder implements IsochroneMapBuilder { @@ -88,19 +90,19 @@ protected void addBufferPoints(List points, double lon0, double lat0 } } - protected void addPointsFromEdge(List points, double isolineCost, float maxCost, float minCost, double bufferSize, double edgeDist, PointList pl) { + protected void addPointsFromEdge(List points, double isolineCost, float maxCost, float minCost, double bufferSize, double edgeDist, List pl) { double edgeCost = maxCost - minCost; double costPerMeter = edgeCost / edgeDist; double distPolyline = 0.0; - double lat0 = pl.getLat(0); - double lon0 = pl.getLon(0); + double lat0 = pl.get(0).y; + double lon0 = pl.get(0).x; double lat1; double lon1; for (int i = 1; i < pl.size(); ++i) { - lat1 = pl.getLat(i); - lon1 = pl.getLon(i); + lat1 = pl.get(i).y; + lon1 = pl.get(i).x; distPolyline += dcFast.calcDist(lat0, lon0, lat1, lon1); @@ -125,63 +127,66 @@ protected void addPointsFromEdge(List points, double isolineCost, fl /** * Splits a line between two points of the distance is longer than a limit * - * @param point0 point0 of line - * @param point1 point1 of line + * @param lat0 latitude of first point + * @param lon0 longitude of first point + * @param lat1 latitude of second point + * @param lon1 longitude of second point * @param minlim limit above which the edge will be split (in meters) * @param maxlim limit above which the edge will NOT be split anymore (in meters) */ - private void splitEdge(GHPoint3D point0, GHPoint3D point1, PointList pointList, double minlim, double maxlim) { - //No need to consider elevation - double lat0 = point0.getLat(); - double lon0 = point0.getLon(); - double lat1 = point1.getLat(); - double lon1 = point1.getLon(); + protected void splitLineSegment(double lat0, double lon0, double lat1, double lon1, List points, double minlim, double maxlim) { double dist = dcFast.calcDist(lat0, lon0, lat1, lon1); - boolean is3D = pointList.is3D(); if (dist > minlim && dist < maxlim) { int n = (int) Math.ceil(dist / minlim); for (int i = 1; i < n; i++) { - if (is3D) - pointList.add(lat0 + i * (lat1 - lat0) / n, lon0 + i * (lon1 - lon0) / n, 0); - else - pointList.add(lat0 + i * (lat1 - lat0) / n, lon0 + i * (lon1 - lon0) / n); + addPoint(points, lon0 + i * (lon1 - lon0) / n, lat0 + i * (lat1 - lat0) / n); } } } - protected PointList edgeToPoints(EdgeIteratorState iter, double minSplitLength) { + protected List edgeToPoints(EdgeIteratorState iter, double minSplitLength) { // always use mode=3, since other ones do not provide correct results PointList pl = iter.fetchWayGeometry(FetchMode.ALL); - double edgeDist = iter.getDistance(); - if (edgeDist > minSplitLength) { - PointList expandedPoints = new PointList(pl.size(), pl.is3D()); - for (int i = 0; i < pl.size() - 1; i++) - splitEdge(pl.get(i), pl.get(i + 1), expandedPoints, minSplitLength, MAX_SPLIT_LENGTH); - pl.add(expandedPoints); + List points = new ArrayList<>(2 * pl.size()); + + if (!pl.isEmpty()) { + double lat0 = pl.getLat(0); + double lon0 = pl.getLon(0); + double lat1; + double lon1; + for (int i = 1; i < pl.size(); i++) { + lat1 = pl.getLat(i); + lon1 = pl.getLon(i); + addPoint(points, lon0, lat0); + splitLineSegment(lat0, lon0, lat1, lon1, points, minSplitLength, MAX_SPLIT_LENGTH); + lon0 = lon1; + lat0 = lat1; + } + addPoint(points, lon0, lat0); } - return pl; + return points; } protected void addBufferedEdgeGeometry(List points, double minSplitLength, EdgeIteratorState iter, boolean detailedShape, SPTEntry goalEdge, double bufferSize) { - PointList pl = edgeToPoints(iter, minSplitLength); + List pl = edgeToPoints(iter, minSplitLength); if (pl.isEmpty()) { return; } int size = pl.size(); - double lat0 = pl.getLat(0); - double lon0 = pl.getLon(0); + double lat0 = pl.get(0).y; + double lon0 = pl.get(0).x; double lat1; double lon1; if (detailedShape) { for (int i = 1; i < size; ++i) { - lat1 = pl.getLat(i); - lon1 = pl.getLon(i); + lat1 = pl.get(i).y; + lon1 = pl.get(i).x; addBufferPoints(points, lon0, lat0, lon1, lat1, goalEdge.edge < 0 && i == size - 1, bufferSize); @@ -189,14 +194,12 @@ protected void addBufferedEdgeGeometry(List points, double minSplitL lat0 = lat1; } } else { - for (int i = 0; i < size; ++i) { - addPoint(points, pl.getLon(i), pl.getLat(i)); - } + points.addAll(pl); } } - protected void addCuttedEdgeGeometry(List points, double isolineCost, double minSplitLength, EdgeIteratorState iter, float maxCost, float minCost, double bufferSize) { - PointList pl = edgeToPoints(iter, minSplitLength); + protected void addBorderEdgeGeometry(List points, double isolineCost, double minSplitLength, EdgeIteratorState iter, float maxCost, float minCost, double bufferSize) { + List pl = edgeToPoints(iter, minSplitLength); if (pl.isEmpty()) { return; } @@ -233,4 +236,12 @@ protected double determineMeanSpeed(double maxSpeed) { return meanSpeed; } + + protected List createCoordinateListFromPolygon(Polygon poly) { + List ringCoordinates = new ArrayList<>(Arrays.asList(poly.getExteriorRing().getCoordinates())); + // remove the last point as for a closed ring it equals the first one + ringCoordinates.remove(ringCoordinates.size() - 1); + + return ringCoordinates; + } } diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index 1897ab08ab..530d2fd663 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -33,7 +33,6 @@ import org.locationtech.jts.geom.*; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import static org.locationtech.jts.algorithm.hull.ConcaveHull.concaveHullByLength; @@ -172,7 +171,7 @@ private void addIsochrone(IsochroneMap isochroneMap, Coordinate[] points, double return; } Polygon polyShell = (Polygon) shellGeometry; - copyConcaveHullPoints(polyShell); + prevIsoPoints = createCoordinateListFromPolygon(polyShell); if (LOGGER.isDebugEnabled()) { sw.stop(); @@ -249,7 +248,6 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p if (minCost < prevCost && isochronesDifference > 1000) continue; - EdgeIteratorState iter = graph.getEdgeIteratorState(edgeId, nodeId); // edges that are fully inside the isochrone @@ -273,7 +271,7 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p if ((minCost < isolineCost && maxCost >= isolineCost)) { if (LOGGER.isDebugEnabled()) sw.start(); - addCuttedEdgeGeometry(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize); + addBorderEdgeGeometry(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize); if (LOGGER.isDebugEnabled()) sw.stop(); } @@ -290,9 +288,4 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p } return coordinates; } - - private void copyConcaveHullPoints(Polygon poly) { - LineString ring = poly.getExteriorRing(); - prevIsoPoints = new ArrayList<>(Arrays.asList(ring.getCoordinates())); - } } diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java index c2695a160c..f13a96dbc0 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java @@ -73,6 +73,7 @@ public class FastIsochroneMapBuilder extends AbstractIsochroneMapBuilder { private static final double ACTIVE_CELL_APPROXIMATION_FACTOR = 0.99; private static final Logger LOGGER = Logger.getLogger(FastIsochroneMapBuilder.class.getName()); + @Override public void initialize(RouteSearchContext searchContext) { super.initialize(searchContext); defaultSmoothingDistance = 0.010;// Use a default length of ~1000m @@ -214,8 +215,8 @@ public IsochroneMap compute(IsochroneSearchParameters parameters) throws Excepti StopWatch finalConcaveHullStopWatch = new StopWatch(); if (DebugUtility.isDebug()) finalConcaveHullStopWatch.start(); - List contourCoordinates = createCoordinateListFromGeometry(preprocessedGeometry, smoothingDistanceMeter); - GeometryCollection points = buildIsochrone(new AccessibilityMap(new GHIntObjectHashMap<>(0), snappedPosition), contourCoordinates, isoPoints, isoValue, smoothingDistanceMeter); + isoPoints.addAll(createCoordinateListFromGeometry(preprocessedGeometry, smoothingDistanceMeter)); + GeometryCollection points = buildIsochrone(new AccessibilityMap(new GHIntObjectHashMap<>(0), snappedPosition), isoPoints, isoValue, smoothingDistanceMeter); addIsochrone(isochroneMap, points, isoValue, meanRadius, smoothingDistance); if (DebugUtility.isDebug()) { LOGGER.debug("Build final concave hull from " + points.getNumGeometries() + " points: " + finalConcaveHullStopWatch.stop().getSeconds()); @@ -254,7 +255,7 @@ private void buildActiveCellsConcaveHulls(FastIsochroneAlgorithm fastIsochroneAl if (largestSubCellProcessed && splitMap.size() < getMinCellNodesNumber()) continue; largestSubCellProcessed = true; - GeometryCollection points = buildIsochrone(new AccessibilityMap(splitMap, snappedPosition), new ArrayList<>(), new ArrayList<>(), isoValue, minSplitLength); + GeometryCollection points = buildIsochrone(new AccessibilityMap(splitMap, snappedPosition), new ArrayList<>(), isoValue, minSplitLength); createPolyFromPoints(isochroneGeometries, points, smoothingDistance, minSplitLength); } swActiveCellBuild.stop(); @@ -265,31 +266,34 @@ private void buildActiveCellsConcaveHulls(FastIsochroneAlgorithm fastIsochroneAl } } - private List createCoordinateListFromGeometry(Geometry preprocessedGeometry, double minSplitLength) { - List contourCoordinates = new ArrayList<>(); + private List createCoordinateListFromGeometry(Geometry preprocessedGeometry, double minSplitLength) { + List contourCoords = new ArrayList<>(); + for (int j = 0; j < preprocessedGeometry.getNumGeometries(); j++) { - if (!(preprocessedGeometry.getGeometryN(j) instanceof Polygon)) { - for (Coordinate coordinate : preprocessedGeometry.getGeometryN(j).getCoordinates()) { - contourCoordinates.add(coordinate.y); - contourCoordinates.add(coordinate.x); + Geometry geometry = preprocessedGeometry.getGeometryN(j); + + if (geometry instanceof Polygon poly) { + List ringCoords = createCoordinateListFromPolygon(poly); + contourCoords.addAll(ringCoords); + + double lat0 = ringCoords.get(ringCoords.size() - 1).y; + double lon0 = ringCoords.get(ringCoords.size() - 1).x; + double lat1; + double lon1; + for (int i = 0; i < ringCoords.size(); i++) { + lat1 = ringCoords.get(i).y; + lon1 = ringCoords.get(i).x; + splitLineSegment(lat0, lon0, lat1, lon1, contourCoords, minSplitLength / 2, MAX_EDGE_LENGTH_LIMIT); + lon0 = lon1; + lat0 = lat1; } - continue; } - - LinearRing ring = ((Polygon) preprocessedGeometry.getGeometryN(j)).getExteriorRing(); - for (int i = 0; i < ring.getNumPoints() - 1; i++) { - contourCoordinates.add(ring.getCoordinateN(i).y); - contourCoordinates.add(ring.getCoordinateN(i).x); - splitEdgeToDoubles(ring.getCoordinateN(i).y, - ring.getCoordinateN(i + 1).y, - ring.getCoordinateN(i).x, - ring.getCoordinateN(i + 1).x, - contourCoordinates, - minSplitLength/2, - MAX_EDGE_LENGTH_LIMIT); + else { + contourCoords.addAll(Arrays.asList(geometry.getCoordinates())); } } - return contourCoordinates; + + return contourCoords; } private Geometry combineGeometries(Set isochroneGeometries) { @@ -311,10 +315,9 @@ private void addPreviousIsochronePolygon(Set isochroneGeometries) { private void createPolyFromPoints(Set isochroneGeometries, GeometryCollection points, double smoothingDistance, double minSplitLength) { if (points.isEmpty()) return; - LinearRing ring; - Geometry concaveHull; + try { - concaveHull = concaveHullByLength(points, smoothingDistance); + Geometry concaveHull = concaveHullByLength(points, smoothingDistance); if (concaveHull instanceof Polygon && concaveHull.isValid() && !concaveHull.isEmpty()) isochroneGeometries.add(concaveHull); } catch (Exception e) { @@ -342,7 +345,7 @@ private void addIsochrone(IsochroneMap isochroneMap, GeometryCollection points, isochroneMap.addIsochrone(new Isochrone(poly, isoValue, meanRadius)); } - private GeometryCollection buildIsochrone(AccessibilityMap edgeMap, List contourCoordinates, List points, + private GeometryCollection buildIsochrone(AccessibilityMap edgeMap, List points, double isolineCost, double minSplitLength) { IntObjectMap map = edgeMap.getMap(); @@ -383,11 +386,10 @@ private GeometryCollection buildIsochrone(AccessibilityMap edgeMap, List } } else { if ((minCost < isolineCost && maxCost >= isolineCost)) { - addCuttedEdgeGeometry(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize); + addBorderEdgeGeometry(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize); } } } - addContourCoordinates(contourCoordinates, points); Geometry[] geometries = new Geometry[points.size()]; for (int i = 0; i < points.size(); ++i) { @@ -398,17 +400,6 @@ private GeometryCollection buildIsochrone(AccessibilityMap edgeMap, List return new GeometryCollection(geometries, geometryFactory); } - private void addContourCoordinates(List contourCoordinates, List points) { - int j = 0; - while (j < contourCoordinates.size()) { - double latitude = contourCoordinates.get(j); - j++; - double longitude = contourCoordinates.get(j); - j++; - addPoint(points, longitude, latitude); - } - } - private void handleFullyReachableCells(Set isochroneGeometries, Set fullyReachableCells) { //printing for debug // StringBuilder cellsPrintStatement = new StringBuilder(); @@ -490,18 +481,6 @@ private String printCell(List coordinates, int cellId) { return statement.toString(); } - private void splitEdgeToDoubles(double lat0, double lat1, double lon0, double lon1, List coordinates, double minlim, double maxlim) { - double dist = dcFast.calcDist(lat0, lon0, lat1, lon1); - - if (dist > minlim && dist < maxlim) { - int n = (int) Math.ceil(dist / minlim); - for (int i = 1; i < n; i++) { - coordinates.add(lat0 + i * (lat1 - lat0) / n); - coordinates.add(lon0 + i * (lon1 - lon0) / n); - } - } - } - private List> separateDisconnected(IntObjectMap map) { List> disconnectedCells = new ArrayList<>(); EdgeExplorer edgeExplorer = queryGraph.createEdgeExplorer(); From e8423c08d2fd9471d43eea5000438d859f011e01 Mon Sep 17 00:00:00 2001 From: aoles Date: Wed, 28 Feb 2024 12:33:31 +0100 Subject: [PATCH 12/13] refactor: lazy-evaluate coordinate list of previous isochrone polygon --- .../builders/AbstractIsochroneMapBuilder.java | 1 + .../ConcaveBallsIsochroneMapBuilder.java | 12 ++++++------ .../builders/fast/FastIsochroneMapBuilder.java | 1 - 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java index a3f5668db3..fd0d29f85b 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java @@ -24,6 +24,7 @@ public abstract class AbstractIsochroneMapBuilder implements IsochroneMapBuilder protected GeometryFactory geometryFactory; protected RouteSearchContext searchContext; protected double defaultSmoothingDistance = 0.012;// Use a default length of ~1333m + protected Polygon previousIsochronePolygon = null; public void initialize(RouteSearchContext searchContext) { this.searchContext = searchContext; diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index 530d2fd663..b781fc3f89 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -39,7 +39,6 @@ public class ConcaveBallsIsochroneMapBuilder extends AbstractIsochroneMapBuilder { private static final Logger LOGGER = Logger.getLogger(ConcaveBallsIsochroneMapBuilder.class.getName()); - private List prevIsoPoints = null; public IsochroneMap compute(IsochroneSearchParameters parameters) throws Exception { StopWatch swTotal = null; @@ -170,14 +169,15 @@ private void addIsochrone(IsochroneMap isochroneMap, Coordinate[] points, double if (geomColl.isEmpty()) return; } - Polygon polyShell = (Polygon) shellGeometry; - prevIsoPoints = createCoordinateListFromPolygon(polyShell); + + Polygon poly = (Polygon) shellGeometry; + previousIsochronePolygon = poly; if (LOGGER.isDebugEnabled()) { sw.stop(); LOGGER.debug("Build shell concave hull " + sw.getSeconds()); } - isochroneMap.addIsochrone(new Isochrone(polyShell, isoValue, meanRadius)); + isochroneMap.addIsochrone(new Isochrone(poly, isoValue, meanRadius)); } private void markDeadEndEdges(AccessibilityMap edgeMap) { @@ -210,8 +210,8 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p points.clear(); - if (prevIsoPoints != null) - points.addAll(prevIsoPoints); + if (previousIsochronePolygon != null) + points.addAll(createCoordinateListFromPolygon(previousIsochronePolygon)); GraphHopperStorage graph = searchContext.getGraphHopper().getGraphHopperStorage(); NodeAccess nodeAccess = graph.getNodeAccess(); diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java index f13a96dbc0..4b97a5e990 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java @@ -65,7 +65,6 @@ * @author Hendrik Leuschner */ public class FastIsochroneMapBuilder extends AbstractIsochroneMapBuilder { - private Polygon previousIsochronePolygon = null; private CellStorage cellStorage; private IsochroneNodeStorage isochroneNodeStorage; private QueryGraph queryGraph; From 565cbc2ce2e38381b652cfe19662c8a28d4483df Mon Sep 17 00:00:00 2001 From: aoles Date: Wed, 28 Feb 2024 13:24:41 +0100 Subject: [PATCH 13/13] refactor: unify `addIsochrone` method across isochrone builders --- .../builders/AbstractIsochroneMapBuilder.java | 38 +++++++++++-- .../ConcaveBallsIsochroneMapBuilder.java | 54 +++++-------------- .../fast/FastIsochroneMapBuilder.java | 26 ++------- 3 files changed, 52 insertions(+), 66 deletions(-) diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java index fd0d29f85b..2e97de6f0e 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/AbstractIsochroneMapBuilder.java @@ -4,20 +4,23 @@ import com.graphhopper.routing.util.FlagEncoder; import com.graphhopper.routing.util.HikeFlagEncoder; import com.graphhopper.util.*; +import org.apache.log4j.Logger; +import org.heigit.ors.isochrones.Isochrone; +import org.heigit.ors.isochrones.IsochroneMap; import org.heigit.ors.routing.RouteSearchContext; import org.heigit.ors.routing.graphhopper.extensions.flagencoders.FootFlagEncoder; import org.heigit.ors.routing.graphhopper.extensions.flagencoders.ORSAbstractFlagEncoder; import org.heigit.ors.routing.graphhopper.extensions.flagencoders.WheelchairFlagEncoder; import org.heigit.ors.routing.graphhopper.extensions.flagencoders.bike.CommonBikeFlagEncoder; import org.heigit.ors.util.GeomUtility; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.GeometryFactory; -import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.geom.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static org.locationtech.jts.algorithm.hull.ConcaveHull.concaveHullByLength; + public abstract class AbstractIsochroneMapBuilder implements IsochroneMapBuilder { private static final double MAX_SPLIT_LENGTH = 20000.0; protected static final DistanceCalc dcFast = new DistancePlaneProjection(); @@ -26,6 +29,8 @@ public abstract class AbstractIsochroneMapBuilder implements IsochroneMapBuilder protected double defaultSmoothingDistance = 0.012;// Use a default length of ~1333m protected Polygon previousIsochronePolygon = null; + public abstract Logger getLogger(); + public void initialize(RouteSearchContext searchContext) { this.searchContext = searchContext; this.geometryFactory = new GeometryFactory(); @@ -245,4 +250,31 @@ protected List createCoordinateListFromPolygon(Polygon poly) { return ringCoordinates; } + + protected void addIsochrone(IsochroneMap isochroneMap, GeometryCollection points, double isoValue, double meanRadius, double smoothingDistance) { + if (points.isEmpty()) + return; + + StopWatch sw = new StopWatch(); + if (getLogger().isDebugEnabled()) { + sw = new StopWatch(); + sw.start(); + } + + Geometry shellGeometry = concaveHullByLength(points, smoothingDistance); + if (shellGeometry instanceof GeometryCollection geomColl) { + if (geomColl.isEmpty()) + return; + } + + Polygon poly = (Polygon) shellGeometry; + previousIsochronePolygon = poly; + + if (getLogger().isDebugEnabled()) { + sw.stop(); + getLogger().debug("Build shell concave hull " + sw.getSeconds()); + } + + isochroneMap.addIsochrone(new Isochrone(poly, isoValue, meanRadius)); + } } diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java index b781fc3f89..973224f718 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/concaveballs/ConcaveBallsIsochroneMapBuilder.java @@ -24,7 +24,6 @@ import org.apache.log4j.Logger; import org.heigit.ors.common.TravelRangeType; import org.heigit.ors.isochrones.GraphEdgeMapFinder; -import org.heigit.ors.isochrones.Isochrone; import org.heigit.ors.isochrones.IsochroneMap; import org.heigit.ors.isochrones.IsochroneSearchParameters; import org.heigit.ors.isochrones.builders.AbstractIsochroneMapBuilder; @@ -35,11 +34,14 @@ import java.util.ArrayList; import java.util.List; -import static org.locationtech.jts.algorithm.hull.ConcaveHull.concaveHullByLength; - public class ConcaveBallsIsochroneMapBuilder extends AbstractIsochroneMapBuilder { private static final Logger LOGGER = Logger.getLogger(ConcaveBallsIsochroneMapBuilder.class.getName()); + @Override + public Logger getLogger() { + return LOGGER; + } + public IsochroneMap compute(IsochroneSearchParameters parameters) throws Exception { StopWatch swTotal = null; StopWatch sw = null; @@ -124,12 +126,11 @@ public IsochroneMap compute(IsochroneSearchParameters parameters) throws Excepti var smoothingDistance = convertSmoothingFactorToDistance(smoothingFactor, maxRadius); var smoothingDistanceMeter = GeomUtility.degreesToMetres(smoothingDistance); - Coordinate[] points = buildIsochrone(edgeMap, isoPoints, isoValue, prevCost, isochronesDifference, 0.85, smoothingDistanceMeter); + GeometryCollection points = buildIsochrone(edgeMap, isoPoints, isoValue, prevCost, isochronesDifference, 0.85, smoothingDistanceMeter); if (LOGGER.isDebugEnabled()) { sw.stop(); - LOGGER.debug(i + " Find points: " + sw.getSeconds() + " " + points.length); - + LOGGER.debug(i + " Find points: " + sw.getSeconds() + " " + points.getNumPoints()); sw = new StopWatch(); sw.start(); } @@ -148,38 +149,6 @@ public IsochroneMap compute(IsochroneSearchParameters parameters) throws Excepti return isochroneMap; } - private void addIsochrone(IsochroneMap isochroneMap, Coordinate[] points, double isoValue, double meanRadius, double smoothingDistance) { - Geometry[] geometries = new Geometry[points.length]; - for (int i = 0; i < points.length; ++i) { - Coordinate c = points[i]; - geometries[i] = geometryFactory.createPoint(c); - } - GeometryCollection geometry = new GeometryCollection(geometries, geometryFactory); - - if (points.length == 0) - return; - StopWatch sw = new StopWatch(); - if (LOGGER.isDebugEnabled()) { - sw = new StopWatch(); - sw.start(); - } - - Geometry shellGeometry = concaveHullByLength(geometry, smoothingDistance); - if (shellGeometry instanceof GeometryCollection geomColl) { - if (geomColl.isEmpty()) - return; - } - - Polygon poly = (Polygon) shellGeometry; - previousIsochronePolygon = poly; - - if (LOGGER.isDebugEnabled()) { - sw.stop(); - LOGGER.debug("Build shell concave hull " + sw.getSeconds()); - } - isochroneMap.addIsochrone(new Isochrone(poly, isoValue, meanRadius)); - } - private void markDeadEndEdges(AccessibilityMap edgeMap) { IntObjectMap map = edgeMap.getMap(); IntObjectMap result = new GHIntObjectHashMap<>(map.size() / 20); @@ -202,7 +171,7 @@ private void markDeadEndEdges(AccessibilityMap edgeMap) { } } - private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List points, + private GeometryCollection buildIsochrone(AccessibilityMap edgeMap, List points, double isolineCost, double prevCost, double isochronesDifference, double detailedGeomFactor, double minSplitLength) { @@ -280,12 +249,13 @@ private Coordinate[] buildIsochrone(AccessibilityMap edgeMap, List p if (LOGGER.isDebugEnabled()) LOGGER.debug("Expanding edges " + sw.getSeconds()); - Coordinate[] coordinates = new Coordinate[points.size()]; + Geometry[] geometries = new Geometry[points.size()]; for (int i = 0; i < points.size(); ++i) { Coordinate c = points.get(i); - coordinates[i] = c; + geometries[i] = geometryFactory.createPoint(c); } - return coordinates; + + return new GeometryCollection(geometries, geometryFactory); } } diff --git a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java index 4b97a5e990..6db0ac4a0e 100644 --- a/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java +++ b/ors-engine/src/main/java/org/heigit/ors/isochrones/builders/fast/FastIsochroneMapBuilder.java @@ -38,7 +38,6 @@ import org.heigit.ors.fastisochrones.FastIsochroneAlgorithm; import org.heigit.ors.fastisochrones.partitioning.storage.CellStorage; import org.heigit.ors.fastisochrones.partitioning.storage.IsochroneNodeStorage; -import org.heigit.ors.isochrones.Isochrone; import org.heigit.ors.isochrones.IsochroneMap; import org.heigit.ors.isochrones.IsochroneSearchParameters; import org.heigit.ors.isochrones.IsochronesErrorCodes; @@ -72,6 +71,11 @@ public class FastIsochroneMapBuilder extends AbstractIsochroneMapBuilder { private static final double ACTIVE_CELL_APPROXIMATION_FACTOR = 0.99; private static final Logger LOGGER = Logger.getLogger(FastIsochroneMapBuilder.class.getName()); + @Override + public Logger getLogger() { + return LOGGER; + } + @Override public void initialize(RouteSearchContext searchContext) { super.initialize(searchContext); @@ -324,26 +328,6 @@ private void createPolyFromPoints(Set isochroneGeometries, GeometryCol } } - private void addIsochrone(IsochroneMap isochroneMap, GeometryCollection points, double isoValue, double meanRadius, double smoothingDistance) { - if (points.isEmpty()) - return; - Polygon poly; - try { - Geometry geom = concaveHullByLength(points, smoothingDistance); - - if (geom instanceof GeometryCollection geomColl) { - if (geomColl.isEmpty()) - return; - } - - poly = (Polygon) geom; - } catch (Exception e) { - return; - } - previousIsochronePolygon = poly; - isochroneMap.addIsochrone(new Isochrone(poly, isoValue, meanRadius)); - } - private GeometryCollection buildIsochrone(AccessibilityMap edgeMap, List points, double isolineCost, double minSplitLength) { IntObjectMap map = edgeMap.getMap();