Skip to content

Commit 3583947

Browse files
Make CompactShape more robust in the face of very large sequence number gaps
1 parent f7b2aa7 commit 3583947

File tree

3 files changed

+61
-74
lines changed

3 files changed

+61
-74
lines changed
Lines changed: 37 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package org.opentripplanner.gtfs.mapping;
22

3-
import java.util.Arrays;
3+
import gnu.trove.list.TDoubleList;
4+
import gnu.trove.list.array.TDoubleArrayList;
5+
import gnu.trove.map.TIntIntMap;
6+
import gnu.trove.map.hash.TIntIntHashMap;
47
import java.util.Iterator;
8+
import java.util.PrimitiveIterator;
9+
import java.util.stream.IntStream;
510
import org.opentripplanner.model.ShapePoint;
611

712
/**
@@ -19,95 +24,56 @@
1924
class CompactShape implements Iterable<ShapePoint> {
2025

2126
private static final double NO_VALUE = -9999;
22-
private static final int INCREASE = 50;
23-
private double[] lats;
24-
private double[] lons;
25-
// we only initialize this if we need it
26-
private double[] distTraveleds;
27-
private int maxSequence = (int) NO_VALUE;
27+
private static final int CAPACITY = 50;
28+
private final TDoubleList lats = new TDoubleArrayList(CAPACITY, NO_VALUE);
29+
private final TDoubleList lons = new TDoubleArrayList(CAPACITY, NO_VALUE);
30+
private final TDoubleList distTraveleds = new TDoubleArrayList(CAPACITY, NO_VALUE);
31+
/**
32+
* The mapping from GTFS sequence number to index in the array lists. This allows the
33+
* shape points to be inserted in any order and the sequence number to have very large
34+
* gaps - at the cost of consuming a bit more memory.
35+
*/
36+
private final TIntIntMap seqIndex = new TIntIntHashMap();
2837

29-
public CompactShape() {
30-
this.lats = new double[INCREASE];
31-
this.lons = new double[INCREASE];
32-
Arrays.fill(this.lats, NO_VALUE);
33-
Arrays.fill(this.lons, NO_VALUE);
34-
// distTraveleds is created on demand if required
35-
}
38+
CompactShape() {}
3639

3740
public void addPoint(org.onebusaway.gtfs.model.ShapePoint shapePoint) {
38-
int index = shapePoint.getSequence();
39-
ensureLatLonCapacity(index);
40-
lats[index] = shapePoint.getLat();
41-
lons[index] = shapePoint.getLon();
41+
lats.add(shapePoint.getLat());
42+
lons.add(shapePoint.getLon());
4243
if (shapePoint.isDistTraveledSet()) {
43-
ensureDistTraveledCapacity(index);
44-
distTraveleds[index] = shapePoint.getDistTraveled();
45-
}
46-
if (maxSequence < index) {
47-
maxSequence = index;
44+
distTraveleds.add(shapePoint.getDistTraveled());
45+
} else {
46+
distTraveleds.add(NO_VALUE);
4847
}
48+
49+
int seq = shapePoint.getSequence();
50+
seqIndex.put(seq, lats.size() - 1);
4951
}
5052

5153
@Override
5254
public Iterator<ShapePoint> iterator() {
5355
return new Iterator<>() {
54-
private int index = 0;
56+
private final PrimitiveIterator.OfInt seqIterator = IntStream.of(seqIndex.keys())
57+
.sorted()
58+
.iterator();
5559

5660
@Override
5761
public boolean hasNext() {
58-
return index <= maxSequence;
62+
return seqIterator.hasNext();
5963
}
6064

6165
@Override
6266
public ShapePoint next() {
63-
var lat = lats[index];
64-
while (lat == NO_VALUE) {
65-
index++;
66-
lat = lats[index];
67-
}
68-
var lon = lons[index];
69-
Double distTraveled = null;
70-
if (distTraveleds != null && index - 1 < distTraveleds.length) {
71-
distTraveled = distTraveleds[index];
72-
if (distTraveled == NO_VALUE) {
73-
distTraveled = null;
74-
}
67+
var seq = seqIterator.nextInt();
68+
var index = seqIndex.get(seq);
69+
var lat = lats.get(index);
70+
var lon = lons.get(index);
71+
Double distTraveled = distTraveleds.get(index);
72+
if (distTraveled == NO_VALUE) {
73+
distTraveled = null;
7574
}
76-
var ret = new ShapePoint(index, lat, lon, distTraveled);
77-
index++;
78-
return ret;
75+
return new ShapePoint(seq, lat, lon, distTraveled);
7976
}
8077
};
8178
}
82-
83-
private void ensureLatLonCapacity(int index) {
84-
if (lats.length - 1 < index) {
85-
int oldLength = lats.length;
86-
int newLength = increaseCapacity(index);
87-
lats = Arrays.copyOf(lats, newLength);
88-
lons = Arrays.copyOf(lons, newLength);
89-
for (var i = oldLength; i < newLength; i++) {
90-
lats[i] = NO_VALUE;
91-
lons[i] = NO_VALUE;
92-
}
93-
}
94-
}
95-
96-
private void ensureDistTraveledCapacity(int index) {
97-
if (this.distTraveleds == null) {
98-
this.distTraveleds = new double[Math.max(INCREASE, index + 1)];
99-
Arrays.fill(distTraveleds, NO_VALUE);
100-
} else if (distTraveleds.length - 1 < index) {
101-
int oldLength = distTraveleds.length;
102-
int newLength = increaseCapacity(index);
103-
this.distTraveleds = Arrays.copyOf(distTraveleds, newLength);
104-
for (var i = oldLength; i < newLength; i++) {
105-
distTraveleds[i] = NO_VALUE;
106-
}
107-
}
108-
}
109-
110-
private static int increaseCapacity(int index) {
111-
return index + INCREASE;
112-
}
11379
}

application/src/main/java/org/opentripplanner/gtfs/mapping/ShapePointMapper.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ Map<FeedScopedId, CompactShape> map(
2020
var ret = new HashMap<FeedScopedId, CompactShape>();
2121
for (var shapePoint : allShapePoints) {
2222
var shapeId = idFactory.createId(shapePoint.getShapeId(), "shape point");
23-
var shapeBuilder = ret.getOrDefault(shapeId, new CompactShape());
24-
shapeBuilder.addPoint(shapePoint);
25-
ret.put(shapeId, shapeBuilder);
23+
var shape = ret.get(shapeId);
24+
if (shape == null) {
25+
shape = new CompactShape();
26+
ret.put(shapeId, shape);
27+
}
28+
shape.addPoint(shapePoint);
2629
}
2730

2831
return ret;

application/src/test/java/org/opentripplanner/gtfs/mapping/CompactShapeTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,24 @@ void zero() {
122122
);
123123
}
124124

125+
@Test
126+
void largeSeq() {
127+
var shape = new CompactShape();
128+
129+
shape.addPoint(shapePoint(0, 1, 1, 1d));
130+
shape.addPoint(shapePoint(10_000, 2, 2, 2d));
131+
shape.addPoint(shapePoint(30_000, 3, 3, 4d));
132+
shape.addPoint(shapePoint(40_000_000, 4, 4, 5d));
133+
shape.addPoint(shapePoint(Integer.MAX_VALUE, 5, 5, 6d));
134+
135+
var points = ImmutableList.copyOf(shape);
136+
137+
assertEquals(
138+
"[0 (1.0, 1.0) dist=1.0, 10,000 (2.0, 2.0) dist=2.0, 30,000 (3.0, 3.0) dist=4.0, 40,000,000 (4.0, 4.0) dist=5.0, 2,147,483,647 (5.0, 5.0) dist=6.0]",
139+
points.toString()
140+
);
141+
}
142+
125143
private static ShapePoint shapePoint(int sequence, double lat, double lon) {
126144
return shapePoint(sequence, lat, lon, null);
127145
}

0 commit comments

Comments
 (0)