diff --git a/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/onlinemm/OnlineExtendedState.java b/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/onlinemm/OnlineExtendedState.java index f3f5fe7..33ce212 100644 --- a/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/onlinemm/OnlineExtendedState.java +++ b/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/onlinemm/OnlineExtendedState.java @@ -124,5 +124,6 @@ public OnlineExtendedState getParent() { public void setParent(OnlineExtendedState parent) { this.parent = parent; } + } diff --git a/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/onlinemm/OnlineSequenceState.java b/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/onlinemm/OnlineSequenceState.java new file mode 100644 index 0000000..a702e5d --- /dev/null +++ b/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/onlinemm/OnlineSequenceState.java @@ -0,0 +1,24 @@ +package org.urbcomp.cupid.db.algorithm.mapmatch.onlinemm; + +import org.urbcomp.cupid.db.algorithm.mapmatch.tihmm.inner.SequenceState; +import org.urbcomp.cupid.db.model.point.CandidatePoint; +import org.urbcomp.cupid.db.model.point.GPSPoint; + +public class OnlineSequenceState extends SequenceState { + + public int time; + /** + * 构造函数 + * + * @param state 原始point的candidate + * @param observation 原始point + */ + public OnlineSequenceState(CandidatePoint state, GPSPoint observation) { + super(state, observation); + } + + public OnlineSequenceState(CandidatePoint state, GPSPoint observation, int time) { + super(state, observation); + this.time = time; + } +} diff --git a/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/onlinemm/OnlineViterbi.java b/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/onlinemm/OnlineViterbi.java index a8b4e77..3ab5e5c 100644 --- a/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/onlinemm/OnlineViterbi.java +++ b/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/onlinemm/OnlineViterbi.java @@ -2,7 +2,6 @@ import org.urbcomp.cupid.db.algorithm.mapmatch.tihmm.inner.ExtendedState; import org.urbcomp.cupid.db.algorithm.mapmatch.tihmm.inner.ForwardStepResult; -import org.urbcomp.cupid.db.algorithm.mapmatch.tihmm.inner.SequenceState; import org.urbcomp.cupid.db.algorithm.mapmatch.tihmm.inner.TiViterbi; import org.urbcomp.cupid.db.model.point.CandidatePoint; import org.urbcomp.cupid.db.model.point.GPSPoint; @@ -21,7 +20,7 @@ public class OnlineViterbi extends TiViterbi { // Collection of Viterbi states private final LinkedList stateList; // Local solutions for the current sequence - private final List sequenceStates; + private final List sequenceStates; // Convergence status public boolean isConverge; @@ -36,7 +35,9 @@ public class OnlineViterbi extends TiViterbi { // Time diff between root and previous root public int timeDelta; // Starting insert position for global sequence after algorithm interruption - public int insertPosition; + public int validStartInsertIndex; + // Boundary size of convergence from the current time step + public int windowSize; /** * Constructs an OnlineViterbi instance with an initial insert position of zero. @@ -49,16 +50,17 @@ public OnlineViterbi() { currentRoot = null; previousRoot = null; timeDelta = 0; - insertPosition = 0; + validStartInsertIndex = 0; + windowSize = 0; } /** * Constructs an OnlineViterbi instance with a specified insert position. * For recording insert position of global sequence when algorithm breaks. * - * @param insertPosition The starting insert position for the global sequence. + * @param validStartInsertIndex The starting insert position for the global sequence. */ - public OnlineViterbi(int insertPosition) { + public OnlineViterbi(int validStartInsertIndex) { stateList = new LinkedList<>(); sequenceStates = new ArrayList<>(); isConverge = false; @@ -66,17 +68,18 @@ public OnlineViterbi(int insertPosition) { currentRoot = null; previousRoot = null; timeDelta = 0; - this.insertPosition = insertPosition; + this.validStartInsertIndex = validStartInsertIndex; + windowSize = 0; } /** * Constructs an OnlineViterbi instance with a specified insert position and broken state indicator. * This constructor is used for recording the insert position of the global sequence when the algorithm breaks. * - * @param insertPosition The starting insert position for the global sequence. - * @param isBrokenBefore A boolean indicating whether the algorithm was broken before this instance was created. + * @param validStartInsertIndex The starting insert position for the global sequence. + * @param isBrokenBefore A boolean indicating whether the algorithm was broken before this instance was created. */ - public OnlineViterbi(int insertPosition, boolean isBrokenBefore) { + public OnlineViterbi(int validStartInsertIndex, boolean isBrokenBefore) { stateList = new LinkedList<>(); sequenceStates = new ArrayList<>(); isConverge = false; @@ -84,9 +87,33 @@ public OnlineViterbi(int insertPosition, boolean isBrokenBefore) { currentRoot = null; previousRoot = null; timeDelta = 0; - this.insertPosition = insertPosition; + this.validStartInsertIndex = validStartInsertIndex; + windowSize = 0; } + public OnlineViterbi(int validStartInsertIndex, int windowSize) { + stateList = new LinkedList<>(); + sequenceStates = new ArrayList<>(); + isConverge = false; + this.isBrokenBefore = false; + currentRoot = null; + previousRoot = null; + timeDelta = 0; + this.validStartInsertIndex = validStartInsertIndex; + this.windowSize = windowSize; + } + + public OnlineViterbi(int validStartInsertIndex, int windowSize, boolean isBrokenBefore) { + stateList = new LinkedList<>(); + sequenceStates = new ArrayList<>(); + isConverge = false; + this.isBrokenBefore = isBrokenBefore; + currentRoot = null; + previousRoot = null; + timeDelta = 0; + this.validStartInsertIndex = validStartInsertIndex; + this.windowSize = windowSize; + } /** * Processes the next step in the Viterbi algorithm using the given observation @@ -95,10 +122,10 @@ public OnlineViterbi(int insertPosition, boolean isBrokenBefore) { * @param observation The current GPS observation. * @param candidates List of candidate points for the current step. * @param emissionLogProbabilities Map of emission log probabilities for candidate points. - * @param transitionLogProbabilities Map of transition log probabilities between candidate points. + * @param transitionLogProbabilities Map of transition log probabilities between candidate points. * @param time The current time step. * @throws IllegalStateException if the method is called without initializing - * with an observation or after an HMM break. + * with an observation or after an HMM break. */ public void nextStep( GPSPoint observation, @@ -123,7 +150,6 @@ public void nextStep( isBroken = hmmBreak(forwardStepResult.getNewMessage()); if (isBroken) { - System.out.println("viterbi stops when executing [FORWARD STEP]"); return; } @@ -141,7 +167,7 @@ public void nextStep( * @param curCandidates List of current candidate points. * @param message Map of state probabilities. * @param emissionLogProbabilities Map of emission probabilities for each candidate. - * @param transitionLogProbabilities Map of transition probabilities between candidates. + * @param transitionLogProbabilities Map of transition probabilities between candidates. * @param time The current time step. * @return Results after forward extension, including updated state probabilities and states. */ @@ -392,23 +418,28 @@ private boolean searchForNewRoot() { * current root state to the previous root state. */ private void traceback() { + int currentTime = currentRoot.getTime(); + System.out.println("Local path found!"); - List interLocalPath = new ArrayList<>(); - interLocalPath.add(new SequenceState(currentRoot.getState(), currentRoot.getObservation())); + System.out.println("Current root time: " + currentTime); + + List interLocalPath = new ArrayList<>(); + interLocalPath.add(new org.urbcomp.cupid.db.algorithm.mapmatch.onlinemm.OnlineSequenceState(currentRoot.getState(), currentRoot.getObservation(), currentTime--)); int depth = isConvergedBefore() ? currentRoot.getTime() - previousRoot.getTime() - 1 : currentRoot.getTime(); - System.out.println("current root time: " + currentRoot.getTime()); ExtendedState current = currentRoot.getBackPointer(); for (int i = 0; i < depth; i++) { - interLocalPath.add(new SequenceState(current.getState(), current.getObservation())); + interLocalPath.add(new OnlineSequenceState(current.getState(), current.getObservation(), currentTime--)); current = current.getBackPointer(); } assert current == null || current.getState() == previousRoot.getState(); - System.out.println("local added sequence length: " + interLocalPath.size()); + Collections.reverse(interLocalPath); sequenceStates.addAll(interLocalPath); + + System.out.println("Local added sequence length: " + interLocalPath.size()); } /** @@ -420,18 +451,16 @@ private void traceback() { */ public void tracebackLastPart(GPSPoint observation) { System.out.println("traceback last part"); - List interLocalPath = new ArrayList<>(); + List interLocalPath = new ArrayList<>(); CandidatePoint maxValuePoint = findMaxValuePoint(message); - - interLocalPath.add(new SequenceState(maxValuePoint, observation)); + interLocalPath.add(new OnlineSequenceState(maxValuePoint, observation)); if (lastExtendedStates == null) return; ExtendedState current = lastExtendedStates.get(maxValuePoint); - while (current != currentRoot) { - interLocalPath.add(new SequenceState(current.getState(), current.getObservation())); + interLocalPath.add(new OnlineSequenceState(current.getState(), current.getObservation())); current = current.getBackPointer(); } @@ -453,7 +482,7 @@ public LinkedList getStateList() { * * @return A List of SequenceState objects. */ - public List getSequenceStates() { + public List getSequenceStates() { return sequenceStates; } diff --git a/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/stream/StreamMapMatcher.java b/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/stream/StreamMapMatcher.java index 0c9f2b6..3567446 100644 --- a/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/stream/StreamMapMatcher.java +++ b/src/main/java/org/urbcomp/cupid/db/algorithm/mapmatch/stream/StreamMapMatcher.java @@ -1,6 +1,7 @@ package org.urbcomp.cupid.db.algorithm.mapmatch.stream; import org.urbcomp.cupid.db.algorithm.bearing.WindowBearing; +import org.urbcomp.cupid.db.algorithm.mapmatch.onlinemm.OnlineSequenceState; import org.urbcomp.cupid.db.algorithm.mapmatch.onlinemm.OnlineViterbi; import org.urbcomp.cupid.db.algorithm.mapmatch.tihmm.inner.HmmProbabilities; import org.urbcomp.cupid.db.algorithm.mapmatch.tihmm.inner.SequenceState; @@ -12,6 +13,7 @@ import org.urbcomp.cupid.db.model.point.CandidatePoint; import org.urbcomp.cupid.db.model.point.GPSPoint; import org.urbcomp.cupid.db.model.point.MapMatchedPoint; +import org.urbcomp.cupid.db.model.point.SpatialPoint; import org.urbcomp.cupid.db.model.roadnetwork.Path; import org.urbcomp.cupid.db.model.roadnetwork.RoadNetwork; import org.urbcomp.cupid.db.model.roadnetwork.RoadNode; @@ -24,36 +26,78 @@ import java.util.*; +/** + * StreamMapMatcher is responsible for performing map matching on trajectories + * using various Viterbi algorithms. + *

+ * This class leverages the road network and shortest path algorithms to align GPS points + * with the road network based on probabilistic models. + *

+ */ public class StreamMapMatcher { /** - * Sigma parameter for the Gaussian distribution used in emission probabilities + * Sigma parameter for the Gaussian distribution used in emission probabilities. */ private static final double measurementErrorSigma = 50.0; /** - * Beta parameter for the exponential distribution used in transition probabilities + * Beta parameter for the exponential distribution used in transition probabilities. */ private static final double transitionProbabilityBeta = 5.0; /** - * Road network + * The road network on which the map matching is performed. */ protected final RoadNetwork roadNetwork; + + /** + * Shortest path algorithm for many-to-many pathfinding. + */ protected final AbstractManyToManyShortestPath pathAlgorithm; + + /** + * Contains emission and transition probabilities for the Hidden Markov Model. + */ final HmmProbabilities probabilities = new HmmProbabilities( measurementErrorSigma, transitionProbabilityBeta ); + + /** + * Manages the bearing of the current window for processing. + */ private final WindowBearing windowBearing = new WindowBearing(); + + /** + * Bidirectional shortest path algorithm. + */ protected BidirectionalManyToManyShortestPath bidirectionalPathAlgorithm; + /** + * A list to store converged sequence states during processing. + */ public List convergedSequence = new ArrayList<>(); + public List traceDelayRateList = new ArrayList<>(); + + /** + * Constructs a StreamMapMatcher with the specified road network and path algorithm. + * + * @param roadNetwork The road network used for map matching. + * @param pathAlgorithm The shortest path algorithm for matching. + */ public StreamMapMatcher(RoadNetwork roadNetwork, AbstractManyToManyShortestPath pathAlgorithm) { this.roadNetwork = roadNetwork; this.pathAlgorithm = pathAlgorithm; } + /** + * Constructs a StreamMapMatcher with the specified road network, path algorithm, and bidirectional path algorithm. + * + * @param roadNetwork The road network used for map matching. + * @param pathAlgorithm The shortest path algorithm for matching. + * @param bidirectionalPathAlgorithm The bidirectional shortest path algorithm. + */ public StreamMapMatcher(RoadNetwork roadNetwork, AbstractManyToManyShortestPath pathAlgorithm, BidirectionalManyToManyShortestPath bidirectionalPathAlgorithm) { this.roadNetwork = roadNetwork; this.bidirectionalPathAlgorithm = bidirectionalPathAlgorithm; @@ -61,23 +105,26 @@ public StreamMapMatcher(RoadNetwork roadNetwork, AbstractManyToManyShortestPath } /** - * Find the candidate point with the highest probability from the map. + * Finds the candidate point with the highest probability from the given map of candidate points. * - * @param candidateMap Map containing candidate points and their probabilities - * @return The candidate point with the highest probability + * @param candidateMap A map containing candidate points and their associated probabilities. + * @return The candidate point with the highest probability, or null if the map is empty or null. */ public static CandidatePoint findMaxValuePoint(Map candidateMap) { CandidatePoint maxCandidate = null; double maxProbability = Double.MIN_VALUE; + // Check if the candidate map is null if (candidateMap == null) { return maxCandidate; } + // Iterate through the candidate map to find the candidate with the maximum probability for (Map.Entry entry : candidateMap.entrySet()) { CandidatePoint candidatePoint = entry.getKey(); double probability = entry.getValue(); + // Update the max candidate if a new maximum is found if (maxCandidate == null || probability > maxProbability) { maxCandidate = candidatePoint; maxProbability = probability; @@ -87,39 +134,41 @@ public static CandidatePoint findMaxValuePoint(Map candi } /** - * Perform map matching on a trajectory using the stream map-matching method. + * Performs map matching on a trajectory using the stream map-matching method. * - * @param trajectory Trajectory containing GPS points - * @return MapMatchedTrajectory after matching - * @throws AlgorithmExecuteException In case of algorithm errors + * @param trajectory The trajectory containing GPS points to match. + * @return A MapMatchedTrajectory object after performing the matching. + * @throws AlgorithmExecuteException If there are errors during the algorithm execution. */ public MapMatchedTrajectory streamMapMatch(Trajectory trajectory) throws AlgorithmExecuteException { - TimeStep previousTimeStep = null; List sequence = new ArrayList<>(); TiViterbi viterbi = new TiViterbi(); - + // Iterate over each GPS point in the trajectory for (GPSPoint gpsPoint : trajectory.getGPSPointList()) { + // Compute the Viterbi sequence for the current GPS point Tuple3, TimeStep, TiViterbi> result = this.computeViterbiSequence(gpsPoint, sequence, previousTimeStep, viterbi); - sequence = result._1(); - previousTimeStep = result._2(); - viterbi = result._3(); + sequence = result._1(); // Update the sequence with the computed states + previousTimeStep = result._2(); // Update the previous time step + viterbi = result._3(); // Update the Viterbi object } + // Ensure that the number of matched states corresponds to the GPS points assert trajectory.getGPSPointList().size() == sequence.size(); + // Prepare matched points for the resulting trajectory List matchedPoints = new ArrayList<>(sequence.size()); + // Convert the sequence states to matched points for (SequenceState state : sequence) { CandidatePoint candidate = state.getState() != null ? state.getState() : null; matchedPoints.add(new MapMatchedPoint(state.getObservation(), candidate)); } return new MapMatchedTrajectory(trajectory.getTid(), trajectory.getOid(), matchedPoints); - } /** @@ -129,11 +178,11 @@ public MapMatchedTrajectory streamMapMatch(Trajectory trajectory) throws Algorit * @return MapMatchedTrajectory after online matching * @throws AlgorithmExecuteException In case of algorithm errors */ - public MapMatchedTrajectory onlineStreamMapMatch(Trajectory trajectory) throws AlgorithmExecuteException { + public MapMatchedTrajectory onlineStreamMapMatch(Trajectory trajectory, int windowSize) throws AlgorithmExecuteException { TimeStep previousTimeStep = null; List sequence = new ArrayList<>(); - OnlineViterbi viterbi = new OnlineViterbi(); + OnlineViterbi viterbi = new OnlineViterbi(0, windowSize); int currentTime = 0; int trajectorySize = trajectory.getGPSPointList().size(); @@ -148,7 +197,7 @@ public MapMatchedTrajectory onlineStreamMapMatch(Trajectory trajectory) throws A currentTime = (viterbi.message == null) ? 0 : 1; } - result = this.computeViterbiSequence(gpsPoint, sequence, previousTimeStep, viterbi, currentTime); + result = this.computeOnlineViterbiSequence(gpsPoint, sequence, previousTimeStep, viterbi, currentTime); sequence = result._1(); previousTimeStep = result._2(); @@ -177,17 +226,17 @@ public MapMatchedTrajectory onlineStreamMapMatch(Trajectory trajectory) throws A * @param prevTimeStep Previous time step * @param viterbi Viterbi object for sequence calculation * @return A tuple containing the updated sequence, time step, and Viterbi object - * @throws AlgorithmExecuteException In case of errors */ private Tuple3, TimeStep, TiViterbi> computeViterbiSequence( - GPSPoint point, List sequence, TimeStep prevTimeStep, TiViterbi viterbi) - throws AlgorithmExecuteException { + GPSPoint point, List sequence, TimeStep prevTimeStep, TiViterbi viterbi) { windowBearing.addPoint(point); TimeStep timeStep = this.createTimeStep(point); if (timeStep == null) { - sequence.add(new SequenceState(null, point)); // No candidate points for this observation - viterbi = new TiViterbi(); // Reset Viterbi + // No candidate points for this observation + sequence.add(new SequenceState(null, point)); + // Reset Viterbi + viterbi = new TiViterbi(); prevTimeStep = null; } else { if (prevTimeStep != null) { @@ -211,8 +260,8 @@ private Tuple3, TimeStep, TiViterbi> computeViterbiSequence( timeStep.getTransitionLogProbabilities() ); } else { - //第一个点初始化概率 - this.computeEmissionProbabilities(timeStep, probabilities);//计算观测概率 + // Initialize probabilities for the first point + this.computeEmissionProbabilities(timeStep, probabilities); viterbi.startWithInitialObservation( timeStep.getObservation(), timeStep.getCandidates(), @@ -220,6 +269,7 @@ private Tuple3, TimeStep, TiViterbi> computeViterbiSequence( ); } if (viterbi.isBroken) { + // Handle broken Viterbi state sequence.add(viterbi.computeMostLikelySequence().get(viterbi.computeMostLikelySequence().size() - 1)); viterbi = new TiViterbi(); viterbi.startWithInitialObservation( @@ -228,7 +278,8 @@ private Tuple3, TimeStep, TiViterbi> computeViterbiSequence( timeStep.getEmissionLogProbabilities() ); } else { - CandidatePoint maxPoint = StreamMapMatcher.findMaxValuePoint(viterbi.message);//找到最大概率的候选点 + // Find the candidate point with the maximum probability + CandidatePoint maxPoint = StreamMapMatcher.findMaxValuePoint(viterbi.message); sequence.add(new SequenceState(maxPoint, point)); } prevTimeStep = timeStep; @@ -239,146 +290,184 @@ private Tuple3, TimeStep, TiViterbi> computeViterbiSequence( /** * Computes a Viterbi sequence for a given GPS point. * - * @param point The GPS point from the trajectory. - * @param seq The current sequence of states. - * @param preTimeStep The previous time step for reference. - * @param viterbi The Viterbi object used for calculations. - * @param time The current time index. + * @param gpsPoint The GPS point from the trajectory. + * @param currentSequence The current sequence of states. + * @param previousTimeStep The previous time step for reference. + * @param onlineViterbi The Viterbi object used for calculations. + * @param currentTime The current time. * @return A tuple containing the updated sequence, previous time step, and Viterbi object. */ - private Tuple3, TimeStep, OnlineViterbi> computeViterbiSequence( - GPSPoint point, - List seq, - TimeStep preTimeStep, - OnlineViterbi viterbi, - int time + private Tuple3, TimeStep, OnlineViterbi> computeOnlineViterbiSequence( + GPSPoint gpsPoint, + List currentSequence, + TimeStep previousTimeStep, + OnlineViterbi onlineViterbi, + int currentTime ) { - System.out.println("current time: " + time); - windowBearing.addPoint(point); - TimeStep currentTimeStep = this.createTimeStep(point); // Create time step with point and candidate set. + System.out.println("Current time: " + currentTime); + windowBearing.addPoint(gpsPoint); + TimeStep currentTimeStep = this.createTimeStep(gpsPoint); // Create time step with GPS point and candidate set. - int convergeStartIndex = viterbi.getSequenceStates().size(); + int convergeStartIndex = onlineViterbi.getSequenceStates().size(); if (currentTimeStep == null) { - System.out.println("curr time step is null!"); - -// System.out.println("======================================================"); -// System.out.println("Sequence length before traceback last part: " + seq.size()); -// updateSequenceAfterTraceback(viterbi, point, seq); -// -// // Add the last element from the local sequence. - seq.add(new SequenceState(null, point)); -// System.out.println("Sequence length after traceback last part: " + seq.size()); -// System.out.println("======================================================"); + System.out.println("Current time step is null!"); + // Add the last element from the local sequence. + currentSequence.add(new SequenceState(null, gpsPoint)); // Record the start position for global sequence insertion. - viterbi = new OnlineViterbi(seq.size()); - preTimeStep = null; + onlineViterbi = new OnlineViterbi(currentSequence.size(), onlineViterbi.windowSize); + previousTimeStep = null; } else { - if (preTimeStep != null) { + if (previousTimeStep != null) { // Find the shortest path between candidate points of the previous and current time steps. - Set startPoints = new HashSet<>(preTimeStep.getCandidates()); + Set startPoints = new HashSet<>(previousTimeStep.getCandidates()); Set endPoints = new HashSet<>(currentTimeStep.getCandidates()); Map> paths = (bidirectionalPathAlgorithm == null) ? pathAlgorithm.findShortestPath(startPoints, endPoints) : bidirectionalPathAlgorithm.findShortestPath(startPoints, endPoints); -// this.processBackward(preTimeStep, currentTimeStep, viterbi, paths); + // Calculate emission and transition probabilities this.computeEmissionProbabilities(currentTimeStep, probabilities); - this.computeTransitionProbabilities(preTimeStep, currentTimeStep, probabilities, paths); -// this.adjustWithDirection(currentTimeStep, preTimeStep, paths, probabilities); + this.computeTransitionProbabilities(previousTimeStep, currentTimeStep, probabilities, paths); - viterbi.nextStep( + onlineViterbi.nextStep( currentTimeStep.getObservation(), currentTimeStep.getCandidates(), currentTimeStep.getEmissionLogProbabilities(), currentTimeStep.getTransitionLogProbabilities(), - time + currentTime ); } else { - // Initialize probabilities for the first point. + // Initialize probabilities for the first GPS point. this.computeEmissionProbabilities(currentTimeStep, probabilities); - viterbi.startWithInitialObservation( + onlineViterbi.startWithInitialObservation( currentTimeStep.getObservation(), currentTimeStep.getCandidates(), currentTimeStep.getEmissionLogProbabilities() ); } - if (viterbi.isBroken) { + if (onlineViterbi.isBroken) { // Handle the case where the Viterbi algorithm encounters an issue. -// System.out.println("Viterbi is broken."); -// System.out.println("======================================================"); -// System.out.println("Sequence length before traceback last part: " + seq.size()); -// -// updateSequenceAfterTraceback(viterbi, point, seq); -// -// List localSequenceStates = viterbi.getSequenceStates(); -// System.out.println("Local sequence length: " + localSequenceStates.size()); -// -// // Add the second last element from the local sequence. -// seq.add(localSequenceStates.get(localSequenceStates.size() - 2)); -// System.out.println("Sequence length after traceback last part: " + seq.size()); -// System.out.println("======================================================"); - - seq.add(viterbi.computeMostLikelySequence().get(viterbi.computeMostLikelySequence().size() - 1)); + System.out.println("Viterbi is broken."); + System.out.println("Broken sequence size: " + currentSequence.size()); + + List mostLikelySequence = onlineViterbi.computeMostLikelySequence(); + SequenceState lastState = mostLikelySequence.get(mostLikelySequence.size() - 1); + currentSequence.add(lastState); + // Record the start position for global sequence insertion. - viterbi = new OnlineViterbi(seq.size(), true); - viterbi.startWithInitialObservation( + onlineViterbi = new OnlineViterbi(currentSequence.size(), onlineViterbi.windowSize, true); + onlineViterbi.startWithInitialObservation( currentTimeStep.getObservation(), currentTimeStep.getCandidates(), currentTimeStep.getEmissionLogProbabilities() ); } else { - if (viterbi.isConverge) { + if (onlineViterbi.isConverge) { // Handle convergence of the Viterbi algorithm. System.out.println("Viterbi has converged."); System.out.println("======================================================"); - System.out.println("Sequence length before merging converge part: " + seq.size()); - List sequenceStates = viterbi.getSequenceStates(); - // Record converged sequence. - convergedSequence.addAll(sequenceStates.subList(convergeStartIndex, sequenceStates.size())); + List localSequence = onlineViterbi.getSequenceStates(); - int size = sequenceStates.size(); - System.out.println("Local sequence length: " + size); - System.out.println("Insert position: " + viterbi.insertPosition); - System.out.println("Converge start index: " + convergeStartIndex); + int globalSeqInsertStartIndex = -1; + int localSeqInsertStartIndex = -1; + int earliestTime = currentTime - onlineViterbi.windowSize; + int correctedPoints = 0; + double traceDelayRate = 0; + + convergeStartIndex = onlineViterbi.isConvergedBefore() ? convergeStartIndex : 0; + + System.out.println("Local sequence size: " + localSequence.size()); + System.out.println("Global sequence size: " + currentSequence.size()); + + // Determine insertion points for sequences + for (int i = convergeStartIndex; i < localSequence.size(); i++) { + GPSPoint localObservation = localSequence.get(i).getObservation(); - // 之前算法没有发生过中断,第一次收敛的序列从[index==0]开始复制(初始化的元素需要添加) - // 之前算法如果发生过中断,第一次收敛的序列从[index==1]开始复制(初始化的元素不需要添加) - // 非第一次收敛,从上一个时间的[preSeq.size()]开始复制,直到[currSeq.size()] - int isBrokenBefore = viterbi.isBrokenBefore ? 1 : 0; - convergeStartIndex = viterbi.isConvergedBefore() ? convergeStartIndex : isBrokenBefore; - - for (int i = convergeStartIndex; i < size; i++) { - // 算法中断前,从索引0开始复制,无需减1 - // 算法中断后,从索引1开始复制,需要减1 - int insertPosition = viterbi.isBrokenBefore ? i + viterbi.insertPosition - 1 : i + viterbi.insertPosition; - if (i == convergeStartIndex) System.out.println("insert position: " + insertPosition); - seq.set(insertPosition, sequenceStates.get(i)); + for (int j = i + onlineViterbi.validStartInsertIndex; j < currentSequence.size(); j++) { + GPSPoint globalObservation = currentSequence.get(j).getObservation(); + + if (isSamePosition(localObservation, globalObservation)) { + localSeqInsertStartIndex = i; + globalSeqInsertStartIndex = j; + break; + } + } + if (localSeqInsertStartIndex != -1) break; + } + + System.out.println("Converge start index: " + convergeStartIndex); + System.out.println("Global valid start index: " + onlineViterbi.validStartInsertIndex); + System.out.println("Global sequence insert start index: " + globalSeqInsertStartIndex); + System.out.println("Local sequence insert start index: " + localSeqInsertStartIndex); + + if (localSeqInsertStartIndex != -1) { + int globalSeqInsertIndex = globalSeqInsertStartIndex; + int i = localSeqInsertStartIndex; + System.out.println("Expected Update size: " + (localSequence.size() - localSeqInsertStartIndex)); + + for (; i < localSequence.size(); i++) { + // 超过了当前序列的长度 + if (globalSeqInsertIndex == currentSequence.size()) break; + + OnlineSequenceState localState = localSequence.get(i); + SequenceState globalState = currentSequence.get(globalSeqInsertIndex); + + GPSPoint localObservation = localState.getObservation(); + GPSPoint globalObservation = globalState.getObservation(); + + CandidatePoint localCandidate = localState.getState(); + CandidatePoint globalCandidate = globalState.getState(); + + // 回溯的候选点时间在窗口外 + if (localState.time < earliestTime - onlineViterbi.windowSize) continue; + // 观测点的位置不同 + if (!isSamePosition(localObservation, globalObservation)) continue; + // 经过回溯得到了正确匹配点 + if (!isSamePosition(localCandidate, globalCandidate)) { + int delayTime = localState.time - earliestTime; + traceDelayRate += delayTime; + correctedPoints++; + } + currentSequence.set(globalSeqInsertIndex++, localState); + } + + traceDelayRate = correctedPoints != 0 ? traceDelayRate / correctedPoints : 0; + traceDelayRateList.add(traceDelayRate); + + System.out.println("Actual update size: " + (globalSeqInsertIndex - globalSeqInsertStartIndex)); + System.out.println("The number of points that were correctly updated: " + correctedPoints); + System.out.println("Delay rate for correct backtracking: " + traceDelayRate); + + // Record converged sequence. + convergedSequence.addAll(localSequence.subList(localSeqInsertStartIndex, i)); + + } else { + System.out.println("No corresponding sequence found."); } // Reset convergence state until the next convergence occurs. - viterbi.isConverge = false; - System.out.println("Sequence length after merging converge part: " + seq.size()); + onlineViterbi.isConverge = false; } // Find the candidate point with the maximum probability and add to the sequence. - CandidatePoint maxPoint = StreamMapMatcher.findMaxValuePoint(viterbi.message); - seq.add(new SequenceState(maxPoint, point)); + CandidatePoint maxPoint = StreamMapMatcher.findMaxValuePoint(onlineViterbi.message); + currentSequence.add(new SequenceState(maxPoint, gpsPoint)); } - System.out.println("After add current time point, sequence length: " + seq.size()); - System.out.println("################################"); + System.out.println("======================================================"); - preTimeStep = currentTimeStep; + previousTimeStep = currentTimeStep; } - return Tuple3.apply(seq, preTimeStep, viterbi); + return Tuple3.apply(currentSequence, previousTimeStep, onlineViterbi); } + /** * Handles the case where observation points may have shifted backward. * @@ -419,6 +508,7 @@ public void processBackward(TimeStep preTimeStep, TimeStep curTimeStep, } if (isMatch) { + // Add previous candidate to current time step curTimeStep.addCandidate(preCandidatePoint); } } @@ -433,22 +523,27 @@ public void processBackward(TimeStep preTimeStep, TimeStep curTimeStep, */ public void adjustWithDirection(TimeStep currTimeStep, TimeStep preTimeStep, Map> paths, HmmProbabilities probabilities) { + // Check if there is a directional change detected if (!windowBearing.getChange()) { - // Directional change is not detected; no adjustment needed. + // No adjustment needed if there's no directional change System.out.println(windowBearing.getChange() + " " + windowBearing.getChangeScore() + " " + currTimeStep.getObservation()); } else { - GPSPoint currObservationPoint = currTimeStep.getObservation(); - GPSPoint preObservationPoint = preTimeStep.getObservation(); + GPSPoint currObservationPoint = currTimeStep.getObservation(); // Get current observation + GPSPoint preObservationPoint = preTimeStep.getObservation(); // Get previous observation + // Calculate bearing based on the current and previous points double observationBearing = GeoFunctions.getBearing( preObservationPoint.getLng(), preObservationPoint.getLat(), currObservationPoint.getLng(), currObservationPoint.getLat() ); + + // Calculate speed between the two observations double speed = GeoFunctions.getDistanceInM( preObservationPoint.getLng(), preObservationPoint.getLat(), currObservationPoint.getLng(), currObservationPoint.getLat()) * 1000 / (currObservationPoint.getTime().getTime() - preObservationPoint.getTime().getTime()); + // If speed is below a threshold, log the information if (speed < 2.0) { System.out.println( windowBearing.getChange() + " " @@ -456,34 +551,43 @@ public void adjustWithDirection(TimeStep currTimeStep, TimeStep preTimeStep, + "speed: " + speed + " " + currTimeStep.getObservation()); } else { + // Iterate through the transition probabilities for (Map.Entry, Double> entry : currTimeStep.getTransitionLogProbabilities().entrySet()) { - Tuple2 key = entry.getKey(); + Tuple2 key = entry.getKey(); // Get candidate points + // Retrieve road segments for the candidate points RoadSegment startRoadSegment = roadNetwork.getRoadSegmentById(key._1.getRoadSegmentId()); RoadSegment endRoadSegment = roadNetwork.getRoadSegmentById(key._2.getRoadSegmentId()); + // Get the subpath between the end of the start segment and the start of the end segment Path subPath = paths.get(startRoadSegment.getEndNode()).get(endRoadSegment.getStartNode()); + // Complete the path using the algorithm Path path = pathAlgorithm.getCompletePath(key._1, key._2, subPath); + // Calculate the candidate's bearing double candidateBearing = path.calDisWeightDirection(roadNetwork); + // Calculate the angle difference between the observation bearing and candidate bearing double angleDifference = Math.abs(observationBearing - candidateBearing); + // Adjust the angle difference if it's greater than 180 degrees if (angleDifference > 180) { angleDifference = 360 - angleDifference; } + // Update transition probabilities based on the direction currTimeStep.getTransitionLogProbabilities().put( key, currTimeStep.getTransitionLogProbabilities().get(key) + probabilities.directionLogProbability(angleDifference)); } -// System.out.println("true direction: " + windowBearing.getChangeScore() + " " + currTimeStep.getObservation()); +// System.out.println("true direction: " + windowBearing.getChangeScore() + " " + currTimeStep.getObservation()); } } } + /** * Calculates the emission probabilities based on the given time step and probability distribution. * @@ -552,21 +656,13 @@ private TimeStep createTimeStep(GPSPoint point) { } /** - * Updates the given sequence with the local sequence states after performing a traceback. + * Checks if two GPS points are at the same position based on their longitude and latitude. * - * @param viterbi The Viterbi instance used for traceback. - * @param point The point to be traced back. - * @param seq The sequence to be updated. + * @param p1 The first GPS point to compare. + * @param p2 The second GPS point to compare. + * @return True if both points are considered the same position, false otherwise. */ - private void updateSequenceAfterTraceback(OnlineViterbi viterbi, GPSPoint point, List seq) { - viterbi.tracebackLastPart(point); - List localSequenceStates = viterbi.getSequenceStates(); - System.out.println("Local sequence length: " + localSequenceStates.size()); - int startIndex = seq.size() - localSequenceStates.size() + 1; - - // Update elements in the sequence. - for (int i = startIndex; i < seq.size(); i++) { - seq.set(i, localSequenceStates.get(i - startIndex)); - } + private boolean isSamePosition(SpatialPoint p1, SpatialPoint p2) { + return Math.abs(p1.getLng() - p2.getLng()) < 1e-6 && Math.abs(p1.getLat() - p2.getLat()) < 1e-6; } } diff --git a/src/main/java/org/urbcomp/cupid/db/util/GeoJSONParser.java b/src/main/java/org/urbcomp/cupid/db/util/GeoJSONParser.java new file mode 100644 index 0000000..80a8373 --- /dev/null +++ b/src/main/java/org/urbcomp/cupid/db/util/GeoJSONParser.java @@ -0,0 +1,94 @@ +package org.urbcomp.cupid.db.util; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.urbcomp.cupid.db.model.point.CandidatePoint; +import org.urbcomp.cupid.db.model.point.GPSPoint; + +import java.io.File; +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; + +public class GeoJSONParser { + /** + * Parses the raw points (latitude, longitude) from the GeoJSON string. + * + * @param geoJsonPath Path to the GeoJSON file. + * @return A list of raw GPS points extracted from the GeoJSON. + * @throws Exception In case of parsing errors. + */ + public static List parseRawPointsFromGeoJSON(String geoJsonPath) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(new File(geoJsonPath)); + + List rawPoints = new ArrayList<>(); + + // Iterate through the features array + JsonNode features = root.get("features"); + for (JsonNode feature : features) { + JsonNode properties = feature.get("properties"); + + // Extract rawLat, rawLon, and time from properties + double rawLat = properties.get("rawLat").asDouble(); + double rawLon = properties.get("rawLon").asDouble(); + String timeString = properties.get("time").asText(); + Timestamp timestamp = parseTimestamp(timeString); + + GPSPoint rawPoint = new GPSPoint(timestamp, rawLon, rawLat); + rawPoints.add(rawPoint); + } + + return rawPoints; + } + + /** + * Parses the candidate points (latitude, longitude) from the GeoJSON string. + * + * @param geoJsonPath Path to the GeoJSON file. + * @return A list of candidate points extracted from the GeoJSON. + * @throws Exception In case of parsing errors. + */ + public static List parseCandidatePointsFromGeoJSON(String geoJsonPath) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(new File(geoJsonPath)); + + List candidatePoints = new ArrayList<>(); + + // Iterate through the features array + JsonNode features = root.get("features"); + for (JsonNode feature : features) { + JsonNode properties = feature.get("properties"); + JsonNode geometry = feature.get("geometry"); + JsonNode coordinates = geometry.get("coordinates"); + + // Extract candidate point's longitude and latitude from coordinates + double errorDistance = properties.get("errorDistanceInMeter").asDouble(); + double offsetInMeter = properties.get("offsetInMeter").asDouble(); + int matchedIndex = properties.get("matchedIndex").asInt(); + int roadSegmentId = properties.get("roadSegmentId").asInt(); + + double candidateLon = coordinates.get(0).asDouble(); + double candidateLat = coordinates.get(1).asDouble(); + + CandidatePoint candidatePoint = new CandidatePoint(candidateLon, candidateLat, roadSegmentId, matchedIndex, errorDistance, offsetInMeter); + candidatePoints.add(candidatePoint); + } + + return candidatePoints; + } + + /** + * Converts a time string to a Timestamp object. + * + * @param timeString The time string to convert. + * @return The corresponding Timestamp object. + * @throws ParseException If the time string is not in the expected format. + */ + private static Timestamp parseTimestamp(String timeString) throws ParseException { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); + return new Timestamp(dateFormat.parse(timeString).getTime()); + } +} diff --git a/src/main/resources/weight2.txt b/src/main/resources/weight2.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/test/java/OnlineMapMatcherTest.java b/src/test/java/OnlineMapMatcherTest.java index 10bd7f2..7b3b703 100644 --- a/src/test/java/OnlineMapMatcherTest.java +++ b/src/test/java/OnlineMapMatcherTest.java @@ -20,7 +20,6 @@ import org.urbcomp.cupid.db.util.GeoJSONParser; import java.io.*; -import java.text.ParseException; import java.util.List; import static org.junit.Assert.assertEquals; @@ -36,7 +35,6 @@ public class OnlineMapMatcherTest { private StreamMapMatcher streamMapMatcher; private TiHmmMapMatcher baseMapMatcher; private ShortestPathPathRecover recover; - private RoadNetwork roadNetwork; /** * Sets up the test environment by generating a sample trajectory and road network. @@ -45,7 +43,7 @@ public class OnlineMapMatcherTest { @Before public void setUp() { trajectory = ModelGenerator.generateTrajectory(); - roadNetwork = ModelGenerator.generateRoadNetwork(); + RoadNetwork roadNetwork = ModelGenerator.generateRoadNetwork(); baseMapMatcher = new TiHmmMapMatcher(roadNetwork, new SimpleManyToManyShortestPath(roadNetwork)); streamMapMatcher = new StreamMapMatcher(roadNetwork, new SimpleManyToManyShortestPath(roadNetwork), new BidirectionalManyToManyShortestPath(roadNetwork)); recover = new ShortestPathPathRecover(roadNetwork, new BiDijkstraShortestPath(roadNetwork)); @@ -60,8 +58,9 @@ public void setUp() { */ @Test public void matchSingleTrajectory() throws AlgorithmExecuteException, IOException { + int windowSize = 10; trajectory = ModelGenerator.generateTrajectory(6); - MapMatchedTrajectory mmTrajectory = streamMapMatcher.onlineStreamMapMatch(trajectory); + MapMatchedTrajectory mmTrajectory = streamMapMatcher.onlineStreamMapMatch(trajectory, windowSize); System.out.println(trajectory.toGeoJSON()); System.out.println(mmTrajectory.toGeoJSON()); @@ -82,6 +81,7 @@ public void matchSingleTrajectory() throws AlgorithmExecuteException, IOExceptio */ @Test public void matchMultiTrajectory() throws AlgorithmExecuteException { + int windowSize = 10; for (int i = 1; i <= 10; i++) { System.out.println("------------------------------"); System.out.println("index: " + i); @@ -89,7 +89,7 @@ public void matchMultiTrajectory() throws AlgorithmExecuteException { trajectory = ModelGenerator.generateTrajectory(i); MapMatchedTrajectory mmTrajectory = streamMapMatcher.streamMapMatch(trajectory); - MapMatchedTrajectory onlineMMTrajectory = streamMapMatcher.onlineStreamMapMatch(trajectory); + MapMatchedTrajectory onlineMMTrajectory = streamMapMatcher.onlineStreamMapMatch(trajectory, windowSize); List mmPtList = mmTrajectory.getMmPtList(); List onlineMMPtList = onlineMMTrajectory.getMmPtList(); @@ -108,36 +108,39 @@ public void matchMultiTrajectory() throws AlgorithmExecuteException { public void onlineMatchAccuracy() throws AlgorithmExecuteException, IOException { int testNum = 6; int sampleRate = 0; - for (int i = 6; i <= testNum; i++) { - System.out.println("==========================="); + int windowSize = 10; + + for (int i = 1; i <= testNum; i++) { + System.out.println("======================================================"); System.out.println("index: " + i); - System.out.println("==========================="); + System.out.println("======================================================"); + trajectory = ModelGenerator.generateTrajectory(i); Trajectory sampledTrajectory = ModelGenerator.generateTrajectory(i, sampleRate); assert sampledTrajectory != null; MapMatchedTrajectory baseMapMatchedTrajectory = baseMapMatcher.mapMatch(trajectory); - MapMatchedTrajectory streamOnlineMapMatchedTrajectory = streamMapMatcher.onlineStreamMapMatch(sampledTrajectory); + MapMatchedTrajectory streamOnlineMapMatchedTrajectory = streamMapMatcher.onlineStreamMapMatch(sampledTrajectory, windowSize); assert baseMapMatchedTrajectory.getMmPtList().size() == streamOnlineMapMatchedTrajectory.getMmPtList().size(); EvaluateUtils.getAccuracy(baseMapMatchedTrajectory, streamOnlineMapMatchedTrajectory, sampleRate); - System.out.println("==========================="); + System.out.println("======================================================"); System.out.println("results: "); System.out.println("currAcc: " + EvaluateUtils.getCurrAcc()); System.out.println("totalAcc: " + EvaluateUtils.getTotalAcc()); System.out.println("pointNum: " + EvaluateUtils.getTotalNum()); - System.out.println("==========================="); + System.out.println("======================================================"); System.out.println(); - String outputFile = "trajectory_" + i + ".geojson"; - BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile)); - writer.write(baseMapMatchedTrajectory.toGeoJSON(true)); - writer.flush(); - writer.close(); +// String outputFile = "trajectory_" + i + ".geojson"; +// BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile)); +// writer.write(baseMapMatchedTrajectory.toGeoJSON(true)); +// writer.flush(); +// writer.close(); } } @@ -146,7 +149,7 @@ public void onlineMatchAccuracy() throws AlgorithmExecuteException, IOException * Records matching results for each trajectory and generates two CSV files. */ @Test - public void testNoOnlineStreamMatch() throws AlgorithmExecuteException { + public void testStreamMatchAccuracy() throws AlgorithmExecuteException { int testNum = 100; int sampleRate = 0; @@ -201,12 +204,13 @@ public void testNoOnlineStreamMatch() throws AlgorithmExecuteException { * Records matching results for each trajectory and generates a CSV file. */ @Test - public void testOnlineStreamMatch() throws AlgorithmExecuteException { + public void testOnlineStreamMatchAccuracy() throws AlgorithmExecuteException { int testNum = 100; int sampleRate = 0; + int windowSize = 10; // Create CSV file for online results - try (PrintWriter onlineWriter = new PrintWriter(new FileWriter("onlineStreamResult.csv"))) { + try (PrintWriter onlineWriter = new PrintWriter(new FileWriter("onlineStreamResult_window.csv"))) { // Write headers for the CSV file onlineWriter.println("TrajectoryIndex,currPointNum,totalPointNum,currAcc,totalAcc"); @@ -224,7 +228,7 @@ public void testOnlineStreamMatch() throws AlgorithmExecuteException { // Match using baseMatch MapMatchedTrajectory baseMapMatchedTrajectory = baseMapMatcher.mapMatch(trajectory); // Match using onlineStreamMatch - MapMatchedTrajectory onlineMapMatchedTrajectory = streamMapMatcher.onlineStreamMapMatch(sampledTrajectory); + MapMatchedTrajectory onlineMapMatchedTrajectory = streamMapMatcher.onlineStreamMapMatch(sampledTrajectory, windowSize); // Record number of matched points int onlineCurrPointNum = onlineMapMatchedTrajectory.getMmPtList().size(); @@ -256,6 +260,7 @@ public void testConvergedSequenceAccuracy() throws Exception { int testNum = 6; // Number of trajectories to test int sampleRate = 0; // Sample rate for trajectory generation double epsilon = 1e-6; // Allowable error for latitude/longitude comparison + int windowSize = 10; for (int i = 6; i <= testNum; i++) { System.out.println("==========================="); @@ -269,7 +274,7 @@ public void testConvergedSequenceAccuracy() throws Exception { assert sampledTrajectory != null; // Perform online map matching - MapMatchedTrajectory streamOnlineMapMatchedTrajectory = streamMapMatcher.onlineStreamMapMatch(sampledTrajectory); + streamMapMatcher.onlineStreamMapMatch(sampledTrajectory, windowSize); // Get converged sequence from the streamMapMatcher List convergedSequence = streamMapMatcher.convergedSequence; @@ -310,13 +315,13 @@ public void testConvergedSequenceAccuracy() throws Exception { assertTrue(Math.abs(rawPointFromSequence.getLng() - rawPointFromGeoJSON.getLng()) < epsilon); // Compare candidate points' latitude and longitude -// if (candidatePointFromSequence != null && candidatePointFromGeoJSON != null) { -// assertTrue(Math.abs(candidatePointFromSequence.getX() - candidatePointFromGeoJSON.getX()) < epsilon); -// assertTrue(Math.abs(candidatePointFromSequence.getY() - candidatePointFromGeoJSON.getY()) < epsilon); -// } else { -// // Both candidate points must be null -// assertEquals(candidatePointFromSequence, candidatePointFromGeoJSON); -// } + if (candidatePointFromSequence != null && candidatePointFromGeoJSON != null) { + assertTrue(Math.abs(candidatePointFromSequence.getX() - candidatePointFromGeoJSON.getX()) < epsilon); + assertTrue(Math.abs(candidatePointFromSequence.getY() - candidatePointFromGeoJSON.getY()) < epsilon); + } else { + // Both candidate points must be null + assertEquals(candidatePointFromSequence, candidatePointFromGeoJSON); + } } System.out.println("Trajectory " + i + " passed the accuracy test.");