Skip to content
This repository has been archived by the owner on Mar 1, 2021. It is now read-only.

WIP: handling viterbi breaks as multiple sequences #87

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2c1a010
initial work for handling viterbi breaks as multiple sequences
kodonnell Dec 11, 2016
d979ffd
handle case where viterbi breaks immediately after initialization
kodonnell Dec 12, 2016
6b856f1
merge U-turn work
kodonnell Dec 30, 2016
ade4037
refactoring sequences
kodonnell Jan 13, 2017
4b24cbc
use MatchEntry internally instead of GPXEntry
kodonnell Jan 14, 2017
5832623
debug and fix tests
kodonnell Jan 15, 2017
b6c3af4
rename timestep -> viterbimatchentry
kodonnell Jan 15, 2017
c0e5574
fix other tests
kodonnell Jan 15, 2017
2d184a2
tidying calcpath and gpxfile/main
kodonnell Jan 15, 2017
791e53c
web stuff ...
kodonnell Jan 15, 2017
eed78bf
giving up on that test ...
kodonnell Jan 15, 2017
ac105e7
woops, don't need that anymore ...
kodonnell Jan 15, 2017
d6bf213
refactor + tidy + all tests passing
kodonnell Jan 31, 2017
4e217d0
contiguous sequences
kodonnell Jan 31, 2017
f629883
undo test change to fix test change
kodonnell Feb 1, 2017
9e6cc60
add logging in again as per @stefanholder's request
kodonnell Feb 6, 2017
9d6f84b
Merge branch 'master' into sequences
kodonnell Feb 26, 2017
7f45557
note funny bug ...
kodonnell Feb 26, 2017
4a7420a
some changes as per @stefanholder
kodonnell Mar 20, 2017
13707e1
bringing back the missing readme
kodonnell Mar 20, 2017
efb7b57
a few more tidyups
kodonnell Mar 20, 2017
956e7d0
more tidy-ups
kodonnell Mar 20, 2017
1dd88e8
utilise LocationIndexTree.findWithinRadius
kodonnell Mar 20, 2017
56df0ab
ugly hacky gui ...
kodonnell Mar 28, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
* @author kodonnell
* @author Stefan Holder
*/
public class GPXExtension {
public class Candidate {
private final GPXEntry entry;
private final QueryResult queryResult;
private final boolean isDirected;
Expand All @@ -48,7 +48,7 @@ public class GPXExtension {
/**
* Creates an undirected candidate for a real node.
*/
public GPXExtension(GPXEntry entry, QueryResult queryResult) {
public Candidate(GPXEntry entry, QueryResult queryResult) {
this.entry = entry;
this.queryResult = queryResult;
this.isDirected = false;
Expand All @@ -59,7 +59,7 @@ public GPXExtension(GPXEntry entry, QueryResult queryResult) {
/**
* Creates a directed candidate for a virtual node.
*/
public GPXExtension(GPXEntry entry, QueryResult queryResult,
public Candidate(GPXEntry entry, QueryResult queryResult,
VirtualEdgeIteratorState incomingVirtualEdge,
VirtualEdgeIteratorState outgoingVirtualEdge) {
this.entry = entry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,15 @@ public MatchResult doWork(List<GPXEntry> gpxList) {
+ gpxList.size() + "). Correct format?");
}

// map to matchEntries:
List<MatchEntry> matchEntries = new ArrayList<MatchEntry>(gpxList.size());
for (GPXEntry gpxEntry: gpxList) {
matchEntries.add(new MatchEntry(gpxEntry));
}

// create map matching events from the input GPX list:
final EdgeFilter edgeFilter = new DefaultEdgeFilter(algoOptions.getWeighting().getFlagEncoder());
List<TimeStep> timeSteps = createTimeSteps(gpxList, edgeFilter);
List<TimeStep> timeSteps = createTimeSteps(matchEntries, edgeFilter);

// create the candidates per event:
final QueryGraph queryGraph = new QueryGraph(routingGraph).setUseEdgeExplorerCache(true);
Expand All @@ -183,7 +189,7 @@ public MatchResult doWork(List<GPXEntry> gpxList) {
// at this stage, we have a sequence of most likely results stored as viterbi/HMM
// structures - let's convert these to more useful things:
final EdgeExplorer explorer = queryGraph.createEdgeExplorer(edgeFilter);
MatchResult matchResult = computeMatchResult(sequences, timeSteps, gpxList, allCandidateLocations, explorer);
MatchResult matchResult = computeMatchResult(sequences, timeSteps, matchEntries, allCandidateLocations, explorer);

return matchResult;
}
Expand All @@ -192,23 +198,24 @@ public MatchResult doWork(List<GPXEntry> gpxList) {
* Create map match events from the input GPX entries. This is largely reshaping the data,
* though it also clusters GPX entries which are too close together into single steps.
*/
private List<TimeStep> createTimeSteps(List<GPXEntry> gpxList, EdgeFilter edgeFilter) {
private List<TimeStep> createTimeSteps(List<MatchEntry> matchEntries, EdgeFilter edgeFilter) {
final List<TimeStep> timeSteps = new ArrayList<TimeStep>();
TimeStep lastTimeStepAdded = null;
GPXEntry prevEntry = null;
int last = gpxList.size() - 1;
MatchEntry prevEntry = null;
int last = matchEntries.size() - 1;
for (int i = 0; i <= last; i++) {
GPXEntry gpxEntry = gpxList.get(i);
MatchEntry matchEntry = matchEntries.get(i);
// ignore those which are within 2 * measurementErrorSigma of the previous (though
// never ignore the first/last).
if (i == 0 || i == last || distanceCalc.calcDist(
prevEntry.getLat(), prevEntry.getLon(),
gpxEntry.getLat(), gpxEntry.getLon()) > 2 * measurementErrorSigma) {
lastTimeStepAdded = new TimeStep(gpxEntry);
if (i == 0 || i == last
|| distanceCalc.calcDist(prevEntry.gpxEntry.getLat(),
prevEntry.gpxEntry.getLon(), matchEntry.gpxEntry.getLat(),
matchEntry.gpxEntry.getLon()) > 2 * measurementErrorSigma) {
lastTimeStepAdded = new TimeStep(matchEntry);
timeSteps.add(lastTimeStepAdded);
prevEntry = gpxEntry;
prevEntry = matchEntry;
} else {
lastTimeStepAdded.addNeighboringGPXEntry(gpxEntry);
matchEntry.markAsNotUsedForMatching();
}
}
return timeSteps;
Expand Down Expand Up @@ -259,7 +266,7 @@ private List<MatchSequence> computeViterbiSequence(List<TimeStep> timeSteps,
final QueryGraph queryGraph) {
final HmmProbabilities probabilities = new HmmProbabilities(measurementErrorSigma,
transitionProbabilityBeta);
ViterbiAlgorithm<GPXExtension, GPXEntry, Path> viterbi = null;
ViterbiAlgorithm<Candidate, MatchEntry, Path> viterbi = null;
final List<MatchSequence> sequences = new ArrayList<MatchSequence>();
TimeStep seqPrevTimeStep = null;
int currentSequenceSize = 0;
Expand All @@ -279,14 +286,14 @@ private List<MatchSequence> computeViterbiSequence(List<TimeStep> timeSteps,
assert currentSequenceSize == 0;
viterbi = new ViterbiAlgorithm<>();
currentSequenceTimeSteps = new ArrayList<TimeStep>();
viterbi.startWithInitialObservation(timeStep.observation,
viterbi.startWithInitialObservation(timeStep.matchEntry,
timeStep.candidates, timeStep.emissionLogProbabilities);
} else {
// add this step to current sequence:
assert currentSequenceSize > 0;
computeTransitionProbabilities(seqPrevTimeStep, timeStep, probabilities,
queryGraph);
viterbi.nextStep(timeStep.observation, timeStep.candidates,
viterbi.nextStep(timeStep.matchEntry, timeStep.candidates,
timeStep.emissionLogProbabilities,
timeStep.transitionLogProbabilities, timeStep.roadPaths);
}
Expand All @@ -300,7 +307,7 @@ private List<MatchSequence> computeViterbiSequence(List<TimeStep> timeSteps,
} else if (timeStep.transitionLogProbabilities.isEmpty()) {
breakReason = BreakReason.NO_POSSIBLE_TRANSITIONS;
}
final List<SequenceState<GPXExtension, GPXEntry, Path>> viterbiSequence = viterbi.computeMostLikelySequence();
final List<SequenceState<Candidate, MatchEntry, Path>> viterbiSequence = viterbi.computeMostLikelySequence();
// We need to handle two cases separately: single event sequences, and more.
if (seqPrevTimeStep == null) {
// OK, we had a break immediately after initialising. In this case, we simply
Expand Down Expand Up @@ -338,7 +345,7 @@ private List<MatchSequence> computeViterbiSequence(List<TimeStep> timeSteps,

// add the final sequence (if non-empty):
if (seqPrevTimeStep != null) {
final List<SequenceState<GPXExtension, GPXEntry, Path>> viterbiSequence = viterbi.computeMostLikelySequence();
final List<SequenceState<Candidate, MatchEntry, Path>> viterbiSequence = viterbi.computeMostLikelySequence();
sequences.add(new MatchSequence(viterbiSequence, currentSequenceTimeSteps, BreakReason.LAST_GPX_ENTRY, viterbiSequence.size() == 1 ? SequenceType.STATIONARY : SequenceType.SEQUENCE));
}

Expand All @@ -348,7 +355,7 @@ private List<MatchSequence> computeViterbiSequence(List<TimeStep> timeSteps,
}

private void computeEmissionProbabilities(TimeStep timeStep, HmmProbabilities probabilities) {
for (GPXExtension candidate : timeStep.candidates) {
for (Candidate candidate : timeStep.candidates) {
// road distance difference in meters
final double distance = candidate.getQueryResult().getQueryDistance();
timeStep.addEmissionLogProbability(candidate,
Expand All @@ -358,13 +365,14 @@ private void computeEmissionProbabilities(TimeStep timeStep, HmmProbabilities pr

private void computeTransitionProbabilities(TimeStep prevTimeStep, TimeStep timeStep,
HmmProbabilities probabilities, QueryGraph queryGraph) {
final double linearDistance = distanceCalc.calcDist(prevTimeStep.observation.lat,
prevTimeStep.observation.lon, timeStep.observation.lat, timeStep.observation.lon);
final double linearDistance = distanceCalc.calcDist(prevTimeStep.matchEntry.gpxEntry.lat,
prevTimeStep.matchEntry.gpxEntry.lon, timeStep.matchEntry.gpxEntry.lat,
timeStep.matchEntry.gpxEntry.lon);

// TODO: check if timesteps are temporally ordered, e.g. timeStep comes after prevTimeStep?

for (GPXExtension from : prevTimeStep.candidates) {
for (GPXExtension to : timeStep.candidates) {
for (Candidate from : prevTimeStep.candidates) {
for (Candidate to : timeStep.candidates) {
// enforce heading if required:
if (from.isDirected()) {
// Make sure that the path starting at the "from" candidate goes through
Expand Down Expand Up @@ -434,17 +442,17 @@ private double penalizedPathDistance(Path path,
}

private MatchResult computeMatchResult(List<MatchSequence> sequences,
List<TimeStep> timeSteps, List<GPXEntry> gpxList,
List<TimeStep> timeSteps, List<MatchEntry> matchEntries,
List<QueryResult> allCandidateLocations, EdgeExplorer explorer) {

final Map<String, EdgeIteratorState> virtualEdgesMap = createVirtualEdgesMap(
allCandidateLocations, explorer);

MatchResult matchResult = new MatchResult(sequences);
matchResult.computeEdgeMatches(virtualEdgesMap, nodeCount, gpxList);
MatchResult matchResult = new MatchResult(matchEntries, sequences);
matchResult.computeEdgeMatches(virtualEdgesMap, nodeCount);

// compute GPX stats from the original GPX track:
matchResult.computeGPXStats(distanceCalc, false);
matchResult.computeGPXStats(distanceCalc);

return matchResult;
}
Expand Down Expand Up @@ -527,14 +535,12 @@ public Path calcPath(MatchResult mr) {
MapMatchedPath p = new MapMatchedPath(routingGraph, algoOptions.getWeighting());
if (!mr.getEdgeMatches().isEmpty()) {
int prevEdge = EdgeIterator.NO_EDGE;
p.setFromNode(mr.getEdgeMatches().get(0).getEdgeState().getBaseNode());
p.setFromNode(mr.getEdgeMatches().get(0).edge.getBaseNode());
for (MatchEdge em : mr.getEdgeMatches()) {
p.processEdge(em.getEdgeState().getEdge(), em.getEdgeState().getAdjNode(), prevEdge);
prevEdge = em.getEdgeState().getEdge();
p.processEdge(em.edge.getEdge(), em.edge.getAdjNode(), prevEdge);
prevEdge = em.edge.getEdge();
}

p.setFound(true);

return p;
} else {
return p;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,80 @@
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.matching;

import java.util.List;

import com.bmw.hmm.SequenceState;
import com.graphhopper.matching.util.TimeStep;
import com.graphhopper.routing.Path;
import com.graphhopper.util.EdgeIteratorState;
import com.graphhopper.util.GPXEntry;
import com.graphhopper.util.shapes.GHPoint3D;

public class MatchEntry {
public final int sequenceIdx;
public static enum MatchState { MATCHING_NOT_STARTED, NOT_USED_FOR_MATCHING, MATCHED };
private MatchState matchState = MatchState.MATCHING_NOT_STARTED;
public final GPXEntry gpxEntry;
public final List<GPXEntry> neighboringGpxEntries;
public final GHPoint3D point;
public final EdgeIteratorState directedRealEdge;
public final int sequenceMatchEdgeIdx;
public final double distanceAlongRealEdge;
public MatchEntry(int sequenceIdx, SequenceState<GPXExtension, GPXEntry, Path> matchStep, TimeStep timeStep, EdgeIteratorState directedRealEdge, int sequenceMatchEdgeIdx, double distanceAlongRealEdge) {
private int sequenceIdx;
private GHPoint3D snappedPoint;
private EdgeIteratorState directedRealEdge;
private int sequenceMatchEdgeIdx;
private double distanceAlongRealEdge;
private boolean locked = false;

protected MatchEntry(GPXEntry gpxEntry) {
this.gpxEntry = gpxEntry;
}

protected void markAsNotUsedForMatching() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this protected, do you plan to inherit from this class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

assert !locked;
this.matchState = MatchState.NOT_USED_FOR_MATCHING;
locked = true;
}

protected void saveMatchingState(int sequenceIdx, int sequenceMatchEdgeIdx,
Copy link
Contributor

@stefanholder stefanholder Mar 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is called externally (e.g. here) and I don't want it to be available to users - I could be missing something, but isn't this the purpose of 'protected'?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case you should use package-private (no modifier) instead of protected. Protected should only be used if you plan to access the method in derived classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about that, and thanks for pointing it out.

EdgeIteratorState directedRealEdge, double distanceAlongRealEdge,
GHPoint3D snappedPoint) {
assert !locked;
this.sequenceIdx = sequenceIdx;
this.gpxEntry = matchStep.observation;
this.point = matchStep.state.getQueryResult().getSnappedPoint();
this.sequenceMatchEdgeIdx = sequenceMatchEdgeIdx;
this.directedRealEdge = directedRealEdge;
this.distanceAlongRealEdge = distanceAlongRealEdge;
this.neighboringGpxEntries = timeStep.getNeighboringEntries();
this.snappedPoint = snappedPoint;
locked = true;
}

public MatchState getMatchState() {
return matchState;
}

public int getSequenceIdx() {
return sequenceIdx;
}

public GHPoint3D getSnappedPoint() {
return snappedPoint;
}

public EdgeIteratorState getDirectedRealEdge() {
return directedRealEdge;
}

public int getSequenceMatchEdgeIdx() {
return sequenceMatchEdgeIdx;
}

public double getDistanceAlongRealEdge() {
return distanceAlongRealEdge;
}
}
Loading