Skip to content

Commit cbce8e6

Browse files
authored
Use flat data structure to store lists of points (#1630)
1 parent 5eddaf6 commit cbce8e6

26 files changed

Lines changed: 1729 additions & 304 deletions

.github/CODEOWNERS

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
* @mapbox/navigation-android
1+
* @mapbox/navigation-android
2+
3+
# GeoJSON and Turf can be approved by either navigation-android or maps-android teams
4+
/services-geojson/ @mapbox/navigation-android @mapbox/maps-android
5+
/services-turf/ @mapbox/navigation-android @mapbox/maps-android

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Mapbox welcomes participation and contributions from everyone.
44

55
### main
66
- Added `DirectionsRefreshResponse#fromJson(Reader)`, a static factory method that deserializes a `DirectionsRefreshResponse` from a `java.io.Reader`.
7+
- Added `FlattenListOfPoints` to hold a list of points in a more memory-efficient way.
8+
- Deprecate `LineString#coordinates()` and `Point#coordinates()`. It's encouraged to use the new `flattenCoordinates()` methods.
9+
710

811
### v7.9.0 - November 20, 2025
912

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
package com.mapbox.geojson;
22

33
import androidx.annotation.Keep;
4+
import androidx.annotation.NonNull;
45

56
import com.google.gson.TypeAdapter;
67
import com.google.gson.stream.JsonReader;
78
import com.google.gson.stream.JsonToken;
89
import com.google.gson.stream.JsonWriter;
9-
import com.mapbox.geojson.exception.GeoJsonException;
1010
import com.mapbox.geojson.shifter.CoordinateShifterManager;
1111
import com.mapbox.geojson.utils.GeoJsonUtils;
1212

1313
import java.io.IOException;
14-
import java.util.ArrayList;
15-
import java.util.List;
1614

1715
/**
18-
* Base class for converting {@code T} instance of coordinates to JSON and
19-
* JSON to instance of {@code T}.
16+
* Base class for converting {@code T} instance of coordinates to JSON and
17+
* JSON to instance of {@code T}.
2018
*
2119
* @param <T> Type of coordinates
2220
* @since 4.6.0
@@ -25,25 +23,19 @@
2523
abstract class BaseCoordinatesTypeAdapter<T> extends TypeAdapter<T> {
2624

2725

28-
protected void writePoint(JsonWriter out, Point point) throws IOException {
26+
protected void writePoint(JsonWriter out, Point point) throws IOException {
2927
if (point == null) {
3028
return;
3129
}
32-
writePointList(out, point.coordinates());
30+
writePointList(out, point.flattenCoordinates());
3331
}
3432

3533
protected Point readPoint(JsonReader in) throws IOException {
36-
37-
List<Double> coordinates = readPointList(in);
38-
if (coordinates != null && coordinates.size() > 1) {
39-
return new Point("Point",null, coordinates);
40-
}
41-
42-
throw new GeoJsonException(" Point coordinates should be non-null double array");
34+
return new Point("Point", null, readPointList(in));
4335
}
4436

4537

46-
protected void writePointList(JsonWriter out, List<Double> value) throws IOException {
38+
protected void writePointList(JsonWriter out, double[] value) throws IOException {
4739

4840
if (value == null) {
4941
return;
@@ -52,38 +44,52 @@ protected void writePointList(JsonWriter out, List<Double> value) throws IOExcep
5244
out.beginArray();
5345

5446
// Unshift coordinates
55-
List<Double> unshiftedCoordinates =
56-
CoordinateShifterManager.getCoordinateShifter().unshiftPoint(value);
47+
double[] unshiftedCoordinates = CoordinateShifterManager.getCoordinateShifter()
48+
.unshiftPointArray(value);
5749

58-
out.value(GeoJsonUtils.trim(unshiftedCoordinates.get(0)));
59-
out.value(GeoJsonUtils.trim(unshiftedCoordinates.get(1)));
50+
out.value(GeoJsonUtils.trim(unshiftedCoordinates[0]));
51+
out.value(GeoJsonUtils.trim(unshiftedCoordinates[1]));
6052

6153
// Includes altitude
62-
if (value.size() > 2) {
63-
out.value(unshiftedCoordinates.get(2));
54+
if (value.length > 2) {
55+
out.value(unshiftedCoordinates[2]);
6456
}
6557
out.endArray();
6658
}
6759

68-
protected List<Double> readPointList(JsonReader in) throws IOException {
69-
60+
@NonNull
61+
protected double[] readPointList(JsonReader in) throws IOException {
7062
if (in.peek() == JsonToken.NULL) {
7163
throw new NullPointerException();
7264
}
7365

74-
List<Double> coordinates = new ArrayList<Double>(3);
66+
double lon;
67+
double lat;
68+
double altitude;
7569
in.beginArray();
76-
while (in.hasNext()) {
77-
coordinates.add(in.nextDouble());
70+
if (in.hasNext()) {
71+
lon = in.nextDouble();
72+
} else {
73+
throw new IndexOutOfBoundsException("Point coordinates should contain at least two values");
7874
}
79-
in.endArray();
80-
81-
if (coordinates.size() > 2) {
82-
return CoordinateShifterManager.getCoordinateShifter()
83-
.shiftLonLatAlt(coordinates.get(0), coordinates.get(1), coordinates.get(2));
75+
if (in.hasNext()) {
76+
lat = in.nextDouble();
77+
} else {
78+
throw new IndexOutOfBoundsException("Point coordinates should contain at least two values");
79+
}
80+
if (in.hasNext()) {
81+
altitude = in.nextDouble();
82+
// Consume any extra value but don't store it
83+
while (in.hasNext()) {
84+
in.skipValue();
85+
}
86+
in.endArray();
87+
return CoordinateShifterManager.getCoordinateShifter().shift(lon, lat, altitude);
88+
} else {
89+
in.endArray();
90+
return CoordinateShifterManager.getCoordinateShifter().shift(lon, lat);
8491
}
85-
return CoordinateShifterManager.getCoordinateShifter()
86-
.shiftLonLat(coordinates.get(0), coordinates.get(1));
92+
8793
}
8894

8995
}

services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,68 @@
1818
*
1919
* @param <G> Geometry
2020
* @param <T> Type of coordinates
21+
* @param <A> The type of coordinates adapter
2122
* @since 4.6.0
2223
*/
2324
@Keep
24-
abstract class BaseGeometryTypeAdapter<G, T> extends TypeAdapter<G> {
25+
abstract class BaseGeometryTypeAdapter<G, T, A> extends TypeAdapter<G> {
2526

2627
private volatile TypeAdapter<String> stringAdapter;
2728
private volatile TypeAdapter<BoundingBox> boundingBoxAdapter;
28-
private volatile TypeAdapter<T> coordinatesAdapter;
29+
private final BaseCoordinatesTypeAdapter<A> coordinatesAdapter;
2930

3031
private final Gson gson;
3132

32-
BaseGeometryTypeAdapter(Gson gson, TypeAdapter<T> coordinatesAdapter) {
33+
BaseGeometryTypeAdapter(Gson gson, BaseCoordinatesTypeAdapter<A> coordinatesAdapter) {
34+
if (coordinatesAdapter == null) {
35+
throw new GeoJsonException("Coordinates type adapter is null");
36+
}
3337
this.gson = gson;
3438
this.coordinatesAdapter = coordinatesAdapter;
3539
this.boundingBoxAdapter = new BoundingBoxTypeAdapter();
3640
}
3741

38-
public void writeCoordinateContainer(JsonWriter jsonWriter, CoordinateContainer<T> object)
42+
public void writeFlattenedCoordinateContainer(
43+
JsonWriter jsonWriter,
44+
FlattenedCoordinateContainer<T, A> object
45+
) throws IOException {
46+
if (object == null) {
47+
jsonWriter.nullValue();
48+
return;
49+
}
50+
writeCommon(jsonWriter, object);
51+
jsonWriter.name("coordinates");
52+
coordinatesAdapter.write(jsonWriter, object.flattenCoordinates());
53+
jsonWriter.endObject();
54+
}
55+
56+
public void writeCoordinateContainer(JsonWriter jsonWriter, CoordinateContainer<A> object)
3957
throws IOException {
4058
if (object == null) {
4159
jsonWriter.nullValue();
4260
return;
4361
}
62+
63+
writeCommon(jsonWriter, object);
64+
65+
jsonWriter.name("coordinates");
66+
if (object.coordinates() == null) {
67+
jsonWriter.nullValue();
68+
} else {
69+
coordinatesAdapter.write(jsonWriter, object.coordinates());
70+
}
71+
72+
jsonWriter.endObject();
73+
}
74+
75+
/**
76+
* Write the common part of the coordinate container: "type" and "bbox".
77+
*/
78+
private void writeCommon(
79+
JsonWriter jsonWriter,
80+
@SuppressWarnings("rawtypes")
81+
CoordinateContainer object
82+
) throws IOException {
4483
jsonWriter.beginObject();
4584
jsonWriter.name("type");
4685
if (object.type() == null) {
@@ -64,17 +103,6 @@ public void writeCoordinateContainer(JsonWriter jsonWriter, CoordinateContainer<
64103
}
65104
boundingBoxAdapter.write(jsonWriter, object.bbox());
66105
}
67-
jsonWriter.name("coordinates");
68-
if (object.coordinates() == null) {
69-
jsonWriter.nullValue();
70-
} else {
71-
TypeAdapter<T> coordinatesAdapter = this.coordinatesAdapter;
72-
if (coordinatesAdapter == null) {
73-
throw new GeoJsonException("Coordinates type adapter is null");
74-
}
75-
coordinatesAdapter.write(jsonWriter, object.coordinates());
76-
}
77-
jsonWriter.endObject();
78106
}
79107

80108
public CoordinateContainer<T> readCoordinateContainer(JsonReader jsonReader) throws IOException {
@@ -86,7 +114,7 @@ public CoordinateContainer<T> readCoordinateContainer(JsonReader jsonReader) thr
86114
jsonReader.beginObject();
87115
String type = null;
88116
BoundingBox bbox = null;
89-
T coordinates = null;
117+
A coordinates = null;
90118

91119
while (jsonReader.hasNext()) {
92120
String name = jsonReader.nextName();
@@ -114,7 +142,7 @@ public CoordinateContainer<T> readCoordinateContainer(JsonReader jsonReader) thr
114142
break;
115143

116144
case "coordinates":
117-
TypeAdapter<T> coordinatesAdapter = this.coordinatesAdapter;
145+
TypeAdapter<A> coordinatesAdapter = this.coordinatesAdapter;
118146
if (coordinatesAdapter == null) {
119147
throw new GeoJsonException("Coordinates type adapter is null");
120148
}
@@ -133,5 +161,5 @@ public CoordinateContainer<T> readCoordinateContainer(JsonReader jsonReader) thr
133161

134162
abstract CoordinateContainer<T> createCoordinateContainer(String type,
135163
BoundingBox bbox,
136-
T coordinates);
164+
A coordinates);
137165
}

0 commit comments

Comments
 (0)