ConcaveBallsIsochroneMapBuilder.java

  1. /*  This file is part of Openrouteservice.
  2.  *
  3.  *  Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the
  4.  *  GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1
  5.  *  of the License, or (at your option) any later version.

  6.  *  This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
  7.  *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  8.  *  See the GNU Lesser General Public License for more details.

  9.  *  You should have received a copy of the GNU Lesser General Public License along with this library;
  10.  *  if not, see <https://www.gnu.org/licenses/>.
  11.  */
  12. package org.heigit.ors.isochrones.builders.concaveballs;

  13. import com.carrotsearch.hppc.IntObjectMap;
  14. import com.carrotsearch.hppc.cursors.IntObjectCursor;
  15. import com.graphhopper.coll.GHIntObjectHashMap;
  16. import com.graphhopper.routing.SPTEntry;
  17. import com.graphhopper.storage.GraphHopperStorage;
  18. import com.graphhopper.storage.NodeAccess;
  19. import com.graphhopper.util.*;
  20. import com.graphhopper.util.shapes.GHPoint3D;
  21. import org.apache.log4j.Logger;
  22. import org.heigit.ors.common.TravelRangeType;
  23. import org.heigit.ors.isochrones.GraphEdgeMapFinder;
  24. import org.heigit.ors.isochrones.IsochroneMap;
  25. import org.heigit.ors.isochrones.IsochroneSearchParameters;
  26. import org.heigit.ors.isochrones.builders.AbstractIsochroneMapBuilder;
  27. import org.heigit.ors.routing.graphhopper.extensions.AccessibilityMap;
  28. import org.locationtech.jts.geom.*;

  29. import java.util.ArrayList;
  30. import java.util.List;

  31. public class ConcaveBallsIsochroneMapBuilder extends AbstractIsochroneMapBuilder {
  32.     private static final Logger LOGGER = Logger.getLogger(ConcaveBallsIsochroneMapBuilder.class.getName());

  33.     @Override
  34.     public Logger getLogger() {
  35.         return LOGGER;
  36.     }

  37.     public IsochroneMap compute(IsochroneSearchParameters parameters) throws Exception {
  38.         StopWatch swTotal = null;
  39.         StopWatch sw = null;
  40.         if (LOGGER.isDebugEnabled()) {
  41.             swTotal = new StopWatch();
  42.             swTotal.start();
  43.             sw = new StopWatch();
  44.             sw.start();
  45.         }

  46.         GraphHopperStorage graph = searchContext.getGraphHopper().getGraphHopperStorage();
  47.         String graphdate = graph.getProperties().get("datareader.import.date");

  48.         double maxSpeed = determineMaxSpeed();
  49.         double meanSpeed = determineMeanSpeed(maxSpeed);

  50.         AccessibilityMap edgeMap = GraphEdgeMapFinder.findEdgeMap(searchContext, parameters);

  51.         GHPoint3D point = edgeMap.getSnappedPosition();

  52.         Coordinate loc = (point == null) ? parameters.getLocation() : new Coordinate(point.lon, point.lat);

  53.         IsochroneMap isochroneMap = new IsochroneMap(parameters.getTravellerId(), loc);

  54.         isochroneMap.setGraphDate(graphdate);

  55.         if (LOGGER.isDebugEnabled()) {
  56.             sw.stop();

  57.             LOGGER.debug("Find edges: " + sw.getSeconds());
  58.         }

  59.         if (edgeMap.isEmpty())
  60.             return isochroneMap;

  61.         List<Coordinate> isoPoints = new ArrayList<>((int) (1.2 * edgeMap.getMap().size()));

  62.         if (LOGGER.isDebugEnabled()) {
  63.             sw = new StopWatch();
  64.             sw.start();
  65.         }

  66.         markDeadEndEdges(edgeMap);

  67.         if (LOGGER.isDebugEnabled()) {
  68.             sw.stop();
  69.             LOGGER.debug("Mark dead ends: " + sw.getSeconds());
  70.         }

  71.         int nRanges = parameters.getRanges().length;

  72.         double metersPerSecond = maxSpeed / 3.6;
  73.         // only needed for reachfactor property
  74.         double meanMetersPerSecond = meanSpeed / 3.6;

  75.         double prevCost = 0;
  76.         for (int i = 0; i < nRanges; i++) {
  77.             double isoValue = parameters.getRanges()[i];
  78.             double isochronesDifference = parameters.getRanges()[i];
  79.             if (i > 0)
  80.                 isochronesDifference = isochronesDifference - parameters.getRanges()[i - 1];

  81.             TravelRangeType isochroneType = parameters.getRangeType();

  82.             if (LOGGER.isDebugEnabled()) {
  83.                 sw = new StopWatch();
  84.                 sw.start();
  85.             }

  86.             double maxRadius;
  87.             double meanRadius;
  88.             if (isochroneType == TravelRangeType.DISTANCE) {
  89.                 maxRadius = isoValue;
  90.                 meanRadius = isoValue;
  91.             } else {
  92.                 maxRadius = metersPerSecond * isoValue;
  93.                 meanRadius = meanMetersPerSecond * isoValue;
  94.                 isochronesDifference = metersPerSecond * isochronesDifference;
  95.             }

  96.             double smoothingDistance = convertSmoothingFactorToDistance(parameters.getSmoothingFactor(), maxRadius);

  97.             GeometryCollection points = buildIsochrone(edgeMap, isoPoints, isoValue, prevCost, isochronesDifference, 0.85, smoothingDistance);

  98.             if (LOGGER.isDebugEnabled()) {
  99.                 sw.stop();
  100.                 LOGGER.debug(i + " Find points: " + sw.getSeconds() + " " + points.getNumPoints());
  101.                 sw = new StopWatch();
  102.                 sw.start();
  103.             }

  104.             addIsochrone(isochroneMap, points, isoValue, meanRadius, smoothingDistance);

  105.             if (LOGGER.isDebugEnabled())
  106.                 LOGGER.debug("Build concave hull total: " + sw.stop().getSeconds());

  107.             prevCost = isoValue;
  108.         }

  109.         if (LOGGER.isDebugEnabled())
  110.             LOGGER.debug("Total time: " + swTotal.stop().getSeconds());

  111.         return isochroneMap;
  112.     }

  113.     private void markDeadEndEdges(AccessibilityMap edgeMap) {
  114.         IntObjectMap<SPTEntry> map = edgeMap.getMap();
  115.         IntObjectMap<Integer> result = new GHIntObjectHashMap<>(map.size() / 20);

  116.         for (IntObjectCursor<SPTEntry> entry : map) {
  117.             SPTEntry edge = entry.value;
  118.             if (edge.originalEdge == -1)
  119.                 continue;

  120.             result.put(edge.parent.originalEdge, 1);
  121.         }

  122.         for (IntObjectCursor<SPTEntry> entry : map) {
  123.             SPTEntry edge = entry.value;
  124.             if (edge.originalEdge == -1)
  125.                 continue;

  126.             if (!result.containsKey(edge.originalEdge))
  127.                 edge.edge = -2;
  128.         }
  129.     }

  130.     private GeometryCollection buildIsochrone(AccessibilityMap edgeMap, List<Coordinate> points,
  131.                                         double isolineCost, double prevCost, double isochronesDifference,
  132.                                         double detailedGeomFactor,
  133.                                         double minSplitLength) {
  134.         IntObjectMap<SPTEntry> map = edgeMap.getMap();

  135.         points.clear();

  136.         if (previousIsochronePolygon != null)
  137.             points.addAll(createCoordinateListFromPolygon(previousIsochronePolygon));

  138.         GraphHopperStorage graph = searchContext.getGraphHopper().getGraphHopperStorage();
  139.         NodeAccess nodeAccess = graph.getNodeAccess();
  140.         int maxNodeId = graph.getNodes() - 1;
  141.         int maxEdgeId = graph.getEdges() - 1;

  142.         double bufferSize = 0.0018;
  143.         double detailedZone = isolineCost * detailedGeomFactor;

  144.         boolean useHighDetail = map.size() < 1000 || isochronesDifference < 1000;

  145.         if (useHighDetail) {
  146.             bufferSize = 0.00018;
  147.         }

  148.         StopWatch sw = new StopWatch();

  149.         for (IntObjectCursor<SPTEntry> entry : map) {
  150.             SPTEntry goalEdge = entry.value;
  151.             int edgeId = goalEdge.originalEdge;
  152.             int nodeId = goalEdge.adjNode;

  153.             if (edgeId == -1 || nodeId == -1 || nodeId > maxNodeId || edgeId > maxEdgeId)
  154.                 continue;

  155.             float maxCost = (float) goalEdge.weight;
  156.             float minCost = (float) goalEdge.parent.weight;

  157.             // ignore all edges that have been considered in the previous step. We do not want to do this for small
  158.             // isochrones as the edge may have more than one range on it in that case
  159.             if (minCost < prevCost && isochronesDifference > 1000)
  160.                 continue;

  161.             EdgeIteratorState iter = graph.getEdgeIteratorState(edgeId, nodeId);

  162.             // edges that are fully inside the isochrone
  163.             if (isolineCost >= maxCost) {
  164.                 // This checks for dead end edges, but we need to include those in small areas to provide realistic
  165.                 // results
  166.                 if (goalEdge.edge != -2 || useHighDetail) {
  167.                     double edgeDist = iter.getDistance();
  168.                     boolean detailedShape = (edgeDist > 200);
  169.                     if (maxCost >= detailedZone || detailedShape) {
  170.                         if (LOGGER.isDebugEnabled())
  171.                             sw.start();
  172.                         addBufferedEdgeGeometry(points, minSplitLength, iter, detailedShape, goalEdge, bufferSize);
  173.                         if (LOGGER.isDebugEnabled())
  174.                             sw.stop();
  175.                     } else {
  176.                         addPoint(points, nodeAccess.getLon(nodeId), nodeAccess.getLat(nodeId));
  177.                     }
  178.                 }
  179.             } else {
  180.                 if (minCost < isolineCost && maxCost >= isolineCost) {
  181.                     if (LOGGER.isDebugEnabled())
  182.                         sw.start();
  183.                     addBorderEdgeGeometry(points, isolineCost, minSplitLength, iter, maxCost, minCost, bufferSize);
  184.                     if (LOGGER.isDebugEnabled())
  185.                         sw.stop();
  186.                 }
  187.             }
  188.         }
  189.         if (LOGGER.isDebugEnabled())
  190.             LOGGER.debug("Expanding edges " + sw.getSeconds());

  191.         Geometry[] geometries = new Geometry[points.size()];

  192.         for (int i = 0; i < points.size(); ++i) {
  193.             Coordinate c = points.get(i);
  194.             geometries[i] = geometryFactory.createPoint(c);
  195.         }

  196.         return new GeometryCollection(geometries, geometryFactory);
  197.     }
  198. }