private int drawEdge(Edge e) { if (e.getGeometry() == null) return 0; // do not attempt to draw geometry-less edges Coordinate[] coords = e.getGeometry().getCoordinates(); beginShape(); for (int i = 0; i < coords.length; i++) vertex((float) toScreenX(coords[i].x), (float) toScreenY(coords[i].y)); endShape(); return coords.length; // should be used to count segments, not edges drawn }
private boolean testForUshape( Edge edge, long maxTime, long fromTime, long toTime, double angleLimit, double distanceTolerance, double userSpeed, boolean hasCar, boolean performSpeedTest) { LineString ls = (LineString) edge.getGeometry(); if (ls.getNumPoints() <= 3) { // first filter since u-shapes need at least 4 pts // this is the normal case return false; } else { // try to identify u-shapes by checking if the angle EndPoint-StartPoint-StartPoint+1 // is about 90 degrees (using Azimuths on the sphere) double diffTo90Azimuths = 360; if (edge instanceof PlainStreetEdge) { double firstSegmentAngle = DirectionUtils.getFirstAngle(edge.getGeometry()); if (firstSegmentAngle < 0) firstSegmentAngle = firstSegmentAngle + Math.PI; double firstToLastSegmentAngle = getFirstToLastSegmentAngle(edge.getGeometry()); if (firstToLastSegmentAngle < 0) firstToLastSegmentAngle = firstToLastSegmentAngle + Math.PI; double diffAzimuths = Math.abs(firstToLastSegmentAngle - firstSegmentAngle); diffTo90Azimuths = Math.abs(diffAzimuths - (Math.PI / 2.0)); } else { // this will happen in particular for transit routes // LOG.debug("Edge is not a PlainStreetEdge"); } if (diffTo90Azimuths < angleLimit) { // no need to test further if we know its a u-shape // System.out.println("u-shape found, (spherical) angle: " + diffTo90Azimuths* 180/Math.PI); return true; } else { if (performSpeedTest) { // Use also a distance based criteria since the angle criteria may fail. // However a distance based one may fail as well for steep terrain. long dt = Math.abs(toTime - fromTime); double lineDist = edge.getDistance(); double distanceToWalkInTimeMissing = distanceToMoveInRemainingTime(maxTime, fromTime, dt, userSpeed, edge, hasCar, false); double approxWalkableDistanceInTime = distanceToWalkInTimeMissing * distanceTolerance; if ((approxWalkableDistanceInTime < lineDist)) { return true; } } return false; } } }
public synchronized void initIndexes() { if (vertexIndex != null) { return; } graphService.setLoadLevel(LoadLevel.DEBUG); Graph graph = graphService.getGraph(); vertexIndex = new STRtree(); edgeIndex = new STRtree(); for (Vertex v : graph.getVertices()) { Envelope vertexEnvelope = new Envelope(v.getCoordinate()); vertexIndex.insert(vertexEnvelope, v); for (Edge e : v.getOutgoing()) { Envelope envelope; Geometry geometry = e.getGeometry(); if (geometry == null) { envelope = vertexEnvelope; } else { envelope = geometry.getEnvelopeInternal(); } edgeIndex.insert(envelope, e); } } vertexIndex.build(); edgeIndex.build(); }
/** * Get edges inside a bbox. * * @return */ @Secured({"ROLE_USER"}) @GET @Path("/edges") @Produces({MediaType.APPLICATION_JSON}) public Object getEdges( @QueryParam("lowerLeft") String lowerLeft, @QueryParam("upperRight") String upperRight, @QueryParam("exactClass") String className, @QueryParam("skipTransit") boolean skipTransit, @QueryParam("skipStreets") boolean skipStreets, @QueryParam("skipNoGeometry") boolean skipNoGeometry) { initIndexes(); Envelope envelope = getEnvelope(lowerLeft, upperRight); EdgeSet out = new EdgeSet(); Graph graph = graphService.getGraph(); @SuppressWarnings("unchecked") List<Edge> query = edgeIndex.query(envelope); out.edges = new ArrayList<WrappedEdge>(); for (Edge e : query) { if (skipStreets && (e instanceof StreetEdge)) continue; if (skipTransit && !(e instanceof StreetEdge)) continue; if (skipNoGeometry && e.getGeometry() == null) continue; if (className != null && !e.getClass().getName().endsWith("." + className)) continue; out.edges.add(new WrappedEdge(e, graph.getIdForEdge(e))); } return out.withGraph(graph); }
protected List<Edge> getOutgoingMatchableEdges(Vertex vertex) { List<Edge> edges = new ArrayList<Edge>(); for (Edge e : vertex.getOutgoing()) { if (!(e instanceof StreetEdge)) continue; if (e.getGeometry() == null) continue; edges.add(e); } return edges; }
/* use endpoints instead of geometry for quick updating */ private void drawEdgeFast(Edge e) { Coordinate[] coords = e.getGeometry().getCoordinates(); Coordinate c0 = coords[0]; Coordinate c1 = coords[coords.length - 1]; line( (float) toScreenX(c0.x), (float) toScreenY(c0.y), (float) toScreenX(c1.x), (float) toScreenY(c1.y)); }
/* * Iterate through all vertices and their (outgoing) edges. If they are of 'interesting' types, add them to the corresponding spatial index. */ public synchronized void buildSpatialIndex() { vertexIndex = new STRtree(); edgeIndex = new STRtree(); Envelope env; // int xminx, xmax, ymin, ymax; for (Vertex v : graph.getVertices()) { Coordinate c = v.getCoordinate(); env = new Envelope(c); vertexIndex.insert(env, v); for (Edge e : v.getOutgoing()) { if (e.getGeometry() == null) continue; if (e instanceof PatternEdge || e instanceof StreetTransitLink || e instanceof StreetEdge) { env = e.getGeometry().getEnvelopeInternal(); edgeIndex.insert(env, e); } } } vertexIndex.build(); edgeIndex.build(); }
public InferredEdge(@Nonnull Edge edge, @Nonnull Integer edgeId, @Nonnull OtpGraph graph) { Preconditions.checkNotNull(edge); Preconditions.checkNotNull(graph); Preconditions.checkNotNull(edgeId); assert !(edge instanceof TurnEdge || edge == null); this.graph = graph; this.edgeId = edgeId; this.edge = edge; /* * Warning: this geometry is in lon/lat and may contain more than one * straight line. */ this.geometry = edge.getGeometry(); this.locationIndexedLine = new LocationIndexedLine(geometry); this.lengthIndexedLine = new LengthIndexedLine(geometry); this.lengthLocationMap = new LengthLocationMap(geometry); this.startVertex = edge.getFromVertex(); this.endVertex = edge.getToVertex(); final Coordinate startPointCoord = this.locationIndexedLine.extractPoint(this.locationIndexedLine.getStartIndex()); this.startPoint = VectorFactory.getDefault().createVector2D(startPointCoord.x, startPointCoord.y); final Coordinate endPointCoord = this.locationIndexedLine.extractPoint(this.locationIndexedLine.getEndIndex()); this.endPoint = VectorFactory.getDefault().createVector2D(endPointCoord.x, endPointCoord.y); this.velocityPrecisionDist = // ~4.4 m/s, std. dev ~ 30 m/s, Gamma with exp. value = 30 m/s // TODO perhaps variance of velocity should be in m/s^2. yeah... new NormalInverseGammaDistribution( 4.4d, 1d / Math.pow(30d, 2d), 1d / Math.pow(30d, 2d) + 1d, Math.pow(30d, 2d)); this.velocityEstimator = new UnivariateGaussianMeanVarianceBayesianEstimator(velocityPrecisionDist); }
@Test public final void testOnBoardDepartureTime() { Coordinate[] coordinates = new Coordinate[5]; coordinates[0] = new Coordinate(0.0, 0.0); coordinates[1] = new Coordinate(0.0, 1.0); coordinates[2] = new Coordinate(2.0, 1.0); coordinates[3] = new Coordinate(5.0, 1.0); coordinates[4] = new Coordinate(5.0, 5.0); PatternDepartVertex depart = mock(PatternDepartVertex.class); PatternArriveVertex dwell = mock(PatternArriveVertex.class); PatternArriveVertex arrive = mock(PatternArriveVertex.class); Graph graph = mock(Graph.class); RoutingRequest routingRequest = mock(RoutingRequest.class); ServiceDay serviceDay = mock(ServiceDay.class); when(graph.getTimeZone()).thenReturn(TimeZone.getTimeZone("GMT")); GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory(); CoordinateSequenceFactory coordinateSequenceFactory = geometryFactory.getCoordinateSequenceFactory(); CoordinateSequence coordinateSequence = coordinateSequenceFactory.create(coordinates); LineString geometry = new LineString(coordinateSequence, geometryFactory); ArrayList<Edge> hops = new ArrayList<Edge>(2); RoutingContext routingContext = new RoutingContext(routingRequest, graph, null, arrive); AgencyAndId agencyAndId = new AgencyAndId("Agency", "ID"); Route route = new Route(); ArrayList<StopTime> stopTimes = new ArrayList<StopTime>(3); StopTime stopDepartTime = new StopTime(); StopTime stopDwellTime = new StopTime(); StopTime stopArriveTime = new StopTime(); Stop stopDepart = new Stop(); Stop stopDwell = new Stop(); Stop stopArrive = new Stop(); Trip trip = new Trip(); routingContext.serviceDays = new ArrayList<ServiceDay>(Collections.singletonList(serviceDay)); route.setId(agencyAndId); stopDepart.setId(agencyAndId); stopDwell.setId(agencyAndId); stopArrive.setId(agencyAndId); stopDepartTime.setStop(stopDepart); stopDepartTime.setDepartureTime(0); stopDwellTime.setArrivalTime(20); stopDwellTime.setStop(stopDwell); stopDwellTime.setDepartureTime(40); stopArriveTime.setArrivalTime(60); stopArriveTime.setStop(stopArrive); stopTimes.add(stopDepartTime); stopTimes.add(stopDwellTime); stopTimes.add(stopArriveTime); trip.setId(agencyAndId); trip.setTripHeadsign("The right"); TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator()); StopPattern stopPattern = new StopPattern(stopTimes); TripPattern tripPattern = new TripPattern(route, stopPattern); when(depart.getTripPattern()).thenReturn(tripPattern); when(dwell.getTripPattern()).thenReturn(tripPattern); PatternHop patternHop0 = new PatternHop(depart, dwell, stopDepart, stopDwell, 0); PatternHop patternHop1 = new PatternHop(dwell, arrive, stopDwell, stopArrive, 1); hops.add(patternHop0); hops.add(patternHop1); when(graph.getEdges()).thenReturn(hops); when(depart.getCoordinate()).thenReturn(new Coordinate(0, 0)); when(dwell.getCoordinate()).thenReturn(new Coordinate(0, 0)); when(arrive.getCoordinate()).thenReturn(new Coordinate(0, 0)); when(routingRequest.getFrom()).thenReturn(new GenericLocation()); when(routingRequest.getStartingTransitTripId()).thenReturn(agencyAndId); when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(9); patternHop0.setGeometry(geometry); tripPattern.add(tripTimes); graph.index = new GraphIndex(graph); coordinates = new Coordinate[3]; coordinates[0] = new Coordinate(3.5, 1.0); coordinates[1] = new Coordinate(5.0, 1.0); coordinates[2] = new Coordinate(5.0, 5.0); coordinateSequence = coordinateSequenceFactory.create(coordinates); geometry = new LineString(coordinateSequence, geometryFactory); Vertex vertex = onBoardDepartServiceImpl.setupDepartOnBoard(routingContext); Edge edge = vertex.getOutgoing().toArray(new Edge[1])[0]; assertEquals(vertex, edge.getFromVertex()); assertEquals(dwell, edge.getToVertex()); assertEquals("The right", edge.getDirection()); assertEquals(geometry, edge.getGeometry()); assertEquals(coordinates[0].x, vertex.getX(), 0.0); assertEquals(coordinates[0].y, vertex.getY(), 0.0); }
/** * Calculates what distance can be traveled with the remaining time and given speeds. For car use * the speed limit is taken from the edge itself. Slopes are accounted for when walking and * biking. A minimal slope of 0.06 (6m/100m) is necessary. * * @param maxTime in sec, the time we have left * @param fromTime in sec, the time when we enter the edge * @param traverseTime in sec, original edge traverse time needed to adjust the speed based * calculation to slope effects * @param userSpeed in m/sec, dependent on traversal mode * @param edge the edge itself (used to the get the speed in car mode) * @param usesCar if we traverse the edge in car mode * @param hasUshape if know, indicate if the edge has a u-shape * @return the distance in meter that can be moved until maxTime */ double distanceToMoveInRemainingTime( long maxTime, long fromTime, double traverseTime, double userSpeed, Edge edge, boolean usesCar, boolean hasUshape) { boolean isTooFast = false; String msg = ""; double originalTravelSpeed = edge.getDistance() / traverseTime; // this may be wrong for u-shapes if (originalTravelSpeed < userSpeed) { // we may have slope effects if (edge instanceof PlainStreetEdge) { PlainStreetEdge pe = (PlainStreetEdge) edge; double maxSlope = pe.getElevationProfileSegment().getMaxSlope(); // if we are over the slope limit, then we should use the slower speed if (maxSlope > 0.06) { // limit 6m/100m = 3.4 degree userSpeed = originalTravelSpeed; } } } else { // in this case we may have a u-shape, or the user speeds are too small, or something else. double vdiff = Math.abs(originalTravelSpeed - userSpeed); double vDiffPercent = vdiff / (userSpeed / 100.0); if (vDiffPercent > 20) { isTooFast = true; // [sstein Dec 2012]: Note, it seems like most of these edges are indeed of u-shape type, // i.e. small roads that come from and return from (the same) main road msg = "v_traversed is much faster than (allowed) v_user, edgeName: " + edge.getName() + ", >>> (in m/s): v_traversed=" + (int) Math.floor(originalTravelSpeed) + ", v_maxUser="******", known u-shape, "; } if ((usesCar == false) && (hasUshape == false)) { this.tooFastTraversedEdgeGeoms.add(edge.getGeometry()); LOG.debug(msg); } // otherwise we print msg below } } // correct speed for car use, as each road has its speed limits if (usesCar) { if (edge instanceof PlainStreetEdge) { PlainStreetEdge pe = (PlainStreetEdge) edge; userSpeed = pe.getCarSpeed(); // we need to check again if the originalTravelSpeed is faster if ((isTooFast == true) && (originalTravelSpeed > userSpeed) && (hasUshape == false)) { this.tooFastTraversedEdgeGeoms.add(edge.getGeometry()); LOG.debug(msg + "; setting v_PlainStreetEdge=" + (int) Math.floor(userSpeed)); } } } // finally calculate how far we can travel with the remaining time long timeMissing = maxTime - fromTime; double distanceToWalkInTimeMissing = timeMissing * userSpeed; return distanceToWalkInTimeMissing; }
/** * Filters all input edges and returns all those as LineString geometries, that have at least one * end point within the time limits. If they have only one end point inside, then the sub-edge is * returned. * * @param maxTime the time limit in seconds that defines the size of the walkshed * @param allConnectingStateEdges all Edges that have been found to connect all states < maxTime * @param spt the ShortestPathTree generated for the pushpin drop point as origin * @param angleLimit the angle tolerance to detect roads with u-shapes, i.e. Pi/2 angles, in * Radiant. * @param distanceTolerance in percent (e.g. 1.1 = 110%) for u-shape detection based on distance * criteria * @param hasCar is travel mode by CAR? * @param performSpeedTest if true applies a test to each edge to check if the edge can be * traversed in time. The test can detect u-shaped roads. * @return */ ArrayList<LineString> getLinesAndSubEdgesWithinMaxTime( long maxTime, ArrayList<Edge> allConnectingStateEdges, ShortestPathTree spt, double angleLimit, double distanceTolerance, double userSpeed, boolean hasCar, boolean performSpeedTest) { LOG.debug("maximal userSpeed set to: " + userSpeed + " m/sec "); if (hasCar) { LOG.debug("travel mode is set to CAR, hence the given speed may be adjusted for each edge"); } ArrayList<LineString> walkShedEdges = new ArrayList<LineString>(); ArrayList<LineString> otherEdges = new ArrayList<LineString>(); ArrayList<LineString> borderEdges = new ArrayList<LineString>(); ArrayList<LineString> uShapes = new ArrayList<LineString>(); int countEdgesOutside = 0; // -- determination of walkshed edges via edge states for (Iterator iterator = allConnectingStateEdges.iterator(); iterator.hasNext(); ) { Edge edge = (Edge) iterator.next(); State sFrom = spt.getState(edge.getFromVertex()); State sTo = spt.getState(edge.getToVertex()); if ((sFrom != null) && (sTo != null)) { long fromTime = sFrom.getElapsedTimeSeconds(); long toTime = sTo.getElapsedTimeSeconds(); long dt = Math.abs(toTime - fromTime); Geometry edgeGeom = edge.getGeometry(); if ((edgeGeom != null) && (edgeGeom instanceof LineString)) { LineString ls = (LineString) edgeGeom; // detect u-shape roads/crescents - they need to be treated separately boolean uShapeOrLonger = testForUshape( edge, maxTime, fromTime, toTime, angleLimit, distanceTolerance, userSpeed, hasCar, performSpeedTest); if (uShapeOrLonger) { uShapes.add(ls); } // evaluate if an edge is completely within the time or only with one end if ((fromTime < maxTime) && (toTime < maxTime)) { // this one is within the time limit on both ends, however we need to do // a second test if we have a u-shaped road. if (uShapeOrLonger) { treatAndAddUshapeWithinTimeLimits( maxTime, userSpeed, walkShedEdges, edge, fromTime, toTime, ls, hasCar); } else { walkShedEdges.add(ls); } } // end if:fromTime & toTime < maxTime else { // check if at least one end is inside, because then we need to // create the sub edge if ((fromTime < maxTime) || (toTime < maxTime)) { double lineDist = edge.getDistance(); LineString inputLS = ls; double fraction = 1.0; if (fromTime < toTime) { double distanceToWalkInTimeMissing = distanceToMoveInRemainingTime( maxTime, fromTime, dt, userSpeed, edge, hasCar, uShapeOrLonger); fraction = (double) distanceToWalkInTimeMissing / (double) lineDist; } else { // toTime < fromTime : invert the edge direction inputLS = (LineString) ls.reverse(); double distanceToWalkInTimeMissing = distanceToMoveInRemainingTime( maxTime, toTime, dt, userSpeed, edge, hasCar, uShapeOrLonger); fraction = (double) distanceToWalkInTimeMissing / (double) lineDist; } // get the subedge LineString subLine = this.getSubLineString(inputLS, fraction); borderEdges.add(subLine); } else { // this edge is completely outside - this should actually not happen // we will not do anything, just count countEdgesOutside++; } } // end else: fromTime & toTime < maxTime } // end if: edge instance of LineString else { // edge is not instance of LineString LOG.debug("edge not instance of LineString"); } } // end if(sFrom && sTo != null) start Else else { // LOG.debug("could not retrieve state for edge-endpoint"); //for a 6min car ride, there can // be (too) many of such messages Geometry edgeGeom = edge.getGeometry(); if ((edgeGeom != null) && (edgeGeom instanceof LineString)) { otherEdges.add((LineString) edgeGeom); } } // end else: sFrom && sTo != null } // end for loop over edges walkShedEdges.addAll(borderEdges); this.debugGeoms.addAll(uShapes); LOG.debug("number of detected u-shapes/crescents: " + uShapes.size()); return walkShedEdges; }
/** * Calculates walksheds for a given location, based on time given to walk and the walk speed. * * <p>Depending on the value for the "output" parameter (i.e. "POINTS", "SHED" or "EDGES"), a * different type of GeoJSON geometry is returned. If a SHED is requested, then a ConcaveHull of * the EDGES/roads is returned. If that fails, a ConvexHull will be returned. * * <p>The ConcaveHull parameter is set to 0.005 degrees. The offroad walkspeed is assumed to be * 0.83333 m/sec (= 3km/h) until a road is hit. * * <p>Note that the set of EDGES/roads returned as well as POINTS returned may contain duplicates. * If POINTS are requested, then not the end-points are returned at which the max time is reached, * but instead all the graph nodes/crossings that are within the time limits. * * <p>In case there is no road near by within the given time, then a circle for the walktime limit * is created and returned for the SHED parameter. Otherwise the edge with the direction towards * the closest road. Note that the circle is calculated in Euclidian 2D coordinates, and * distortions towards an ellipse will appear if it is transformed/projected to the user location. * * <p>An example request may look like this: * localhost:8080/otp-rest-servlet/ws/iso?layers=traveltime&styles=mask&batch=true&fromPlace=51.040193121307176 * %2C-114.04471635818481&toPlace * =51.09098935%2C-113.95179705&time=2012-06-06T08%3A00%3A00&mode=WALK&maxWalkDistance=10000&walkSpeed=1.38&walkTime=10.7&output=EDGES * Though the first parameters (i) layer, (ii) styles and (iii) batch could be discarded. * * @param walkmins Maximum number of minutes to walk. * @param output Can be set to "POINTS", "SHED" or "EDGES" to return different types of GeoJSON * geometry. SHED returns a ConcaveHull or ConvexHull of the edges/roads. POINTS returns all * graph nodes that are within the time limit. * @return a JSON document containing geometries (either points, lineStrings or a polygon). * @throws Exception * @author sstein---geo.uzh.ch */ @GET @Produces({MediaType.APPLICATION_JSON}) public String getIsochrone( @QueryParam("walkTime") @DefaultValue("15") double walkmins, @QueryParam("output") @DefaultValue("POINTS") String output) throws Exception { this.debugGeoms = new ArrayList(); this.tooFastTraversedEdgeGeoms = new ArrayList(); RoutingRequest sptRequestA = buildRequest(0); String from = sptRequestA.getFrom().toString(); int pos = 1; float lat = 0; float lon = 0; for (String s : from.split(",")) { if (s.isEmpty()) { // no location Response.status(Status.BAD_REQUEST).entity("no position").build(); return null; } try { float num = Float.parseFloat(s); if (pos == 1) { lat = num; } if (pos == 2) { lon = num; } } catch (Exception e) { throw new WebApplicationException( Response.status(Status.BAD_REQUEST) .entity( "Could not parse position string to number. Require numerical lat & long coords.") .build()); } pos++; } GeometryFactory gf = new GeometryFactory(); Coordinate dropPoint = new Coordinate(lon, lat); int walkInMin = (int) Math.floor(walkmins); double walkInSec = walkmins * 60; LOG.debug( "given travel time: " + walkInMin + " mins + " + (walkInSec - (60 * walkInMin)) + " sec"); // restrict the evaluated SPT size to 30mins for requests with walking < 30min // if larger walking times are requested we adjust the evaluated // graph dynamically by 1.3 * min -> this should save processing time if (walkInMin < 30) { sptRequestA.worstTime = sptRequestA.dateTime + (30 * 60); } else { sptRequestA.worstTime = sptRequestA.dateTime + Math.round(walkInMin * 1.3 * 60); } // set the switch-time for shed/area calculation, i.e. to decide if the hull is calculated based // on points or on edges TraverseModeSet modes = sptRequestA.modes; LOG.debug("mode(s): " + modes); if ((modes.contains(TraverseMode.TRANSIT)) || (modes.contains(TraverseMode.BUSISH)) || (modes.contains(TraverseMode.TRAINISH))) { shedCalcMethodSwitchTimeInSec = 60 * 20; // 20min (use 20min for transit, since buses may not come all the time) } else if (modes.contains(TraverseMode.CAR)) { shedCalcMethodSwitchTimeInSec = 60 * 10; // 10min } else if (modes.contains(TraverseMode.BICYCLE)) { shedCalcMethodSwitchTimeInSec = 60 * 10; // 10min } else { shedCalcMethodSwitchTimeInSec = 60 * 20; // 20min } // set the maxUserSpeed, which is used later to check for u-type streets/crescents when // calculating sub-edges; // Note, that the car speed depends on the edge itself, so this value may be replaced later this.usesCar = false; int numberOfModes = modes.getModes().size(); if (numberOfModes == 1) { if (modes.getWalk()) { this.maxUserSpeed = sptRequestA.getWalkSpeed(); } else if (modes.getBicycle()) { this.maxUserSpeed = sptRequestA.getBikeSpeed(); } else if (modes.getDriving()) { this.maxUserSpeed = sptRequestA.getCarSpeed(); this.usesCar = true; } } else { // for all other cases (multiple-modes) // sstein: I thought I may set it to 36.111 m/sec = 130 km/h, // but maybe it is better to assume walk speed for transit, i.e. treat it like if the // person gets off the bus on the last crossing and walks the "last mile". this.maxUserSpeed = sptRequestA.getWalkSpeed(); } if (doSpeedTest) { LOG.debug("performing angle and speed based test to detect u-shapes"); } else { LOG.debug("performing only angle based test to detect u-shapes"); } // TODO: OTP prefers to snap to car-roads/ways, which is not so nice, when walking, // and a footpath is closer by. So far there is no option to switch that off // create the ShortestPathTree try { sptRequestA.setRoutingContext(graphService.getGraph()); } catch (Exception e) { // if we get an exception here, and in particular a VertexNotFoundException, // then it is likely that we chose a (transit) mode without having that (transit) modes data LOG.debug("cannot set RoutingContext: " + e.toString()); LOG.debug("cannot set RoutingContext: setting mode=WALK"); sptRequestA.setMode(TraverseMode.WALK); // fall back to walk mode sptRequestA.setRoutingContext(graphService.getGraph()); } ShortestPathTree sptA = sptService.getShortestPathTree(sptRequestA); StreetLocation origin = (StreetLocation) sptRequestA.rctx.fromVertex; sptRequestA.cleanup(); // remove inserted points // create a LineString for display Coordinate pathToStreetCoords[] = new Coordinate[2]; pathToStreetCoords[0] = dropPoint; pathToStreetCoords[1] = origin.getCoordinate(); LineString pathToStreet = gf.createLineString(pathToStreetCoords); // get distance between origin and drop point for time correction double distanceToRoad = this.distanceLibrary.distance(origin.getY(), origin.getX(), dropPoint.y, dropPoint.x); long offRoadTimeCorrection = (long) (distanceToRoad / this.offRoadWalkspeed); // // --- filter the states --- // Set<Coordinate> visitedCoords = new HashSet<Coordinate>(); ArrayList<Edge> allConnectingEdges = new ArrayList<Edge>(); Coordinate coords[] = null; long maxTime = (long) walkInSec - offRoadTimeCorrection; // System.out.println("Reducing walktime from: " + (int)(walkmins * 60) + "sec to " + maxTime + // "sec due to initial walk of " + distanceToRoad // + "m"); // if the initial walk is already to long, there is no need to parse... if (maxTime <= 0) { noRoadNearBy = true; long timeToWalk = (long) walkInSec; long timeBetweenStates = offRoadTimeCorrection; long timeMissing = timeToWalk; double fraction = (double) timeMissing / (double) timeBetweenStates; pathToStreet = getSubLineString(pathToStreet, fraction); LOG.debug( "no street found within giving travel time (for off-road walkspeed: {} m/sec)", this.offRoadWalkspeed); } else { noRoadNearBy = false; Map<ReversibleLineStringWrapper, Edge> connectingEdgesMap = Maps.newHashMap(); for (State state : sptA.getAllStates()) { long et = state.getElapsedTimeSeconds(); if (et <= maxTime) { // -- filter points, as the same coordinate may be passed several times due to the graph // structure // in a Calgary suburb family homes neighborhood with a 15min walkshed it filtered about // 250 points away (while 145 were finally displayed) if (visitedCoords.contains(state.getVertex().getCoordinate())) { continue; } else { visitedCoords.add(state.getVertex().getCoordinate()); } // -- get all Edges needed later for the edge representation // and to calculate an edge-based walkshed // Note, it can happen that we get a null geometry here, e.g. for hop-edges! Collection<Edge> vertexEdgesIn = state.getVertex().getIncoming(); for (Iterator<Edge> iterator = vertexEdgesIn.iterator(); iterator.hasNext(); ) { Edge edge = (Edge) iterator.next(); Geometry edgeGeom = edge.getGeometry(); if (edgeGeom != null) { // make sure we get only real edges if (edgeGeom instanceof LineString) { // allConnectingEdges.add(edge); // instead of this, use a map now, so we don't have // similar edge many times connectingEdgesMap.put( new ReversibleLineStringWrapper((LineString) edgeGeom), edge); } } } Collection<Edge> vertexEdgesOut = state.getVertex().getOutgoing(); for (Iterator<Edge> iterator = vertexEdgesOut.iterator(); iterator.hasNext(); ) { Edge edge = (Edge) iterator.next(); Geometry edgeGeom = edge.getGeometry(); if (edgeGeom != null) { if (edgeGeom instanceof LineString) { // allConnectingEdges.add(edge); // instead of this, use a map now, so we don't // similar edge many times connectingEdgesMap.put( new ReversibleLineStringWrapper((LineString) edgeGeom), edge); } } } } // end : if(et < maxTime) } // -- // points from list to array, for later coords = new Coordinate[visitedCoords.size()]; int i = 0; for (Coordinate c : visitedCoords) coords[i++] = c; // connection edges from Map to List allConnectingEdges.clear(); for (Edge tedge : connectingEdgesMap.values()) allConnectingEdges.add(tedge); } StringWriter sw = new StringWriter(); GeoJSONBuilder json = new GeoJSONBuilder(sw); // // -- create the different outputs --- // try { if (output.equals(IsoChrone.RESULT_TYPE_POINTS)) { // in case there was no road we create a circle and // and return those points if (noRoadNearBy) { Geometry circleShape = createCirle(dropPoint, pathToStreet); coords = circleShape.getCoordinates(); } // -- the states/nodes with time elapsed <= X min. LOG.debug("write multipoint geom with {} points", coords.length); json.writeGeom(gf.createMultiPoint(coords)); LOG.debug("done"); } else if (output.equals(IsoChrone.RESULT_TYPE_SHED)) { Geometry geomsArray[] = null; // in case there was no road we create a circle if (noRoadNearBy) { Geometry circleShape = createCirle(dropPoint, pathToStreet); json.writeGeom(circleShape); } else { if (maxTime > shedCalcMethodSwitchTimeInSec) { // eg., walkshed > 20 min // -- create a point-based walkshed // less exact and should be used for large walksheds with many edges LOG.debug("create point-based shed (not from edges)"); geomsArray = new Geometry[coords.length]; for (int j = 0; j < geomsArray.length; j++) { geomsArray[j] = gf.createPoint(coords[j]); } } else { // -- create an edge-based walkshed // it is more exact and should be used for short walks LOG.debug("create edge-based shed (not from points)"); Map<ReversibleLineStringWrapper, LineString> walkShedEdges = Maps.newHashMap(); // add the walk from the pushpin to closest street point walkShedEdges.put(new ReversibleLineStringWrapper(pathToStreet), pathToStreet); // get the edges and edge parts within time limits ArrayList<LineString> withinTimeEdges = this.getLinesAndSubEdgesWithinMaxTime( maxTime, allConnectingEdges, sptA, angleLimitForUShapeDetection, distanceToleranceForUShapeDetection, maxUserSpeed, usesCar, doSpeedTest); for (LineString ls : withinTimeEdges) { walkShedEdges.put(new ReversibleLineStringWrapper(ls), ls); } geomsArray = new Geometry[walkShedEdges.size()]; int k = 0; for (LineString ls : walkShedEdges.values()) geomsArray[k++] = ls; } // end if-else: maxTime condition GeometryCollection gc = gf.createGeometryCollection(geomsArray); // create the concave hull, but in case it fails we just return the convex hull Geometry outputHull = null; LOG.debug( "create concave hull from {} geoms with edge length limit of about {} m (distance on meridian)", geomsArray.length, concaveHullAlpha * 111132); // 1deg at Latitude phi = 45deg is about 111.132km // (see wikipedia: // http://en.wikipedia.org/wiki/Latitude#The_length_of_a_degree_of_latitude) try { ConcaveHull hull = new ConcaveHull(gc, concaveHullAlpha); outputHull = hull.getConcaveHull(); } catch (Exception e) { outputHull = gc.convexHull(); LOG.debug("Could not generate ConcaveHull for WalkShed, using ConvexHull instead."); } LOG.debug("write shed geom"); json.writeGeom(outputHull); LOG.debug("done"); } } else if (output.equals(IsoChrone.RESULT_TYPE_EDGES)) { // in case there was no road we return only the suggested path to the street if (noRoadNearBy) { json.writeGeom(pathToStreet); } else { // -- if we would use only the edges from the paths to the origin we will miss // some edges that will be never on the shortest path (e.g. loops/crescents). // However, we can retrieve all edges by checking the times for each // edge end-point Map<ReversibleLineStringWrapper, LineString> walkShedEdges = Maps.newHashMap(); // add the walk from the pushpin to closest street point walkShedEdges.put(new ReversibleLineStringWrapper(pathToStreet), pathToStreet); // get the edges and edge parts within time limits ArrayList<LineString> withinTimeEdges = this.getLinesAndSubEdgesWithinMaxTime( maxTime, allConnectingEdges, sptA, angleLimitForUShapeDetection, distanceToleranceForUShapeDetection, maxUserSpeed, usesCar, doSpeedTest); for (LineString ls : withinTimeEdges) { walkShedEdges.put(new ReversibleLineStringWrapper(ls), ls); } Geometry mls = null; LineString edges[] = new LineString[walkShedEdges.size()]; int k = 0; for (LineString ls : walkShedEdges.values()) edges[k++] = ls; LOG.debug("create multilinestring from {} geoms", edges.length); mls = gf.createMultiLineString(edges); LOG.debug("write geom"); json.writeGeom(mls); LOG.debug("done"); } } else if (output.equals("DEBUGEDGES")) { // -- for debugging, i.e. display of detected u-shapes/crescents ArrayList<LineString> withinTimeEdges = this.getLinesAndSubEdgesWithinMaxTime( maxTime, allConnectingEdges, sptA, angleLimitForUShapeDetection, distanceToleranceForUShapeDetection, maxUserSpeed, usesCar, doSpeedTest); if (this.showTooFastEdgesAsDebugGeomsANDnotUShapes) { LOG.debug("displaying edges that are traversed too fast"); this.debugGeoms = this.tooFastTraversedEdgeGeoms; } else { LOG.debug("displaying detected u-shaped roads/crescents"); } LineString edges[] = new LineString[this.debugGeoms.size()]; int k = 0; for (Iterator iterator = debugGeoms.iterator(); iterator.hasNext(); ) { LineString ls = (LineString) iterator.next(); edges[k] = ls; k++; } Geometry mls = gf.createMultiLineString(edges); LOG.debug("write debug geom"); json.writeGeom(mls); LOG.debug("done"); } } catch (org.codehaus.jettison.json.JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } return sw.toString(); }
public synchronized void draw() { final int BLOCK_SIZE = 1000; final long DECIMATE = 100; final int FRAME_TIME = 800 / FRAME_RATE; int startMillis = millis(); if (drawLevel == DRAW_PARTIAL) { background(15); stroke(30, 128, 30); strokeWeight(1); noFill(); // noSmooth(); int drawIndex = 0; int drawStart = 0; int drawCount = 0; while (drawStart < visibleStreetEdges.size()) { if (drawFast) drawEdgeFast(visibleStreetEdges.get(drawIndex)); else drawEdge(visibleStreetEdges.get(drawIndex)); drawIndex += DECIMATE; drawCount += 1; if (drawCount % BLOCK_SIZE == 0 && millis() - startMillis > FRAME_TIME) { drawFast = drawCount < visibleStreetEdges.size() / 4; break; } if (drawIndex >= visibleStreetEdges.size()) { drawStart += 1; drawIndex = drawStart; } } } else if (drawLevel == DRAW_ALL) { // } else if (drawLevel == DRAW_STREETS) { // smooth(); if (drawOffset == 0) { findVisibleElements(); background(15); } if (drawStreetEdges) { stroke(30, 128, 30); // dark green strokeWeight(1); noFill(); // for (Edge e : visibleStreetEdges) drawEdge(e); while (drawOffset < visibleStreetEdges.size()) { drawEdge(visibleStreetEdges.get(drawOffset)); drawOffset += 1; // if (drawOffset % FRAME_SIZE == 0) return; if (drawOffset % BLOCK_SIZE == 0) { if (millis() - startMillis > FRAME_TIME) return; } } } } else if (drawLevel == DRAW_TRANSIT) { if (drawTransitEdges) { stroke(40, 40, 128, 30); // transparent blue strokeWeight(4); noFill(); // for (Edge e : visibleTransitEdges) { while (drawOffset < visibleTransitEdges.size()) { Edge e = visibleTransitEdges.get(drawOffset); drawEdge(e); drawOffset += 1; if (drawOffset % BLOCK_SIZE == 0) { if (millis() - startMillis > FRAME_TIME) return; } } } } else if (drawLevel == DRAW_VERTICES) { /* turn off vertex display when zoomed out */ final double METERS_PER_DEGREE_LAT = 111111.111111; drawTransitStopVertices = (modelBounds.getHeight() * METERS_PER_DEGREE_LAT / this.width < 4); /* Draw selected visible vertices */ fill(60, 60, 200); for (Vertex v : visibleVertices) { if (drawTransitStopVertices && v instanceof TransitStop) { drawVertex(v, 5); } } /* Draw highlighted edges in another color */ noFill(); stroke(200, 200, 000, 16); // yellow transparent edge highlight strokeWeight(8); if (highlightedEdges != null) { for (Edge e : highlightedEdges) { drawEdge(e); } } /* Draw highlighted graph path in another color */ if (highlightedGraphPath != null) { drawGraphPath(highlightedGraphPath); } /* Draw (single) highlighted edge in highlight color */ if (highlightedEdge != null && highlightedEdge.getGeometry() != null) { stroke(200, 10, 10, 128); strokeWeight(8); drawEdge(highlightedEdge); } /* Draw highlighted vertices */ fill(255, 127, 0); // orange fill noStroke(); if (highlightedVertices != null) { for (Vertex v : highlightedVertices) { drawVertex(v, 8); } } /* Draw (single) highlighed vertex in a different color */ if (highlightedVertex != null) { fill(255, 255, 30); drawVertex(highlightedVertex, 7); } noFill(); } else if (drawLevel == DRAW_MINIMAL) { if (!newHighlightedEdges.isEmpty()) handleNewHighlights(); // Black background box fill(0, 0, 0); stroke(30, 128, 30); // noStroke(); strokeWeight(1); rect(3, 3, 303, textAscent() + textDescent() + 6); // Print lat & lon coordinates fill(128, 128, 256); // noStroke(); String output = lonFormatter.format(mouseModelX) + " " + latFormatter.format(mouseModelY); textAlign(LEFT, TOP); text(output, 6, 6); } drawOffset = 0; if (drawLevel > DRAW_MINIMAL) drawLevel -= 1; // move to next layer }
/** * Converts a list of street edges to a list of turn-by-turn directions. * * @param edges : A list of street edges * @return */ private List<WalkStep> getWalkSteps(List<State> states) { List<WalkStep> steps = new ArrayList<WalkStep>(); WalkStep step = null; double lastAngle = 0, distance = 0; // distance used for appending elevation profiles int roundaboutExit = 0; // track whether we are in a roundabout, and if so the exit number String roundaboutPreviousStreet = null; for (State currState : states) { State backState = currState.getBackState(); Edge edge = currState.getBackEdge(); EdgeNarrative edgeNarrative = currState.getBackEdgeNarrative(); boolean createdNewStep = false; if (edge instanceof FreeEdge) { continue; } if (!edgeNarrative.getMode().isOnStreetNonTransit()) { continue; // ignore STLs and the like } Geometry geom = edgeNarrative.getGeometry(); if (geom == null) { continue; } // generate a step for getting off an elevator (all // elevator narrative generation occurs when alighting). We don't need to know what came // before or will come after if (edge instanceof ElevatorAlightEdge) { // don't care what came before or comes after step = createWalkStep(currState); // tell the user where to get off the elevator using the exit notation, so the // i18n interface will say 'Elevator to <exit>' // what happens is that the webapp sees name == null and ignores that, and it sees // exit != null and uses to <exit> // the floor name is the AlightEdge name // reset to avoid confusion with 'Elevator on floor 1 to floor 1' step.streetName = ((ElevatorAlightEdge) edge).getName(); step.relativeDirection = RelativeDirection.ELEVATOR; steps.add(step); continue; } String streetName = edgeNarrative.getName(); int idx = streetName.indexOf('('); String streetNameNoParens; if (idx > 0) streetNameNoParens = streetName.substring(0, idx - 1); else streetNameNoParens = streetName; if (step == null) { // first step step = createWalkStep(currState); createdNewStep = true; steps.add(step); double thisAngle = DirectionUtils.getFirstAngle(geom); step.setAbsoluteDirection(thisAngle); // new step, set distance to length of first edge distance = edgeNarrative.getDistance(); } else if ((step.streetName != null && !step.streetNameNoParens().equals(streetNameNoParens)) && (!step.bogusName || !edgeNarrative.hasBogusName())) { /* street name has changed */ if (roundaboutExit > 0) { // if we were just on a roundabout, // make note of which exit was taken in the existing step step.exit = Integer.toString(roundaboutExit); // ordinal numbers from if (streetNameNoParens.equals(roundaboutPreviousStreet)) { step.stayOn = true; } // localization roundaboutExit = 0; } /* start a new step */ step = createWalkStep(currState); createdNewStep = true; steps.add(step); if (edgeNarrative.isRoundabout()) { // indicate that we are now on a roundabout // and use one-based exit numbering roundaboutExit = 1; roundaboutPreviousStreet = backState.getBackEdgeNarrative().getName(); idx = roundaboutPreviousStreet.indexOf('('); if (idx > 0) roundaboutPreviousStreet = roundaboutPreviousStreet.substring(0, idx - 1); } double thisAngle = DirectionUtils.getFirstAngle(geom); step.setDirections(lastAngle, thisAngle, edgeNarrative.isRoundabout()); // new step, set distance to length of first edge distance = edgeNarrative.getDistance(); } else { /* street name has not changed */ double thisAngle = DirectionUtils.getFirstAngle(geom); RelativeDirection direction = WalkStep.getRelativeDirection(lastAngle, thisAngle, edgeNarrative.isRoundabout()); boolean optionsBefore = backState.multipleOptionsBefore(); if (edgeNarrative.isRoundabout()) { // we are on a roundabout, and have already traversed at least one edge of it. if (optionsBefore) { // increment exit count if we passed one. roundaboutExit += 1; } } if (edgeNarrative.isRoundabout() || direction == RelativeDirection.CONTINUE) { // we are continuing almost straight, or continuing along a roundabout. // just append elevation info onto the existing step. } else { // we are not on a roundabout, and not continuing straight through. // figure out if there were other plausible turn options at the last // intersection // to see if we should generate a "left to continue" instruction. boolean shouldGenerateContinue = false; if (edge instanceof PlainStreetEdge) { // the next edges will be TinyTurnEdges or PlainStreetEdges, we hope double angleDiff = getAbsoluteAngleDiff(thisAngle, lastAngle); for (Edge alternative : backState.getVertex().getOutgoingStreetEdges()) { if (alternative instanceof TinyTurnEdge) { // a tiny turn edge has no geometry, but the next // edge will be a TurnEdge or PSE and will have direction alternative = alternative.getToVertex().getOutgoingStreetEdges().get(0); } if (alternative.getName().equals(streetName)) { // alternatives that have the same name // are usually caused by street splits continue; } double altAngle = DirectionUtils.getFirstAngle(alternative.getGeometry()); double altAngleDiff = getAbsoluteAngleDiff(altAngle, lastAngle); if (angleDiff > Math.PI / 4 || altAngleDiff - angleDiff < Math.PI / 16) { shouldGenerateContinue = true; break; } } } else if (edge instanceof TinyTurnEdge) { // do nothing as this will be handled in other cases } else { double angleDiff = getAbsoluteAngleDiff(lastAngle, thisAngle); // in the case of a turn edge, we actually have to go back two steps to see // where // else we might be, as once we are on the streetvertex leading into this // edge, // we are stuck State twoStatesBack = backState.getBackState(); Vertex backVertex = twoStatesBack.getVertex(); for (Edge alternative : backVertex.getOutgoingStreetEdges()) { List<Edge> alternatives = alternative.getToVertex().getOutgoingStreetEdges(); if (alternatives.size() == 0) { continue; // this is not an alternative } alternative = alternatives.get(0); if (alternative.getName().equals(streetName)) { // alternatives that have the same name // are usually caused by street splits continue; } double altAngle = DirectionUtils.getFirstAngle(alternative.getGeometry()); double altAngleDiff = getAbsoluteAngleDiff(altAngle, lastAngle); if (angleDiff > Math.PI / 4 || altAngleDiff - angleDiff < Math.PI / 16) { shouldGenerateContinue = true; break; } } } if (shouldGenerateContinue) { // turn to stay on same-named street step = createWalkStep(currState); createdNewStep = true; steps.add(step); step.setDirections(lastAngle, thisAngle, false); step.stayOn = true; // new step, set distance to length of first edge distance = edgeNarrative.getDistance(); } } } if (createdNewStep) { // check last three steps for zag int last = steps.size() - 1; if (last >= 2) { WalkStep threeBack = steps.get(last - 2); WalkStep twoBack = steps.get(last - 1); WalkStep lastStep = steps.get(last); if (twoBack.distance < MAX_ZAG_DISTANCE && lastStep.streetNameNoParens().equals(threeBack.streetNameNoParens())) { // total hack to remove zags. steps.remove(last); steps.remove(last - 1); step = threeBack; step.distance += twoBack.distance; if (twoBack.elevation != null) { if (step.elevation == null) { step.elevation = twoBack.elevation; } else { for (P2<Double> d : twoBack.elevation) { step.elevation.add(new P2<Double>(d.getFirst() + step.distance, d.getSecond())); } } } } } } else { if (step.elevation != null) { List<P2<Double>> s = encodeElevationProfile(edge, distance); if (step.elevation != null && step.elevation.size() > 0) { step.elevation.addAll(s); } else { step.elevation = s; } } distance += edgeNarrative.getDistance(); } // increment the total length for this step step.distance += edgeNarrative.getDistance(); step.addAlerts(edgeNarrative.getNotes()); lastAngle = DirectionUtils.getLastAngle(geom); } return steps; }
/** * Generate an itinerary from a @{link GraphPath}. The algorithm here is to walk over each state * in the graph path, accumulating geometry, time, and length data from the incoming edge. When * the incoming edge and outgoing edge have different modes (or when a vehicle changes names due * to interlining) a new leg is generated. Street legs undergo an additional processing step to * generate turn-by-turn directions. * * @param path * @param showIntermediateStops whether intermediate stops are included in the generated itinerary * @return itinerary */ private Itinerary generateItinerary(GraphPath path, boolean showIntermediateStops) { Graph graph = path.getRoutingContext().graph; TransitIndexService transitIndex = graph.getService(TransitIndexService.class); Itinerary itinerary = makeEmptyItinerary(path); Set<Alert> postponedAlerts = null; Leg leg = null; CoordinateArrayListSequence coordinates = new CoordinateArrayListSequence(); double previousElevation = Double.MAX_VALUE; int startWalk = -1; int i = -1; boolean foldingElevatorLegIntoCycleLeg = false; PlanGenState pgstate = PlanGenState.START; String nextName = null; for (State state : path.states) { i += 1; Edge backEdge = state.getBackEdge(); if (backEdge == null) { continue; } // debug: push vehicle late status out to UI // if (backEdge instanceof PatternHop) { // TripTimes tt = state.getTripTimes(); // int hop = ((PatternHop)backEdge).stopIndex; // LOG.info("{} {}", tt.getTrip().toString(), hop); // if ( ! tt.isScheduled()) { // int delay = tt.getDepartureDelay(hop); // String d = "on time"; // if (Math.abs(delay) > 10) { // d = String.format("%2.1f min %s", delay / 60.0, // (delay < 0) ? "early" : "late"); // } // d = "Using real-time delay information: ".concat(d); // leg.addAlert(Alert.createSimpleAlerts(d)); // LOG.info(d); // } // else { // leg.addAlert(Alert.createSimpleAlerts("Using published timetables.")); // LOG.info("sched"); // } // } TraverseMode mode = state.getBackMode(); if (mode != null) { long dt = state.getAbsTimeDeltaSec(); if (mode == TraverseMode.BOARDING || mode == TraverseMode.ALIGHTING || mode == TraverseMode.STL) { itinerary.waitingTime += dt; } else if (mode.isOnStreetNonTransit()) { itinerary.walkDistance += backEdge.getDistance(); itinerary.walkTime += dt; } else if (mode.isTransit()) { itinerary.transitTime += dt; } } if (backEdge instanceof FreeEdge) { if (backEdge instanceof PreBoardEdge) { // Add boarding alerts to the next leg postponedAlerts = state.getBackAlerts(); } else if (backEdge instanceof PreAlightEdge) { // Add alighting alerts to the previous leg addNotesToLeg(itinerary.legs.get(itinerary.legs.size() - 1), state.getBackAlerts()); } continue; } if (backEdge instanceof EdgeWithElevation) { PackedCoordinateSequence profile = ((EdgeWithElevation) backEdge).getElevationProfile(); previousElevation = applyElevation(profile, itinerary, previousElevation); } switch (pgstate) { case START: if (mode == TraverseMode.WALK) { pgstate = PlanGenState.WALK; leg = makeLeg(itinerary, state); leg.from.orig = nextName; startWalk = i; } else if (mode == TraverseMode.BICYCLE) { pgstate = PlanGenState.BICYCLE; leg = makeLeg(itinerary, state); leg.from.orig = nextName; startWalk = i; } else if (mode == TraverseMode.CAR) { pgstate = PlanGenState.CAR; leg = makeLeg(itinerary, state); leg.from.orig = nextName; startWalk = i; } else if (mode == TraverseMode.BOARDING) { // this itinerary starts with transit pgstate = PlanGenState.PRETRANSIT; leg = makeLeg(itinerary, state); leg.from.orig = nextName; itinerary.transfers++; startWalk = -1; } else if (mode == TraverseMode.STL) { // this comes after an alight; do nothing } else if (mode == TraverseMode.TRANSFER) { // handle the whole thing in one step leg = makeLeg(itinerary, state); coordinates = new CoordinateArrayListSequence(); coordinates.add(state.getBackState().getVertex().getCoordinate()); coordinates.add(state.getVertex().getCoordinate()); finalizeLeg(leg, state, path.states, i, i, coordinates, itinerary); coordinates.clear(); } else { LOG.error("Unexpected state (in START): " + mode); } break; case WALK: if (leg == null) { leg = makeLeg(itinerary, state); } if (mode == TraverseMode.WALK) { // do nothing } else if (mode == TraverseMode.BICYCLE) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); startWalk = i; leg = makeLeg(itinerary, state); pgstate = PlanGenState.BICYCLE; } else if (mode == TraverseMode.STL) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); leg = null; pgstate = PlanGenState.PRETRANSIT; } else if (mode == TraverseMode.BOARDING) { // this only happens in case of a timed transfer. pgstate = PlanGenState.PRETRANSIT; finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); leg = makeLeg(itinerary, state); itinerary.transfers++; } else if (backEdge instanceof LegSwitchingEdge) { nextName = state.getBackState().getBackState().getBackState().getVertex().getName(); finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates, itinerary); leg = null; pgstate = PlanGenState.START; } else { LOG.error("Unexpected state (in WALK): " + mode); } break; case BICYCLE: if (leg == null) { leg = makeLeg(itinerary, state); } // If there are elevator edges that have mode == BICYCLE on both sides, they should // be folded into the bicycle leg. But ones with walk on one side or the other should // not if (state.getBackEdge() instanceof ElevatorBoardEdge) { int j = i + 1; // proceed forward from the current state until we find one that isn't on an // elevator, and check the traverse mode while (path.states.get(j).getBackEdge() instanceof ElevatorEdge) j++; // path.states[j] is not an elevator edge if (path.states.get(j).getBackMode() == TraverseMode.BICYCLE) foldingElevatorLegIntoCycleLeg = true; } if (foldingElevatorLegIntoCycleLeg) { if (state.getBackEdge() instanceof ElevatorEdge) { break; // from the case } else { foldingElevatorLegIntoCycleLeg = false; // do not break but allow it to be processed below (which will do nothing) } } if (mode == TraverseMode.BICYCLE) { // do nothing } else if (mode == TraverseMode.WALK) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); leg = makeLeg(itinerary, state); startWalk = i; pgstate = PlanGenState.WALK; } else if (mode == TraverseMode.STL) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); startWalk = i; leg = null; pgstate = PlanGenState.PRETRANSIT; } else if (backEdge instanceof LegSwitchingEdge) { finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates, itinerary); leg = null; pgstate = PlanGenState.START; } else { LOG.error("Unexpected state (in BICYCLE): " + mode); } break; case CAR: if (leg == null) { leg = makeLeg(itinerary, state); } if (mode == TraverseMode.CAR) { // do nothing } else if (mode == TraverseMode.STL) { finalizeLeg(leg, state, path.states, startWalk, i, coordinates, itinerary); leg = null; pgstate = PlanGenState.PRETRANSIT; } else if (backEdge instanceof LegSwitchingEdge) { finalizeLeg(leg, state, path.states, startWalk, i - 1, coordinates, itinerary); leg = null; pgstate = PlanGenState.START; } else { LOG.error("Unexpected state (in CAR): " + mode); } break; case PRETRANSIT: if (mode == TraverseMode.BOARDING) { if (leg != null) { LOG.error("leg unexpectedly not null (boarding loop)"); } else { leg = makeLeg(itinerary, state); leg.from.stopIndex = ((OnBoardForwardEdge) backEdge).getStopIndex(); leg.stop = new ArrayList<Place>(); itinerary.transfers++; leg.boardRule = (String) state.getExtension("boardAlightRule"); } } else if (backEdge instanceof HopEdge) { pgstate = PlanGenState.TRANSIT; fixupTransitLeg(leg, state, transitIndex); leg.stop = new ArrayList<Place>(); } else { LOG.error("Unexpected state (in PRETRANSIT): " + mode); } break; case TRANSIT: String route = backEdge.getName(); if (mode == TraverseMode.ALIGHTING) { if (showIntermediateStops && leg.stop != null && leg.stop.size() > 0) { if (leg.stop.isEmpty()) { leg.stop = null; } } leg.alightRule = (String) state.getExtension("boardAlightRule"); finalizeLeg(leg, state, null, -1, -1, coordinates, itinerary); leg = null; pgstate = PlanGenState.START; } else if (mode.toString().equals(leg.mode)) { // no mode change, handle intermediate stops if (showIntermediateStops) { /* * any further transit edge, add "from" vertex to intermediate stops */ if (!(backEdge instanceof DwellEdge)) { Place stop = makePlace( state.getBackState(), state.getBackState().getVertex().getName(), true); leg.stop.add(stop); } else if (leg.stop.size() > 0) { leg.stop.get(leg.stop.size() - 1).departure = makeCalendar(state); } } if (!route.equals(leg.route)) { // interline dwell finalizeLeg(leg, state, null, -1, -1, coordinates, itinerary); leg = makeLeg(itinerary, state); leg.stop = new ArrayList<Place>(); fixupTransitLeg(leg, state, transitIndex); leg.startTime = makeCalendar(state); leg.interlineWithPreviousLeg = true; } } else { LOG.error("Unexpected state (in TRANSIT): " + mode); } break; } if (leg != null) { leg.distance += backEdge.getDistance(); Geometry edgeGeometry = backEdge.getGeometry(); if (edgeGeometry != null) { Coordinate[] edgeCoordinates = edgeGeometry.getCoordinates(); if (coordinates.size() > 0 && coordinates.getCoordinate(coordinates.size() - 1).equals(edgeCoordinates[0])) { coordinates.extend(edgeCoordinates, 1); } else { coordinates.extend(edgeCoordinates); } } if (postponedAlerts != null) { addNotesToLeg(leg, postponedAlerts); postponedAlerts = null; } addNotesToLeg(leg, state.getBackAlerts()); } } /* end loop over graphPath edge list */ if (leg != null) { finalizeLeg(leg, path.states.getLast(), path.states, startWalk, i, coordinates, itinerary); } itinerary.removeBogusLegs(); itinerary.fixupDates(graph.getService(CalendarServiceData.class)); if (itinerary.legs.size() == 0) throw new TrivialPathException(); return itinerary; }
@Override public void buildGraph(Graph graph, HashMap<Class<?>, Object> extra) { final Parser parser[] = new Parser[] {new Parser()}; GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory(); EarliestArrivalSPTService earliestArrivalSPTService = new EarliestArrivalSPTService(); earliestArrivalSPTService.maxDuration = (maxDuration); for (TransitStop ts : Iterables.filter(graph.getVertices(), TransitStop.class)) { // Only link street linkable stops if (!ts.isStreetLinkable()) continue; LOG.trace("linking stop '{}' {}", ts.getStop(), ts); // Determine the set of pathway/transfer destinations Set<TransitStop> pathwayDestinations = new HashSet<TransitStop>(); for (Edge e : ts.getOutgoing()) { if (e instanceof PathwayEdge || e instanceof SimpleTransfer) { if (e.getToVertex() instanceof TransitStop) { TransitStop to = (TransitStop) e.getToVertex(); pathwayDestinations.add(to); } } } int n = 0; RoutingRequest routingRequest = new RoutingRequest(TraverseMode.WALK); routingRequest.clampInitialWait = (0L); routingRequest.setRoutingContext(graph, ts, null); routingRequest.rctx.pathParsers = parser; ShortestPathTree spt = earliestArrivalSPTService.getShortestPathTree(routingRequest); if (spt != null) { for (State state : spt.getAllStates()) { Vertex vertex = state.getVertex(); if (ts == vertex) continue; if (vertex instanceof TransitStop) { TransitStop other = (TransitStop) vertex; if (!other.isStreetLinkable()) continue; if (pathwayDestinations.contains(other)) { LOG.trace("Skipping '{}', {}, already connected.", other.getStop(), other); continue; } double distance = 0.0; GraphPath graphPath = new GraphPath(state, false); CoordinateArrayListSequence coordinates = new CoordinateArrayListSequence(); for (Edge edge : graphPath.edges) { if (edge instanceof StreetEdge) { LineString geometry = edge.getGeometry(); if (geometry != null) { if (coordinates.size() == 0) { coordinates.extend(geometry.getCoordinates()); } else { coordinates.extend(geometry.getCoordinates(), 1); } } distance += edge.getDistance(); } } if (coordinates.size() < 2) { // Otherwise the walk step generator breaks. ArrayList<Coordinate> coordinateList = new ArrayList<Coordinate>(2); coordinateList.add(graphPath.states.get(1).getVertex().getCoordinate()); State lastState = graphPath.states.getLast().getBackState(); coordinateList.add(lastState.getVertex().getCoordinate()); coordinates = new CoordinateArrayListSequence(coordinateList); } LineString geometry = geometryFactory.createLineString( new PackedCoordinateSequence.Double(coordinates.toCoordinateArray())); LOG.trace(" to stop: '{}' {} ({}m) [{}]", other.getStop(), other, distance, geometry); new SimpleTransfer(ts, other, distance, geometry); n++; } } } LOG.trace("linked to {} others.", n); if (n == 0) { LOG.warn(graph.addBuilderAnnotation(new StopNotLinkedForTransfers(ts))); } } }