Skip to content

Commit 93dd742

Browse files
authored
Don't interpolate travel times in GPX; fixes graphhopper#1490 (graphhopper#1595)
1 parent 0463d84 commit 93dd742

File tree

23 files changed

+862
-820
lines changed

23 files changed

+862
-820
lines changed

api/src/main/java/com/graphhopper/util/Instruction.java

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
package com.graphhopper.util;
1919

2020
import java.util.HashMap;
21-
import java.util.List;
2221
import java.util.Map;
2322

2423
public class Instruction {
@@ -127,24 +126,6 @@ public Instruction setTime(long time) {
127126
return this;
128127
}
129128

130-
/**
131-
* Latitude of the location where this instruction should take place.
132-
*/
133-
double getFirstLat() {
134-
return points.getLatitude(0);
135-
}
136-
137-
/**
138-
* Longitude of the location where this instruction should take place.
139-
*/
140-
double getFirstLon() {
141-
return points.getLongitude(0);
142-
}
143-
144-
double getFirstEle() {
145-
return points.getElevation(0);
146-
}
147-
148129
/* This method returns the points associated to this instruction. Please note that it will not include the last point,
149130
* i.e. the first point of the next instruction object.
150131
*/
@@ -156,43 +137,6 @@ public void setPoints(PointList points) {
156137
this.points = points;
157138
}
158139

159-
/**
160-
* This method returns a list of gpx entries where the time (in time) is relative to the first
161-
* which is 0. It does NOT contain the last point which is the first of the next instruction.
162-
*
163-
* @return the time offset to add for the next instruction
164-
*/
165-
long fillGPXList(List<GPXEntry> list, long time,
166-
Instruction prevInstr, Instruction nextInstr, boolean firstInstr) {
167-
checkOne();
168-
int len = points.size();
169-
long prevTime = time;
170-
double lat = points.getLatitude(0);
171-
double lon = points.getLongitude(0);
172-
double ele = Double.NaN;
173-
boolean is3D = points.is3D();
174-
if (is3D)
175-
ele = points.getElevation(0);
176-
177-
for (int i = 0; i < len; i++) {
178-
list.add(new GPXEntry(lat, lon, ele, prevTime));
179-
180-
boolean last = i + 1 == len;
181-
double nextLat = last ? nextInstr.getFirstLat() : points.getLatitude(i + 1);
182-
double nextLon = last ? nextInstr.getFirstLon() : points.getLongitude(i + 1);
183-
double nextEle = is3D ? (last ? nextInstr.getFirstEle() : points.getElevation(i + 1)) : Double.NaN;
184-
if (is3D)
185-
prevTime = Math.round(prevTime + this.time * Helper.DIST_3D.calcDist(nextLat, nextLon, nextEle, lat, lon, ele) / distance);
186-
else
187-
prevTime = Math.round(prevTime + this.time * Helper.DIST_3D.calcDist(nextLat, nextLon, lat, lon) / distance);
188-
189-
lat = nextLat;
190-
lon = nextLon;
191-
ele = nextEle;
192-
}
193-
return time + this.time;
194-
}
195-
196140
@Override
197141
public String toString() {
198142
StringBuilder sb = new StringBuilder();
@@ -209,7 +153,7 @@ public String toString() {
209153
* Return the direction like 'NE' based on the first tracksegment of the instruction. If
210154
* Instruction does not contain enough coordinate points, an empty string will be returned.
211155
*/
212-
String calcDirection(Instruction nextI) {
156+
public String calcDirection(Instruction nextI) {
213157
double azimuth = calcAzimuth(nextI);
214158
if (Double.isNaN(azimuth))
215159
return "";
@@ -241,11 +185,6 @@ public double calcAzimuth(Instruction nextI) {
241185
return AC.calcAzimuth(lat, lon, nextLat, nextLon);
242186
}
243187

244-
void checkOne() {
245-
if (points.size() < 1)
246-
throw new IllegalStateException("Instruction must contain at least one point " + toString());
247-
}
248-
249188
/**
250189
* This method returns the length of an Instruction. The length of an instruction is defined by [the
251190
* index of the first point of the next instruction] - [the index of the first point of this instruction].

api/src/main/java/com/graphhopper/util/InstructionList.java

Lines changed: 4 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,13 @@
1717
*/
1818
package com.graphhopper.util;
1919

20-
import java.text.DateFormat;
21-
import java.text.DecimalFormat;
22-
import java.text.DecimalFormatSymbols;
2320
import java.util.*;
2421

2522
/**
2623
* List of instructions.
2724
*/
2825
public class InstructionList extends AbstractList<Instruction> {
2926

30-
static String simpleXMLEscape(String str) {
31-
// We could even use the 'more flexible' CDATA section but for now do the following. The 'and' could be important sometimes:
32-
return str.replaceAll("&", "&amp;").
33-
// but do not care for:
34-
replaceAll("[\\<\\>]", "_");
35-
}
36-
3727
private final List<Instruction> instructions;
3828
private final Translation tr;
3929

@@ -71,211 +61,6 @@ public Instruction remove(int index) {
7161
return instructions.remove(index);
7262
}
7363

74-
public void replaceLast(Instruction instr) {
75-
if (instructions.isEmpty())
76-
throw new IllegalStateException("Cannot replace last instruction as list is empty");
77-
78-
instructions.set(instructions.size() - 1, instr);
79-
}
80-
81-
public List<Map<String, Object>> createJson() {
82-
List<Map<String, Object>> instrList = new ArrayList<>(instructions.size());
83-
int pointsIndex = 0;
84-
int counter = 0;
85-
for (Instruction instruction : instructions) {
86-
Map<String, Object> instrJson = new HashMap<>();
87-
instrList.add(instrJson);
88-
89-
InstructionAnnotation ia = instruction.getAnnotation();
90-
String text = instruction.getTurnDescription(tr);
91-
if (Helper.isEmpty(text))
92-
text = ia.getMessage();
93-
instrJson.put("text", Helper.firstBig(text));
94-
if (!ia.isEmpty()) {
95-
instrJson.put("annotation_text", ia.getMessage());
96-
instrJson.put("annotation_importance", ia.getImportance());
97-
}
98-
99-
instrJson.put("street_name", instruction.getName());
100-
instrJson.put("time", instruction.getTime());
101-
instrJson.put("distance", Helper.round(instruction.getDistance(), 3));
102-
instrJson.put("sign", instruction.getSign());
103-
instrJson.putAll(instruction.getExtraInfoJSON());
104-
105-
int tmpIndex = pointsIndex + instruction.getLength();
106-
instrJson.put("interval", Arrays.asList(pointsIndex, tmpIndex));
107-
pointsIndex = tmpIndex;
108-
109-
counter++;
110-
}
111-
return instrList;
112-
}
113-
114-
/**
115-
* @return This method returns a list of gpx entries where the time (in millis) is relative to
116-
* the first which is 0.
117-
*/
118-
public List<GPXEntry> createGPXList() {
119-
if (isEmpty())
120-
return Collections.emptyList();
121-
122-
List<GPXEntry> gpxList = new ArrayList<>();
123-
long timeOffset = 0;
124-
for (int i = 0; i < size() - 1; i++) {
125-
Instruction prevInstr = (i > 0) ? get(i - 1) : null;
126-
boolean instrIsFirst = prevInstr == null;
127-
Instruction nextInstr = get(i + 1);
128-
nextInstr.checkOne();
129-
// current instruction does not contain last point which is equals to first point of next instruction:
130-
timeOffset = get(i).fillGPXList(gpxList, timeOffset, prevInstr, nextInstr, instrIsFirst);
131-
}
132-
Instruction lastI = get(size() - 1);
133-
if (lastI.points.size() != 1)
134-
throw new IllegalStateException("Last instruction must have exactly one point but was " + lastI.points.size());
135-
double lastLat = lastI.getFirstLat(), lastLon = lastI.getFirstLon(),
136-
lastEle = lastI.getPoints().is3D() ? lastI.getFirstEle() : Double.NaN;
137-
gpxList.add(new GPXEntry(lastLat, lastLon, lastEle, timeOffset));
138-
return gpxList;
139-
}
140-
141-
/**
142-
* Creates the standard GPX string out of the points according to the schema found here:
143-
* https://graphhopper.com/public/schema/gpx-1.1.xsd
144-
* <p>
145-
*
146-
* @return string to be stored as gpx file
147-
*/
148-
public String createGPX(String version) {
149-
return createGPX("GraphHopper", new Date().getTime(), version);
150-
}
151-
152-
public String createGPX(String trackName, long startTimeMillis, String version) {
153-
boolean includeElevation = size() > 0 && get(0).getPoints().is3D();
154-
return createGPX(trackName, startTimeMillis, includeElevation, true, true, true, version);
155-
}
156-
157-
private void createWayPointBlock(StringBuilder output, Instruction instruction, DecimalFormat decimalFormat) {
158-
output.append("\n<wpt ");
159-
output.append("lat=\"").append(decimalFormat.format(instruction.getFirstLat()));
160-
output.append("\" lon=\"").append(decimalFormat.format(instruction.getFirstLon())).append("\">");
161-
String name;
162-
if (instruction.getName().isEmpty())
163-
name = instruction.getTurnDescription(tr);
164-
else
165-
name = instruction.getName();
166-
167-
output.append(" <name>").append(simpleXMLEscape(name)).append("</name>");
168-
output.append("</wpt>");
169-
}
170-
171-
public String createGPX(String trackName, long startTimeMillis, boolean includeElevation, boolean withRoute, boolean withTrack, boolean withWayPoints, String version) {
172-
DateFormat formatter = Helper.createFormatter();
173-
174-
DecimalFormat decimalFormat = new DecimalFormat("#", DecimalFormatSymbols.getInstance(Locale.ROOT));
175-
decimalFormat.setMinimumFractionDigits(1);
176-
decimalFormat.setMaximumFractionDigits(6);
177-
decimalFormat.setMinimumIntegerDigits(1);
178-
179-
String header = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>"
180-
+ "<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
181-
+ " creator=\"Graphhopper version " + version + "\" version=\"1.1\""
182-
// This xmlns:gh acts only as ID, no valid URL necessary.
183-
// Use a separate namespace for custom extensions to make basecamp happy.
184-
+ " xmlns:gh=\"https://graphhopper.com/public/schema/gpx/1.1\">"
185-
+ "\n<metadata>"
186-
+ "<copyright author=\"OpenStreetMap contributors\"/>"
187-
+ "<link href=\"http://graphhopper.com\">"
188-
+ "<text>GraphHopper GPX</text>"
189-
+ "</link>"
190-
+ "<time>" + formatter.format(startTimeMillis) + "</time>"
191-
+ "</metadata>";
192-
StringBuilder gpxOutput = new StringBuilder(header);
193-
if (!isEmpty()) {
194-
if (withWayPoints) {
195-
createWayPointBlock(gpxOutput, instructions.get(0), decimalFormat); // Start
196-
for (Instruction currInstr : instructions) {
197-
if ((currInstr.getSign() == Instruction.REACHED_VIA) // Via
198-
|| (currInstr.getSign() == Instruction.FINISH)) // End
199-
{
200-
createWayPointBlock(gpxOutput, currInstr, decimalFormat);
201-
}
202-
}
203-
}
204-
if (withRoute) {
205-
gpxOutput.append("\n<rte>");
206-
Instruction nextInstr = null;
207-
for (Instruction currInstr : instructions) {
208-
if (null != nextInstr)
209-
createRteptBlock(gpxOutput, nextInstr, currInstr, decimalFormat);
210-
211-
nextInstr = currInstr;
212-
}
213-
createRteptBlock(gpxOutput, nextInstr, null, decimalFormat);
214-
gpxOutput.append("\n</rte>");
215-
}
216-
}
217-
if (withTrack) {
218-
gpxOutput.append("\n<trk><name>").append(trackName).append("</name>");
219-
220-
gpxOutput.append("<trkseg>");
221-
for (GPXEntry entry : createGPXList()) {
222-
gpxOutput.append("\n<trkpt lat=\"").append(decimalFormat.format(entry.getLat()));
223-
gpxOutput.append("\" lon=\"").append(decimalFormat.format(entry.getLon())).append("\">");
224-
if (includeElevation)
225-
gpxOutput.append("<ele>").append(Helper.round2(entry.getEle())).append("</ele>");
226-
gpxOutput.append("<time>").append(formatter.format(startTimeMillis + entry.getTime())).append("</time>");
227-
gpxOutput.append("</trkpt>");
228-
}
229-
gpxOutput.append("\n</trkseg>");
230-
gpxOutput.append("\n</trk>");
231-
}
232-
233-
// we could now use 'wpt' for via points
234-
gpxOutput.append("\n</gpx>");
235-
return gpxOutput.toString();
236-
}
237-
238-
public void createRteptBlock(StringBuilder output, Instruction instruction, Instruction nextI, DecimalFormat decimalFormat) {
239-
output.append("\n<rtept lat=\"").append(decimalFormat.format(instruction.getFirstLat())).
240-
append("\" lon=\"").append(decimalFormat.format(instruction.getFirstLon())).append("\">");
241-
242-
if (!instruction.getName().isEmpty())
243-
output.append("<desc>").append(simpleXMLEscape(instruction.getTurnDescription(tr))).append("</desc>");
244-
245-
output.append("<extensions>");
246-
output.append("<gh:distance>").append(Helper.round(instruction.getDistance(), 1)).append("</gh:distance>");
247-
output.append("<gh:time>").append(instruction.getTime()).append("</gh:time>");
248-
249-
String direction = instruction.calcDirection(nextI);
250-
if (!direction.isEmpty())
251-
output.append("<gh:direction>").append(direction).append("</gh:direction>");
252-
253-
double azimuth = instruction.calcAzimuth(nextI);
254-
if (!Double.isNaN(azimuth))
255-
output.append("<gh:azimuth>").append(Helper.round2(azimuth)).append("</gh:azimuth>");
256-
257-
if (instruction instanceof RoundaboutInstruction) {
258-
RoundaboutInstruction ri = (RoundaboutInstruction) instruction;
259-
260-
output.append("<gh:exit_number>").append(ri.getExitNumber()).append("</gh:exit_number>");
261-
}
262-
263-
output.append("<gh:sign>").append(instruction.getSign()).append("</gh:sign>");
264-
output.append("</extensions>");
265-
output.append("</rtept>");
266-
}
267-
268-
/**
269-
* @return list of lat lon
270-
*/
271-
List<List<Double>> createStartPoints() {
272-
List<List<Double>> res = new ArrayList<>(instructions.size());
273-
for (Instruction instruction : instructions) {
274-
res.add(Arrays.asList(instruction.getFirstLat(), instruction.getFirstLon()));
275-
}
276-
return res;
277-
}
278-
27964
/**
28065
* This method is useful for navigation devices to find the next instruction for the specified
28166
* coordinate (e.g. the current position).
@@ -339,4 +124,8 @@ public Instruction find(double lat, double lon, double maxDistance) {
339124
return get(foundInstruction);
340125
}
341126

127+
public Translation getTr() {
128+
return tr;
129+
}
130+
342131
}

api/src/main/java/com/graphhopper/util/PointList.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public int size() {
136136
}
137137

138138
@Override
139-
public GHPoint3D toGHPoint(int index) {
139+
public GHPoint3D get(int index) {
140140
throw new UnsupportedOperationException("cannot access EMPTY PointList");
141141
}
142142

@@ -571,7 +571,7 @@ public void parse2DJSON(String str) {
571571
}
572572
}
573573

574-
public GHPoint3D toGHPoint(int index) {
574+
public GHPoint3D get(int index) {
575575
return new GHPoint3D(getLatitude(index), getLongitude(index), getElevation(index));
576576
}
577577

@@ -594,7 +594,7 @@ public GHPoint3D next() {
594594
if (counter >= getSize())
595595
throw new NoSuchElementException();
596596

597-
GHPoint3D point = PointList.this.toGHPoint(counter);
597+
GHPoint3D point = PointList.this.get(counter);
598598
counter++;
599599
return point;
600600
}

client-hc/src/test/java/com/graphhopper/api/GraphHopperWebIT.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -235,22 +235,6 @@ public void testExportWithoutTrack() {
235235
assertTrue(res.endsWith("</gpx>"));
236236
}
237237

238-
@Test
239-
public void testCreateGPXFromInstructionList() {
240-
GHRequest req = new GHRequest().
241-
addPoint(new GHPoint(49.6724, 11.3494)).
242-
addPoint(new GHPoint(49.6550, 11.4180));
243-
req.getHints().put("elevation", false);
244-
req.getHints().put("instructions", true);
245-
req.getHints().put("calc_points", true);
246-
GHResponse ghResponse = gh.route(req);
247-
String gpx = ghResponse.getBest().getInstructions().createGPX("wurst");
248-
assertTrue(gpx.contains("<gpx"));
249-
assertTrue(gpx.contains("<rtept lat="));
250-
assertTrue(gpx.contains("<trk><name>"));
251-
assertTrue(gpx.endsWith("</gpx>"));
252-
}
253-
254238
void isBetween(double from, double to, double expected) {
255239
assertTrue("expected value " + expected + " was smaller than limit " + from, expected >= from);
256240
assertTrue("expected value " + expected + " was bigger than limit " + to, expected <= to);

0 commit comments

Comments
 (0)