
org.opentripplanner.profile.RaptorWorkerData Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otp Show documentation
Show all versions of otp Show documentation
The OpenTripPlanner multimodal journey planning system
package org.opentripplanner.profile;
import com.beust.jcommander.internal.Lists;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import gnu.trove.iterator.TIntIntIterator;
import gnu.trove.iterator.TIntIterator;
import gnu.trove.iterator.TObjectIntIterator;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.map.TIntIntMap;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TIntIntHashMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import gnu.trove.map.hash.TObjectIntHashMap;
import org.joda.time.LocalDate;
import org.onebusaway.gtfs.model.Stop;
import org.opentripplanner.analyst.SampleSet;
import org.opentripplanner.analyst.cluster.TaskStatistics;
import org.opentripplanner.analyst.scenario.AddTripPattern;
import org.opentripplanner.analyst.scenario.ConvertToFrequency;
import org.opentripplanner.analyst.scenario.Scenario;
import org.opentripplanner.analyst.scenario.TransferRule;
import org.opentripplanner.analyst.scenario.TripPatternFilter;
import org.opentripplanner.common.model.GenericLocation;
import org.opentripplanner.routing.algorithm.AStar;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.edgetype.SimpleTransfer;
import org.opentripplanner.routing.edgetype.TripPattern;
import org.opentripplanner.routing.error.VertexNotFoundException;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.spt.DominanceFunction;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.routing.trippattern.FrequencyEntry;
import org.opentripplanner.routing.trippattern.TripTimes;
import org.opentripplanner.routing.vertextype.TransitStop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class RaptorWorkerData implements Serializable {
public static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerData.class);
/** we use empty int arrays for various things, e.g. transfers from isolated stops. They're immutable so only use one */
public static final int[] EMPTY_INT_ARRAY = new int[0];
public final int nStops;
public final int nPatterns;
/** The number of targets (vertices or samples) */
public final int nTargets;
/** For every stop, one pair of ints (targetStopIndex, distanceMeters) for each transfer out of that stop. This uses 0-based stop indices that are specific to RaptorData */
public final List transfersForStop = new ArrayList<>();
/** A list of pattern indexes passing through each stop, again using Raptor indices. */
public final List patternsForStop = new ArrayList<>();
/** For each pattern, a 2D array of stoptimes for each trip on the pattern. */
public List timetablesForPattern = new ArrayList<>();
/** does this RaptorData have any scheduled trips? */
public boolean hasSchedules = false;
/** does this RaptorData have any frequency trips? */
public boolean hasFrequencies = false;
/**
* Map from stops that do not exist in the graph but only for the duration of this search to their stop indices in the RAPTOR data.
*/
public TObjectIntMap addedStops = new TObjectIntHashMap<>();
/** Some stops may have special transfer rules. They are stored here. */
public TIntObjectMap> transferRules = new TIntObjectHashMap<>();
/** The transfer rules to be applied in the absence of another transfer rule */
public List baseTransferRules = new ArrayList<>();
/** The boarding assumption used for initial vehicle boarding, and for when there is no transfer rule defined */
public RaptorWorkerTimetable.BoardingAssumption boardingAssumption;
/**
* For each stop, one pair of ints (targetID, distanceMeters) for each destination near that stop.
* For generic TimeSurfaces these are street intersections. They could be anything though since the worker doesn't
* care what the IDs stand for. For example, they could be point indexes in a pointset.
*/
public final List targetsForStop = new ArrayList<>();
/** The 0-based RAPTOR indices of each stop from their vertex IDs */
public transient final TIntIntMap indexForStop;
/** Optional debug data: the name of each stop. */
public transient final List stopNames = new ArrayList<>();
public transient final List patternNames = new ArrayList<>();
/** Create RaptorWorkerData for the given window and graph */
public RaptorWorkerData (Graph graph, TimeWindow window, ProfileRequest request, TaskStatistics ts) {
this(graph, window, request, null, ts);
}
public RaptorWorkerData (Graph graph, TimeWindow window, ProfileRequest request, SampleSet sampleSet) {
this(graph, window, request, sampleSet, new TaskStatistics());
}
public RaptorWorkerData (Graph graph, TimeWindow window, ProfileRequest request) {
this (graph, window, request, null, new TaskStatistics());
}
/** Create RaptorWorkerData to be used to build ResultSets directly without creating an intermediate SampleSet */
public RaptorWorkerData (Graph graph, TimeWindow window, ProfileRequest req, SampleSet sampleSet, TaskStatistics ts) {
Scenario scenario = req.scenario;
int totalPatterns = graph.index.patternForId.size();
int totalStops = graph.index.stopForId.size();
timetablesForPattern = new ArrayList(totalPatterns);
List patternForIndex = Lists.newArrayList(totalPatterns);
TObjectIntMap indexForPattern = new TObjectIntHashMap<>(totalPatterns, 0.75f, -1);
indexForStop = new TIntIntHashMap(totalStops, 0.75f, Integer.MIN_VALUE, -1);
TIntList stopForIndex = new TIntArrayList(totalStops, Integer.MIN_VALUE);
this.boardingAssumption = req.boardingAssumption;
ts.patternCount = 0;
ts.frequencyEntryCount = 0;
ts.frequencyTripCount = 0;
ts.scheduledTripCount = 0;
// first apply any filters that need to be applied to the entire schedule at once
Collection graphPatterns = graph.index.patternForId.values();
// convert scheduled trips to freuquencies as needed
if (scenario != null && scenario.modifications != null) {
Collection frequencies = scenario.modifications.stream()
.filter(m -> m instanceof ConvertToFrequency)
.map(m -> (ConvertToFrequency) m)
.collect(Collectors.toList());
if (!frequencies.isEmpty()) {
// apply the operations
List scheduled = graphPatterns.stream()
.flatMap(p -> p.scheduledTimetable.tripTimes.stream())
.collect(Collectors.toList());
List frequencyEntries = graphPatterns.stream()
.filter(p -> p.getSingleFrequencyEntry() != null)
.flatMap(p -> p.scheduledTimetable.frequencyEntries.stream())
.collect(Collectors.toList());
for (ConvertToFrequency c : frequencies) {
c.apply(frequencyEntries, scheduled, graph, window.servicesRunning, req.boardingAssumption);
scheduled = c.scheduledTrips;
frequencyEntries = c.frequencyEntries;
}
// aggregate the modified trips back into patterns
// this is so that the patterns have object references to the relevant schedules and frequency entries
// group by the original pattern - each entry is still associated with a trip
Multimap newScheduledPatterns = HashMultimap.create();
for (TripTimes tt : scheduled) {
newScheduledPatterns.put(graph.index.patternForTrip.get(tt.trip), tt);
}
Multimap newFreqEntries = HashMultimap.create();
for (FrequencyEntry fe : frequencyEntries) {
newFreqEntries.put(graph.index.patternForTrip.get(fe.tripTimes.trip), fe);
}
// filter to just the patterns that are relevant, then replace each pattern with a new pattern
// representing its modified trips
graphPatterns = graphPatterns.stream()
.filter(p -> newScheduledPatterns.containsKey(p) || newFreqEntries.containsKey(p))
.map(p -> {
TripPattern newp = new TripPattern(p.route, p.stopPattern);
newScheduledPatterns.get(p).stream().forEach(newp.scheduledTimetable::addTripTimes);
newFreqEntries.get(p).stream().forEach(newp.scheduledTimetable::addFrequencyEntry);
return newp;
})
.collect(Collectors.toList());
}
}
/* Make timetables for active trip patterns and record the stops each active pattern uses. */
for (TripPattern originalPattern : graphPatterns) {
Collection patterns = Arrays.asList(originalPattern);
// apply filters. note that a filter can create multiple trip patterns from a single trip pattern
// so we need to make sure we handle all of them
if (scenario != null && scenario.modifications != null) {
for (TripPatternFilter filter : Iterables
.filter(scenario.modifications, TripPatternFilter.class)) {
Collection modifiedPatterns = Lists.newArrayList();
for (TripPattern pattern : patterns) {
Collection result = filter.apply(pattern);
if (result != null)
modifiedPatterns.addAll(result);
}
// this is the result of this filter for all trip patterns
patterns = modifiedPatterns;
}
}
for (TripPattern pattern : patterns) {
RaptorWorkerTimetable timetable = RaptorWorkerTimetable
.forPattern(graph, pattern, window, scenario, ts);
if (timetable == null) {
// Pattern is not running during the time window
continue;
}
timetable.dataIndex = timetablesForPattern.size();
timetable.raptorData = this;
timetablesForPattern.add(timetable);
if (timetable.hasFrequencyTrips())
hasFrequencies = true;
if (timetable.hasScheduledTrips())
hasSchedules = true;
// Temporary bidirectional mapping between 0-based indexes and patterns
indexForPattern.put(pattern, patternForIndex.size());
patternForIndex.add(pattern);
patternNames.add(pattern.code);
TIntList stopIndexesForPattern = new TIntArrayList();
for (Stop stop : pattern.getStops()) {
int vidx = graph.index.stopVertexForStop.get(stop).getIndex();
int stopIndex = indexForStop.get(vidx);
if (stopIndex == -1) {
stopIndex = indexForStop.size();
indexForStop.put(vidx, stopIndex);
stopForIndex.add(vidx);
stopNames.add(stop.getName());
}
stopIndexesForPattern.add(stopIndex);
}
timetable.stopIndices = stopIndexesForPattern.toArray();
}
}
// and do roughly the same thing for added patterns
if (scenario != null && scenario.modifications != null) {
for (AddTripPattern atp : Iterables.filter(scenario.modifications, AddTripPattern.class)) {
// note that added trip patterns are not affected by modifications
RaptorWorkerTimetable timetable = RaptorWorkerTimetable.forAddedPattern(atp, window, ts);
if (timetable == null)
continue;
timetable.dataIndex = timetablesForPattern.size();
timetablesForPattern.add(timetable);
timetable.raptorData = this;
if (timetable.hasFrequencyTrips())
this.hasFrequencies = true;
if (timetable.hasScheduledTrips())
this.hasSchedules = true;
// TODO: patternForIndex, indexForPattern
patternNames.add(atp.name);
// create the stops for the pattern, and collect the temporary stops
for (AddTripPattern.TemporaryStop t : atp.temporaryStops) {
// the index of this stop in the worker data
int stopIndex = stopForIndex.size();
addedStops.put(t, stopIndex);
indexForStop.put(t.index, stopIndex);
stopForIndex.add(t.index);
}
timetable.stopIndices = Arrays.asList(atp.temporaryStops).stream()
.mapToInt(t -> indexForStop.get(t.index))
.toArray();
}
}
// for each of the added stops, compute transfers and a stop tree cache
// Indexed by vertex ID, not RAPTOR index
TIntObjectMap temporaryStopTreeCache = new TIntObjectHashMap<>();;
// Holds transfer both from _and_ to temporary stops
// Indexed by and contains vertex ID, not RAPTOR index
TIntObjectMap temporaryTransfers = new TIntObjectHashMap<>();
AStar astar = new AStar();
for (AddTripPattern.TemporaryStop t : addedStops.keySet()) {
// forward search: stop tree cache and transfers out
RoutingRequest rr = new RoutingRequest(TraverseMode.WALK);
rr.batch = true;
rr.from = rr.to = new GenericLocation(t.lat, t.lon);
try {
rr.setRoutingContext(graph);
} catch (VertexNotFoundException vnfe) {
LOG.warn("Temporary stop at {}, {} not connected to graph", t.lat, t.lon);
temporaryStopTreeCache.put(t.index, new int[0]);
temporaryTransfers.put(t.index, new TIntIntHashMap());
continue;
}
// We use walk-distance limiting and a least-walk dominance function in order to be consistent with egress walking
// which is implemented this way because walk times can change when walk speed changes. Also, walk times are floating
// point and can change slightly when streets are split. Street lengths are internally fixed-point ints, which do not
// suffer from roundoff. Great care is taken when splitting to preserve sums.
// When cycling, this is not an issue; we already have an explicitly asymmetrical search (cycling at the origin, walking at the destination),
// so we need not preserve symmetry.
rr.maxWalkDistance = 2000;
rr.softWalkLimiting = false;
rr.dominanceFunction = new DominanceFunction.LeastWalk();
rr.longDistance = true;
rr.numItineraries = 1;
ShortestPathTree spt = astar.getShortestPathTree(rr, 5);
// create the stop tree cache
// TODO duplicated code from stopTreeCache
// Copy vertex indices and distances into a flattened 2D array
int[] distances = new int[spt.getVertexCount() * 2];
int i = 0;
for (Vertex vertex : spt.getVertices()) {
State state = spt.getState(vertex);
if (state == null)
continue;
distances[i++] = vertex.getIndex();
distances[i++] = (int) state.getWalkDistance();
}
temporaryStopTreeCache.put(t.index, distances);
// NB using walk speed of 1 m/s here since we want meters, not seconds
// walk speed is applied during search.
TIntIntMap transfersFromStop = findStopsNear(spt, graph, false, 1f);
// convert it to use indices in the graph not in the worker data
TIntIntMap transfersFromStopWithGraphIndices = new TIntIntHashMap();
for (TIntIntIterator trIt = transfersFromStop.iterator(); trIt.hasNext();) {
trIt.advance();
transfersFromStopWithGraphIndices.put(stopForIndex.get(trIt.key()), trIt.value());
}
temporaryTransfers.put(t.index, transfersFromStopWithGraphIndices);
rr.cleanup();
// now compute transfers to the stop by doing a batch arrive-by search
// necessary to hand-clear routing context so that it is not cached
rr.rctx = null;
rr.setArriveBy(true);
// reset routing context because temporary edges face the wrong way
rr.setRoutingContext(graph);
spt = astar.getShortestPathTree(rr, 5);
// NB using walk speed of 1 m/s here since we want meters, not seconds
// walk speed is applied during search
TIntIntMap transfersToStop = findStopsNear(spt, graph, false, 1f);
// turn the array around; these are transfers from elsewhere to this stop.
for (TIntIntIterator it = transfersToStop.iterator(); it.hasNext(); ) {
it.advance();
// index of vertex in graph
int graphIndex = stopForIndex.get(it.key());
// for absolute determinism we want to ensure that transfers between two added stops are always computed
// in the forward direction. Otherwise they would be computed twice, once forwards
// and once reverse, and which one was saved would depend on iteration order.
// OTP should be symmetrical but let's not write code that depends on that property
Vertex tstop = graph.getVertexById(graphIndex);
if (tstop == null || !TransitStop.class.isInstance(tstop))
// this is not a permanent stop - there is no transit stop vertex associated with it
continue;
if (!temporaryTransfers.containsKey(graphIndex))
temporaryTransfers.put(graphIndex, new TIntIntHashMap());
// temporary transfers are stored by index in the graph not the worker data
temporaryTransfers.get(graphIndex).put(t.index, it.value());
}
}
// create the mapping from stops to patterns
TIntObjectMap patternsForStopList = new TIntObjectHashMap<>();
for (int pattern = 0; pattern < timetablesForPattern.size(); pattern++) {
for (int stop : timetablesForPattern.get(pattern).stopIndices) {
if (!patternsForStopList.containsKey(stop))
patternsForStopList.put(stop, new TIntArrayList());
patternsForStopList.get(stop).add(pattern);
}
}
for (int stop = 0; patternsForStopList.containsKey(stop); stop++) {
patternsForStop.add(patternsForStopList.get(stop).toArray());
}
/** Record transfers between all used stops. */
for (TIntIterator it = stopForIndex.iterator(); it.hasNext();) {
int stop = it.next();
TIntList transfers = new TIntArrayList();
TransitStop tstop = (TransitStop) graph.getVertexById(stop);
if (tstop != null) {
// not an added stop, look for transfers in the graph
for (SimpleTransfer simpleTransfer : Iterables
.filter(tstop.getOutgoing(), SimpleTransfer.class)) {
int targetStopIndex = indexForStop.get(simpleTransfer.getToVertex().getIndex());
if (targetStopIndex != -1) {
transfers.add(targetStopIndex);
transfers.add((int) (simpleTransfer.getDistance()));
}
}
}
// check for any transfers to/from added stops
if (temporaryTransfers.containsKey(stop)) {
for (TIntIntIterator tranIt = temporaryTransfers.get(stop).iterator(); tranIt.hasNext();) {
tranIt.advance();
// stop index
transfers.add(indexForStop.get(tranIt.key()));
// distance
transfers.add(tranIt.value());
}
}
if (!transfers.isEmpty())
transfersForStop.add(transfers.toArray());
else
transfersForStop.add(EMPTY_INT_ARRAY);
}
long stcStart = System.currentTimeMillis();
StopTreeCache stc = graph.index.getStopTreeCache();
ts.stopTreeCaching = (int) (System.currentTimeMillis() - stcStart);
// Record times to nearby intersections for all used stops.
// We use times rather than distances to avoid a costly floating-point divide during propagation
if (sampleSet == null) {
int maxWalkDistance = (int) (req.maxWalkTime * 60 * req.walkSpeed);
for (TIntIterator stopIt = stopForIndex.iterator(); stopIt.hasNext();) {
int stop = stopIt.next();
// permanent stop
Vertex tstop = graph.getVertexById(stop);
boolean isPermanentStop = tstop != null && TransitStop.class.isInstance(tstop);
// convert distance to time
int[] distancesForStop = isPermanentStop ? stc.distancesForStop.get(tstop) : temporaryStopTreeCache.get(stop);
TIntList timesForStop = new TIntArrayList();
for (int i = 0; i < distancesForStop.length; i += 2) {
int vidx = distancesForStop[i];
int dist = distancesForStop[i + 1];
// only add if it's less than the max walk distance
if (dist <= maxWalkDistance) {
timesForStop.add(vidx);
// convert meters to seconds by dividing by meters / second
timesForStop.add((int) (dist / req.walkSpeed));
}
}
targetsForStop.add(timesForStop.toArray());
}
// TODO memory leak when many graphs have been built
nTargets = Vertex.getMaxIndex();
}
// Record distances to each sample
// We need to propagate all the way to samples when doing repeated RAPTOR.
// Consider the situation where there are two parallel transit lines on
// 5th Street and 6th Street, and you live on A Street halfway between 5th and 6th.
// Both lines run at 30 minute headways, but they are exactly out of phase, and for the
// purposes of this conversation both go the same place with the same in-vehicle travel time.
// Thus, even though the lines run every 30 minutes, you never experience a wait of more than
// 15 minutes because you are clever when you choose which line to take. The worst case at each
// transit stop is much worse than the worst case at samples. While unlikely, it is possible that
// a sample would be able to reach these two stops within the walk limit, but that the two
// intersections it is connected to cannot reach both.
else {
TIntObjectMap> sampleIndex = new TIntObjectHashMap>();
for (int i = 0; i < sampleSet.pset.capacity; i++) {
if (sampleSet.v0s[i] == null)
continue;
// VERTEX 0
int v0 = sampleSet.v0s[i].getIndex();
List list;
if (sampleIndex.containsKey(v0))
list = sampleIndex.get(v0);
else {
list = new ArrayList();
sampleIndex.put(v0, list);
}
list.add(new HalfSample(i, sampleSet.d0s[i]));
// VERTEX 1
if (sampleSet.v1s[i] != null) {
int v1 = sampleSet.v1s[i].getIndex();
if (sampleIndex.containsKey(v1))
list = sampleIndex.get(v1);
else {
list = new ArrayList();
sampleIndex.put(v1, list);
}
list.add(new HalfSample(i, sampleSet.d1s[i]));
}
}
// Iterate over all stops, saving the least distance to each reachable sample from
// from each transit stop.
// We first make a map stops to samples, so we can ensure we save only the shortest
// distance from a transit stop to a sample. Most transit stops can reach a given sample two
// ways because they can reach both of the vertices the sample is connected to.
TIntIntMap out = new TIntIntHashMap();
for (TIntIterator stopIt = stopForIndex.iterator(); stopIt.hasNext();) {
out.clear();
int stop = stopIt.next();
int[] distancesForStop;
Vertex tstop = graph.getVertexById(stop);
if (tstop != null && TransitStop.class.isInstance(tstop))
// permanent stop
distancesForStop = stc.distancesForStop.get(tstop);
else
// temporary stop
distancesForStop = temporaryStopTreeCache.get(stop);
STREET: for (int i = 0; i < distancesForStop.length; i++) {
int v = distancesForStop[i++];
int d = distancesForStop[i];
if (!sampleIndex.containsKey(v))
continue STREET;
// Build the map
SAMPLE: for (HalfSample s : sampleIndex.get(v)) {
int distance = Math.round(d + s.distance);
if (distance > stc.maxWalkMeters)
continue;
// only save it if there isn't another shorter walking path that we've already encountered.
int time = (int) (distance / req.walkSpeed);
if (!out.containsKey(s.index) || out.get(s.index) > time)
out.put(s.index, time);
}
}
// Save a flat array of (target, distance) pairs keyed on this transit stops's index in the RAPTOR table.
int[] flat = new int[out.size() * 2];
int pos = 0;
for (TIntIntIterator it = out.iterator(); it.hasNext();) {
it.advance();
flat[pos++] = it.key();
flat[pos++] = it.value();
}
targetsForStop.add(flat);
}
nTargets = sampleSet.pset.capacity;
}
// store transfer rules by stop
if (scenario != null && scenario.modifications != null) {
for (TransferRule tr : Iterables.filter(scenario.modifications, TransferRule.class)) {
if (tr.stop == null) {
this.baseTransferRules.add(tr);
}
else {
Vertex tstop = graph.getVertex(tr.stop);
if (tstop == null || !TransitStop.class.isInstance(tstop))
LOG.warn("Transit stop not found for transfer rule with stop label {}", tr.stop);
if (!indexForStop.containsKey(tstop.getIndex()))
// this stop is not used in this time window
continue;
int index = indexForStop.get(tstop.getIndex());
if (!transferRules.containsKey(index))
transferRules.put(index, new ArrayList<>());
transferRules.get(index).add(tr);
}
}
}
ts.stopCount = nStops = stopForIndex.size();
ts.patternCount = nPatterns = timetablesForPattern.size();
ts.targetCount = nTargets;
}
/** find stops from a given SPT, including temporary stops. If useTimes is true, use times from the SPT, otherwise use distances */
public TIntIntMap findStopsNear (ShortestPathTree spt, Graph graph, boolean useTimes, float walkSpeed) {
TIntIntMap accessTimes = new TIntIntHashMap();
for (TransitStop tstop : graph.index.stopVertexForStop.values()) {
State s = spt.getState(tstop);
if (s != null) {
// note that we calculate the time based on the walk speed here rather than
// based on the time. this matches what we do in the stop tree cache.
int stopIndex = indexForStop.get(tstop.getIndex());
if (stopIndex != -1) {
if (useTimes)
accessTimes.put(stopIndex, (int) s.getElapsedTimeSeconds());
else
accessTimes.put(stopIndex, (int) (s.getWalkDistance() / walkSpeed));
}
}
}
// and handle the additional stops
for (TObjectIntIterator it = addedStops.iterator(); it.hasNext();) {
it.advance();
AddTripPattern.TemporaryStop tstop = it.key();
if (tstop.sample == null) {
continue;
}
double dist = Double.POSITIVE_INFINITY;
if (tstop.sample.v0 != null) {
State s0 = spt.getState(tstop.sample.v0);
if (s0 != null) {
dist = s0.getWalkDistance() + tstop.sample.d0;
}
}
if (tstop.sample.v1 != null) {
State s1 = spt.getState(tstop.sample.v1);
if (s1 != null) {
double d1 = s1.getWalkDistance() + tstop.sample.d1;
dist = Double.isInfinite(dist) ? d1 : Math.min(d1, dist);
}
}
if (Double.isInfinite(dist))
continue;
// NB using the index in the worker data not the index in the graph!
accessTimes.put(it.value(), (int) (dist / walkSpeed));
}
return accessTimes;
}
/** half a sample: the index in the sample set, and the distance to one of the vertices */
private static class HalfSample {
public HalfSample(int index, float distance) {
this.index = index;
this.distance = distance;
}
int index;
float distance;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy