From 8c69ad489edef28ce50fe75435319c65fb0fe535 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 23 Jan 2026 17:04:42 +0200 Subject: [PATCH 01/31] Use primitive array in Point instead of List --- .../geojson/BaseCoordinatesTypeAdapter.java | 66 +++++++------- .../geojson/BaseGeometryTypeAdapter.java | 61 +++++++++---- .../java/com/mapbox/geojson/LineString.java | 2 +- .../ListOfDoublesCoordinatesTypeAdapter.java | 9 +- .../com/mapbox/geojson/MultiLineString.java | 2 +- .../java/com/mapbox/geojson/MultiPoint.java | 2 +- .../java/com/mapbox/geojson/MultiPolygon.java | 2 +- .../main/java/com/mapbox/geojson/Point.java | 90 +++++++++++-------- .../main/java/com/mapbox/geojson/Polygon.java | 2 +- .../geojson/PrimitiveCoordinateContainer.java | 5 ++ .../geojson/shifter/CoordinateShifter.java | 4 + .../shifter/CoordinateShifterManager.java | 19 ++++ .../com/mapbox/geojson/LineStringTest.java | 3 +- .../mapbox/geojson/shifter/ShifterTest.java | 56 ++++++++++++ .../java/com/mapbox/turf/TurfMiscTest.java | 37 +++++--- 15 files changed, 252 insertions(+), 108 deletions(-) create mode 100644 services-geojson/src/main/java/com/mapbox/geojson/PrimitiveCoordinateContainer.java diff --git a/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java index 752c43efb..7eb27c2b6 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java @@ -1,18 +1,16 @@ package com.mapbox.geojson; import androidx.annotation.Keep; +import androidx.annotation.NonNull; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; -import com.mapbox.geojson.exception.GeoJsonException; import com.mapbox.geojson.shifter.CoordinateShifterManager; import com.mapbox.geojson.utils.GeoJsonUtils; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; /** * Base class for converting {@code T} instance of coordinates to JSON and @@ -29,21 +27,15 @@ protected void writePoint(JsonWriter out, Point point) throws IOException { if (point == null) { return; } - writePointList(out, point.coordinates()); + writePointList(out, point.coordinatesPrimitives()); } protected Point readPoint(JsonReader in) throws IOException { - - List coordinates = readPointList(in); - if (coordinates != null && coordinates.size() > 1) { - return new Point("Point",null, coordinates); - } - - throw new GeoJsonException(" Point coordinates should be non-null double array"); + return new Point("Point",null, readPointList(in)); } - protected void writePointList(JsonWriter out, List value) throws IOException { + protected void writePointList(JsonWriter out, double[] value) throws IOException { if (value == null) { return; @@ -52,38 +44,52 @@ protected void writePointList(JsonWriter out, List value) throws IOExcep out.beginArray(); // Unshift coordinates - List unshiftedCoordinates = - CoordinateShifterManager.getCoordinateShifter().unshiftPoint(value); + double[] unshiftedCoordinates = + CoordinateShifterManager.getCoordinateShifter().unshiftPointArray(value); - out.value(GeoJsonUtils.trim(unshiftedCoordinates.get(0))); - out.value(GeoJsonUtils.trim(unshiftedCoordinates.get(1))); + out.value(GeoJsonUtils.trim(unshiftedCoordinates[0])); + out.value(GeoJsonUtils.trim(unshiftedCoordinates[1])); // Includes altitude - if (value.size() > 2) { - out.value(unshiftedCoordinates.get(2)); + if (value.length > 2) { + out.value(unshiftedCoordinates[2]); } out.endArray(); } - protected List readPointList(JsonReader in) throws IOException { - + @NonNull + protected double[] readPointList(JsonReader in) throws IOException { if (in.peek() == JsonToken.NULL) { throw new NullPointerException(); } - List coordinates = new ArrayList(3); + double coordinate0; + double coordinate1; + double coordinate2; in.beginArray(); - while (in.hasNext()) { - coordinates.add(in.nextDouble()); + if (in.hasNext()) { + coordinate0 = in.nextDouble(); + } else { + throw new IndexOutOfBoundsException("Point coordinates should contain at least two values"); } - in.endArray(); - - if (coordinates.size() > 2) { - return CoordinateShifterManager.getCoordinateShifter() - .shiftLonLatAlt(coordinates.get(0), coordinates.get(1), coordinates.get(2)); + if (in.hasNext()) { + coordinate1 = in.nextDouble(); + } else { + throw new IndexOutOfBoundsException("Point coordinates should contain at least two values"); + } + if (in.hasNext()) { + coordinate2 = in.nextDouble(); + // Consume any extra value but don't store it + while (in.hasNext()) { + in.skipValue(); + } + in.endArray(); + return CoordinateShifterManager.getCoordinateShifter().shift(coordinate0, coordinate1, coordinate2); + } else { + in.endArray(); + return CoordinateShifterManager.getCoordinateShifter().shift(coordinate0, coordinate1); } - return CoordinateShifterManager.getCoordinateShifter() - .shiftLonLat(coordinates.get(0), coordinates.get(1)); + } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java index 47ed7c913..e3cf635e9 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java @@ -21,26 +21,62 @@ * @since 4.6.0 */ @Keep -abstract class BaseGeometryTypeAdapter extends TypeAdapter { +abstract class BaseGeometryTypeAdapter extends TypeAdapter { private volatile TypeAdapter stringAdapter; private volatile TypeAdapter boundingBoxAdapter; - private volatile TypeAdapter coordinatesAdapter; + private volatile TypeAdapter coordinatesAdapter; private final Gson gson; - BaseGeometryTypeAdapter(Gson gson, TypeAdapter coordinatesAdapter) { + BaseGeometryTypeAdapter(Gson gson, TypeAdapter coordinatesAdapter) { this.gson = gson; this.coordinatesAdapter = coordinatesAdapter; this.boundingBoxAdapter = new BoundingBoxTypeAdapter(); } - public void writeCoordinateContainer(JsonWriter jsonWriter, CoordinateContainer object) + public void writeCoordinateContainerPrimitive(JsonWriter jsonWriter, PrimitiveCoordinateContainer object) throws IOException { + if (object == null) { + jsonWriter.nullValue(); + return; + } + writeCommon(jsonWriter, object); + jsonWriter.name("coordinates"); + if (object.coordinates() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter coordinatesAdapter = this.coordinatesAdapter; + if (coordinatesAdapter == null) { + throw new GeoJsonException("Coordinates type adapter is null"); + } + coordinatesAdapter.write(jsonWriter, object.coordinatesPrimitives()); + } + jsonWriter.endObject(); + } + public void writeCoordinateContainer(JsonWriter jsonWriter, CoordinateContainer object) throws IOException { if (object == null) { jsonWriter.nullValue(); return; } + + writeCommon(jsonWriter, object); + + jsonWriter.name("coordinates"); + if (object.coordinates() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter coordinatesAdapter = this.coordinatesAdapter; + if (coordinatesAdapter == null) { + throw new GeoJsonException("Coordinates type adapter is null"); + } + coordinatesAdapter.write(jsonWriter, object.coordinates()); + } + + jsonWriter.endObject(); + } + + private void writeCommon(JsonWriter jsonWriter, CoordinateContainer object) throws IOException { jsonWriter.beginObject(); jsonWriter.name("type"); if (object.type() == null) { @@ -64,17 +100,6 @@ public void writeCoordinateContainer(JsonWriter jsonWriter, CoordinateContainer< } boundingBoxAdapter.write(jsonWriter, object.bbox()); } - jsonWriter.name("coordinates"); - if (object.coordinates() == null) { - jsonWriter.nullValue(); - } else { - TypeAdapter coordinatesAdapter = this.coordinatesAdapter; - if (coordinatesAdapter == null) { - throw new GeoJsonException("Coordinates type adapter is null"); - } - coordinatesAdapter.write(jsonWriter, object.coordinates()); - } - jsonWriter.endObject(); } public CoordinateContainer readCoordinateContainer(JsonReader jsonReader) throws IOException { @@ -86,7 +111,7 @@ public CoordinateContainer readCoordinateContainer(JsonReader jsonReader) thr jsonReader.beginObject(); String type = null; BoundingBox bbox = null; - T coordinates = null; + A coordinates = null; while (jsonReader.hasNext()) { String name = jsonReader.nextName(); @@ -114,7 +139,7 @@ public CoordinateContainer readCoordinateContainer(JsonReader jsonReader) thr break; case "coordinates": - TypeAdapter coordinatesAdapter = this.coordinatesAdapter; + TypeAdapter coordinatesAdapter = this.coordinatesAdapter; if (coordinatesAdapter == null) { throw new GeoJsonException("Coordinates type adapter is null"); } @@ -133,5 +158,5 @@ public CoordinateContainer readCoordinateContainer(JsonReader jsonReader) thr abstract CoordinateContainer createCoordinateContainer(String type, BoundingBox bbox, - T coordinates); + A coordinates); } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index e57edb101..b693eed3c 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -299,7 +299,7 @@ public int hashCode() { * * @since 4.6.0 */ - static final class GsonTypeAdapter extends BaseGeometryTypeAdapter> { + static final class GsonTypeAdapter extends BaseGeometryTypeAdapter, List> { GsonTypeAdapter(Gson gson) { super(gson, new ListOfPointCoordinatesTypeAdapter()); diff --git a/services-geojson/src/main/java/com/mapbox/geojson/ListOfDoublesCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/ListOfDoublesCoordinatesTypeAdapter.java index 4be4e86f7..97b9dacb7 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/ListOfDoublesCoordinatesTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/ListOfDoublesCoordinatesTypeAdapter.java @@ -6,23 +6,22 @@ import com.google.gson.stream.JsonWriter; import java.io.IOException; -import java.util.List; /** - * Type Adapter to serialize/deserialize Poinr into/from for double array. + * Type Adapter to serialize/deserialize Point into/from for double array. * * @since 4.6.0 */ @Keep -class ListOfDoublesCoordinatesTypeAdapter extends BaseCoordinatesTypeAdapter> { +class ListOfDoublesCoordinatesTypeAdapter extends BaseCoordinatesTypeAdapter { @Override - public void write(JsonWriter out, List value) throws IOException { + public void write(JsonWriter out, double[] value) throws IOException { writePointList(out, value); } @Override - public List read(JsonReader in) throws IOException { + public double[] read(JsonReader in) throws IOException { return readPointList(in); } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/MultiLineString.java b/services-geojson/src/main/java/com/mapbox/geojson/MultiLineString.java index 659b5ffa0..92096eb17 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiLineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiLineString.java @@ -326,7 +326,7 @@ public int hashCode() { * @since 4.6.0 */ static final class GsonTypeAdapter - extends BaseGeometryTypeAdapter>> { + extends BaseGeometryTypeAdapter>, List>> { GsonTypeAdapter(Gson gson) { super(gson, new ListOfListOfPointCoordinatesTypeAdapter()); diff --git a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java index 4e3757890..ba170041f 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java @@ -221,7 +221,7 @@ public int hashCode() { * * @since 4.6.0 */ - static final class GsonTypeAdapter extends BaseGeometryTypeAdapter> { + static final class GsonTypeAdapter extends BaseGeometryTypeAdapter, List> { GsonTypeAdapter(Gson gson) { super(gson, new ListOfPointCoordinatesTypeAdapter()); diff --git a/services-geojson/src/main/java/com/mapbox/geojson/MultiPolygon.java b/services-geojson/src/main/java/com/mapbox/geojson/MultiPolygon.java index 325479d9c..e9f6ef053 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiPolygon.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiPolygon.java @@ -345,7 +345,7 @@ public int hashCode() { * @since 4.6.0 */ static final class GsonTypeAdapter - extends BaseGeometryTypeAdapter>>> { + extends BaseGeometryTypeAdapter>>, List>>> { GsonTypeAdapter(Gson gson) { super(gson, new ListofListofListOfPointCoordinatesTypeAdapter()); diff --git a/services-geojson/src/main/java/com/mapbox/geojson/Point.java b/services-geojson/src/main/java/com/mapbox/geojson/Point.java index e75b1b0eb..713ef7882 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Point.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Point.java @@ -12,7 +12,10 @@ import com.mapbox.geojson.shifter.CoordinateShifterManager; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; /** * A point represents a single geographic position and is one of the seven Geometries found in the @@ -46,7 +49,7 @@ * @since 1.0.0 */ @Keep -public final class Point implements CoordinateContainer> { +public final class Point implements PrimitiveCoordinateContainer, double[]> { private static final String TYPE = "Point"; @@ -57,7 +60,7 @@ public final class Point implements CoordinateContainer> { private final BoundingBox bbox; @NonNull - private final List coordinates; + private final double[] coordinates; /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are @@ -91,8 +94,8 @@ public static Point fromJson(@NonNull String json) { */ public static Point fromLngLat(double longitude, double latitude) { - List coordinates = - CoordinateShifterManager.getCoordinateShifter().shiftLonLat(longitude, latitude); + double[] coordinates = + CoordinateShifterManager.getCoordinateShifter().shift(longitude, latitude); return new Point(TYPE, null, coordinates); } @@ -113,8 +116,8 @@ public static Point fromLngLat(double longitude, double latitude) { public static Point fromLngLat(double longitude, double latitude, @Nullable BoundingBox bbox) { - List coordinates = - CoordinateShifterManager.getCoordinateShifter().shiftLonLat(longitude, latitude); + double[] coordinates = + CoordinateShifterManager.getCoordinateShifter().shift(longitude, latitude); return new Point(TYPE, bbox, coordinates); } @@ -135,8 +138,8 @@ public static Point fromLngLat(double longitude, double latitude, */ public static Point fromLngLat(double longitude, double latitude, double altitude) { - List coordinates = - CoordinateShifterManager.getCoordinateShifter().shiftLonLatAlt(longitude, latitude, altitude); + double[] coordinates = + CoordinateShifterManager.getCoordinateShifter().shift(longitude, latitude, altitude); return new Point(TYPE, null, coordinates); } @@ -160,28 +163,24 @@ public static Point fromLngLat(double longitude, double latitude, double altitud public static Point fromLngLat(double longitude, double latitude, double altitude, @Nullable BoundingBox bbox) { - List coordinates = - CoordinateShifterManager.getCoordinateShifter().shiftLonLatAlt(longitude, latitude, altitude); + double[] coordinates = + CoordinateShifterManager.getCoordinateShifter().shift(longitude, latitude, altitude); return new Point(TYPE, bbox, coordinates); } static Point fromLngLat(@NonNull double[] coords) { if (coords.length == 2) { return Point.fromLngLat(coords[0], coords[1]); - } else if (coords.length > 2) { return Point.fromLngLat(coords[0], coords[1], coords[2]); } return null; } - Point(String type, @Nullable BoundingBox bbox, List coordinates) { - if (type == null) { - throw new NullPointerException("Null type"); - } + Point(@NonNull String type, @Nullable BoundingBox bbox, @NonNull double[] coordinates) { this.type = type; this.bbox = bbox; - if (coordinates == null || coordinates.size() == 0) { + if (coordinates.length == 0) { throw new NullPointerException("Null coordinates"); } this.coordinates = coordinates; @@ -197,7 +196,7 @@ static Point fromLngLat(@NonNull double[] coords) { * @since 3.0.0 */ public double longitude() { - return coordinates().get(0); + return coordinates[0]; } /** @@ -210,7 +209,7 @@ public double longitude() { * @since 3.0.0 */ public double latitude() { - return coordinates().get(1); + return coordinates[1]; } /** @@ -223,10 +222,10 @@ public double latitude() { * @since 3.0.0 */ public double altitude() { - if (coordinates().size() < 3) { + if (coordinates.length < 3) { return Double.NaN; } - return coordinates().get(2); + return coordinates[2]; } /** @@ -274,14 +273,33 @@ public BoundingBox bbox() { /** * Provide a single double array containing the longitude, latitude, and optionally an * altitude/elevation. {@link #longitude()}, {@link #latitude()}, and {@link #altitude()} are all - * avaliable which make getting specific coordinates more direct. + * available which make getting specific coordinates more direct. * * @return a double array which holds this points coordinates * @since 3.0.0 + * @deprecated Please use {@link #coordinatesPrimitives()} instead. */ @NonNull @Override + @Deprecated public List coordinates() { + ArrayList list = new ArrayList<>(coordinates.length); + for (double coordinate : coordinates) { + list.add(coordinate); + } + return list; + } + + /** + * Provide a single double array containing the longitude, latitude, and optionally an + * altitude/elevation. {@link #longitude()}, {@link #latitude()}, and {@link #altitude()} are all + * available which make getting specific coordinates more direct. + * + * @return a double array which holds this points coordinates + * @since 3.0.0 + */ + @Override + public double[] coordinatesPrimitives() { return coordinates; } @@ -312,25 +330,23 @@ public static TypeAdapter typeAdapter(Gson gson) { @Override public String toString() { + String coordinatesStr; + if (coordinates.length > 2) + coordinatesStr = "[" + this.coordinates[0] + ", " + this.coordinates[1] + ", " + this.coordinates[2] + "]"; + else + coordinatesStr = "[" + this.coordinates[0] + ", " + this.coordinates[1] + "]"; return "Point{" + "type=" + type + ", " + "bbox=" + bbox + ", " - + "coordinates=" + coordinates + + "coordinates=" + coordinatesStr + "}"; } @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof Point) { - Point that = (Point) obj; - return (this.type.equals(that.type())) - && ((this.bbox == null) ? (that.bbox() == null) : this.bbox.equals(that.bbox())) - && (this.coordinates.equals(that.coordinates())); - } - return false; + public boolean equals(Object o) { + if (!(o instanceof Point)) return false; + Point point = (Point) o; + return Objects.equals(type, point.type) && Objects.equals(bbox, point.bbox) && Objects.deepEquals(coordinates, point.coordinates); } @Override @@ -341,7 +357,7 @@ public int hashCode() { hashCode *= 1000003; hashCode ^= (bbox == null) ? 0 : bbox.hashCode(); hashCode *= 1000003; - hashCode ^= coordinates.hashCode(); + hashCode ^= Arrays.hashCode(coordinates); return hashCode; } @@ -350,7 +366,7 @@ public int hashCode() { * * @since 4.6.0 */ - static final class GsonTypeAdapter extends BaseGeometryTypeAdapter> { + static final class GsonTypeAdapter extends BaseGeometryTypeAdapter, double[]> { GsonTypeAdapter(Gson gson) { super(gson, new ListOfDoublesCoordinatesTypeAdapter()); @@ -359,7 +375,7 @@ static final class GsonTypeAdapter extends BaseGeometryTypeAdapter> createCoordinateContainer(String type, BoundingBox bbox, - List coordinates) { + double[] coordinates) { return new Point(type == null ? "Point" : type, bbox, coordinates); } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/Polygon.java b/services-geojson/src/main/java/com/mapbox/geojson/Polygon.java index c40d5791c..df9d02974 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Polygon.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Polygon.java @@ -432,7 +432,7 @@ public int hashCode() { * * @since 4.6.0 */ - static final class GsonTypeAdapter extends BaseGeometryTypeAdapter>> { + static final class GsonTypeAdapter extends BaseGeometryTypeAdapter>, List>> { GsonTypeAdapter(Gson gson) { super(gson, new ListOfListOfPointCoordinatesTypeAdapter()); diff --git a/services-geojson/src/main/java/com/mapbox/geojson/PrimitiveCoordinateContainer.java b/services-geojson/src/main/java/com/mapbox/geojson/PrimitiveCoordinateContainer.java new file mode 100644 index 000000000..f1591b17a --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/PrimitiveCoordinateContainer.java @@ -0,0 +1,5 @@ +package com.mapbox.geojson; + +interface PrimitiveCoordinateContainer extends CoordinateContainer { + P coordinatesPrimitives(); +} diff --git a/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifter.java b/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifter.java index a7cab8894..75d7541bf 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifter.java @@ -54,4 +54,8 @@ public interface CoordinateShifter { * @since 4.2.0 */ List unshiftPoint(List shiftedCoordinates); + + double[] shift(double lon, double lat, double altitude); + double[] shift(double lon, double lat); + double[] unshiftPointArray(double[] shiftedCoordinates); } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifterManager.java b/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifterManager.java index ef9ecb186..40ac56e0d 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifterManager.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifterManager.java @@ -34,6 +34,25 @@ public List unshiftPoint(Point point) { public List unshiftPoint(List coordinates) { return coordinates; } + + @Override + public double[] shift(double lon, double lat) { + return new double[]{lon, lat}; + } + + @Override + public double[] shift(double lon, double lat, double altitude) { + if (Double.isNaN(altitude)){ + return shift(lon, lat); + } else { + return new double[]{lon, lat, altitude}; + } + } + + @Override + public double[] unshiftPointArray(double[] shiftedCoordinates) { + return shiftedCoordinates; + } }; private static volatile CoordinateShifter coordinateShifter = DEFAULT; diff --git a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java index 0e6a8d3fc..c319c32a6 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -122,7 +122,8 @@ public void testSerializable() throws Exception { BoundingBox bbox = BoundingBox.fromLngLats(1.0, 2.0, 3.0, 4.0); LineString lineString = LineString.fromLngLats(points, bbox); byte[] bytes = serialize(lineString); - assertEquals(lineString, deserialize(bytes, LineString.class)); + LineString deserialize = deserialize(bytes, LineString.class); + assertEquals(lineString, deserialize); } @Test diff --git a/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java b/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java index b2430b15b..6475e592f 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java @@ -22,11 +22,25 @@ public List shiftLonLat(double lon, double lat) { return Arrays.asList(lon + 3, lat + 5); } + @Override + public double[] shift(double lon, double lat) { + return new double[]{lon + 3, lat + 5}; + } + @Override public List shiftLonLatAlt(double lon, double lat, double altitude) { return Arrays.asList(lon + 3, lat + 5, altitude + 8); } + @Override + public double[] shift(double lon, double lat, double altitude) { + if (Double.isNaN(altitude)) { + return shift(lon, lat); + } else { + return new double[]{lon, lat, altitude}; + } + } + @Override public List unshiftPoint(Point shiftedPoint) { return Arrays.asList(shiftedPoint.longitude() - 3, @@ -44,6 +58,18 @@ public List unshiftPoint(List coordinates) { return Arrays.asList(coordinates.get(0) - 3, coordinates.get(1) - 5); } + + @Override + public double[] unshiftPointArray(double[] coordinates) { + if (coordinates.length > 2) { + return new double[]{coordinates[0] - 3, + coordinates[1] - 5, + coordinates[2] - 8 + }; + } + return new double[]{coordinates[0] - 3, + coordinates[1] - 5}; + } } @Test @@ -120,6 +146,36 @@ public void bbox_basic_shift() throws Exception { CoordinateShifterManager.setCoordinateShifter(null); } + @Test + public void bbox_basic_shift_primitive() throws Exception { + + Point southwest = Point.fromLngLat(2.0, 2.0); + Point northeast = Point.fromLngLat(4.0, 4.0); + + CoordinateShifter shifter = new TestCoordinateShifter(); + + // Manually shifted + double[] shifted = shifter.shift(southwest.longitude(), southwest.latitude()); + Point southwestManualShifted = Point.fromLngLat(shifted[0], shifted[1]); + shifted = shifter.shift(northeast.longitude(), northeast.latitude()); + Point northeastManualShifted = Point.fromLngLat(shifted[0], shifted[1]); + + CoordinateShifterManager.setCoordinateShifter(shifter); + + BoundingBox boundingBoxFromDouble = BoundingBox.fromLngLats(2.0, 2.0, 4.0, 4.0); + + BoundingBox boundingBoxFromPoints = + BoundingBox.fromPoints(Point.fromLngLat(2.0, 2.0), + Point.fromLngLat(4.0, 4.0)); + + + assertEquals(boundingBoxFromDouble, boundingBoxFromPoints); + assertEquals(southwestManualShifted, boundingBoxFromPoints.southwest()); + assertEquals(northeastManualShifted, boundingBoxFromPoints.northeast()); + + CoordinateShifterManager.setCoordinateShifter(null); + } + @Test public void point_toJson() throws Exception { diff --git a/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java b/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java index f2d4f7770..9c7b47f2e 100644 --- a/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java +++ b/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java @@ -453,10 +453,12 @@ public void testLineSliceAlongLine1() throws IOException, TurfException { Point end_point = TurfMeasurement.along(lineStringLine1, stop, TurfConstants.UNIT_MILES); LineString sliced = TurfMisc.lineSliceAlong(line1, start, stop, TurfConstants.UNIT_MILES); - assertEquals(sliced.coordinates().get(0).coordinates(), - start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), end_point.coordinates()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), + end_point.coordinatesPrimitives()); } @Test @@ -471,10 +473,12 @@ public void testLineSliceAlongOvershootLine1() throws IOException, TurfException Point end_point = TurfMeasurement.along(lineStringLine1, stop, TurfConstants.UNIT_MILES); LineString sliced = TurfMisc.lineSliceAlong(line1, start, stop, TurfConstants.UNIT_MILES); - assertEquals(sliced.coordinates().get(0).coordinates(), - start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), end_point.coordinates()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), + end_point.coordinatesPrimitives()); } @Test @@ -490,10 +494,12 @@ public void testLineSliceAlongRoute1() throws IOException, TurfException { LineString sliced = TurfMisc.lineSliceAlong(route1, start, stop, TurfConstants.UNIT_MILES); - assertEquals(sliced.coordinates().get(0).coordinates(), - start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), end_point.coordinates()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), + end_point.coordinatesPrimitives()); } @Test @@ -508,10 +514,12 @@ public void testLineSliceAlongRoute2() throws IOException, TurfException { Point end_point = TurfMeasurement.along(lineStringRoute2, stop, TurfConstants.UNIT_MILES); LineString sliced = TurfMisc.lineSliceAlong(route2, start, stop, TurfConstants.UNIT_MILES); - assertEquals(sliced.coordinates().get(0).coordinates(), - start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), end_point.coordinates()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), + end_point.coordinatesPrimitives()); } @Test @@ -536,10 +544,13 @@ public void testLineAlongStopLongerThanLength() throws IOException, TurfExceptio Point start_point = TurfMeasurement.along(lineStringLine1, start, TurfConstants.UNIT_MILES); List lineCoordinates = lineStringLine1.coordinates(); LineString sliced = TurfMisc.lineSliceAlong(line1, start, stop, TurfConstants.UNIT_MILES); - assertEquals(sliced.coordinates().get(0).coordinates(), - start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), lineCoordinates.get(lineCoordinates.size() - 1).coordinates()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), + lineCoordinates.get(lineCoordinates.size() - 1).coordinatesPrimitives()); } @Test @@ -557,9 +568,11 @@ public void testShortLine() throws IOException, TurfException { Point end_point = TurfMeasurement.along(lineStringLine1, stop, TurfConstants.UNIT_MILES); LineString sliced = TurfMisc.lineSliceAlong(lineStringLine1, start, stop, TurfConstants.UNIT_MILES); - assertEquals(sliced.coordinates().get(0).coordinates(), - start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); + assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), end_point.coordinates()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), + end_point.coordinatesPrimitives()); } } \ No newline at end of file From 16a474de78be4ce8b4691f014902bbd7e2d4a490 Mon Sep 17 00:00:00 2001 From: Ramon Date: Sun, 25 Jan 2026 23:25:45 +0200 Subject: [PATCH 02/31] Use flat structure for LineString data --- .../java/com/mapbox/geojson/LineString.java | 126 ++++++++++++++---- .../main/java/com/mapbox/geojson/Point.java | 2 +- .../com/mapbox/geojson/LineStringTest.java | 56 ++++++-- 3 files changed, 141 insertions(+), 43 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index b693eed3c..e97bd5971 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -13,7 +13,9 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; /** * A linestring represents two or more geographic points that share a relationship and is one of the @@ -49,7 +51,7 @@ * @since 1.0.0 */ @Keep -public final class LineString implements CoordinateContainer> { +public final class LineString implements PrimitiveCoordinateContainer, double[][]> { private static final String TYPE = "LineString"; @@ -57,7 +59,30 @@ public final class LineString implements CoordinateContainer> { private final BoundingBox bbox; - private final List coordinates; + /** + * A one-dimensional array to store the flattened coordinates: [lat1, lng1, lat2, lng2, ...]. + *

+ * Note: we use one-dimensional array for performance reasons related to JNI access ( + * Android JNI Tips + * - Primitive arrays) + * @see #coordinatesPrimitives() + */ + @NonNull + private final double[] flattenLatLngCoordinates; + /** + * An array to store the altitudes of each coordinate or {@link Double#NaN} if the coordinate + * does not have altitude. + * + * @see #coordinatesPrimitives() + */ + @Nullable + private final double[] altitudes; + /** + * An array to store the {@link BoundingBox} of each coordinate or null if the coordinate does + * not have bounding box. + */ + @Nullable + private BoundingBox[] coordinatesBoundingBoxes; /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are @@ -145,12 +170,42 @@ public static LineString fromLngLats(@NonNull MultiPoint multiPoint, @Nullable B if (type == null) { throw new NullPointerException("Null type"); } + double[] flattenLatLngCoordinates = new double[coordinates.size() * 2]; + double[] altitudes = null; + for (int i = 0; i < coordinates.size(); i++) { + Point point = coordinates.get(i); + flattenLatLngCoordinates[i*2] = point.longitude(); + flattenLatLngCoordinates[(i*2)+1] = point.latitude(); + + // It is quite common to not have altitude in Point. Therefore only if we have points + // with altitude then we create an array to store those. + if (point.hasAltitude()) { + // If one point has altitude we create an array of double to store the altitudes. + if (altitudes == null) { + altitudes = new double[coordinates.size()]; + // Fill in any previous altitude as NaN + for (int j = 0; j < i; j++) { + altitudes[j] = Double.NaN; + } + } + altitudes[i] = point.altitude(); + } else if (altitudes != null) { + // If we are storing altitudes but this point doesn't have it then set it to NaN + altitudes[i] = Double.NaN; + } + + // Similarly to altitudes, if one point has bound we create an array to store those. + if (point.bbox() != null) { + if (coordinatesBoundingBoxes == null) { + coordinatesBoundingBoxes = new BoundingBox[coordinates.size()]; + } + coordinatesBoundingBoxes[i] = point.bbox(); + } + } this.type = type; this.bbox = bbox; - if (coordinates == null) { - throw new NullPointerException("Null coordinates"); - } - this.coordinates = coordinates; + this.flattenLatLngCoordinates = flattenLatLngCoordinates; + this.altitudes = altitudes; } static LineString fromLngLats(double[][] coordinates) { @@ -211,14 +266,33 @@ public BoundingBox bbox() { /** * Provides the list of {@link Point}s that make up the LineString geometry. + *

+ * Please consider using {@link #coordinatesPrimitives()} instead for better performance. * * @return a list of points * @since 3.0.0 */ @NonNull @Override - public List coordinates() { - return coordinates; + public List coordinates() { + ArrayList points = new ArrayList<>(flattenLatLngCoordinates.length / 2); + for (int i = 0; i < flattenLatLngCoordinates.length / 2; i++) { + double[] coordinates; + if (altitudes != null && !Double.isNaN(altitudes[i])) { + coordinates = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1], altitudes[i]}; + } else { + coordinates = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1]}; + } + BoundingBox pointBbox = null; + if (coordinatesBoundingBoxes != null) { + pointBbox = coordinatesBoundingBoxes[i]; + } + // We create the Point directly instead of static factory method to avoid double coordinate + // shifting. + Point point = new Point(Point.TYPE, pointBbox, coordinates); + points.add(point); + } + return points; } /** @@ -264,34 +338,30 @@ public String toString() { return "LineString{" + "type=" + type + ", " + "bbox=" + bbox + ", " - + "coordinates=" + coordinates + + "coordinates=" + coordinates() + "}"; } @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof LineString) { - LineString that = (LineString) obj; - return (this.type.equals(that.type())) - && ((this.bbox == null) ? (that.bbox() == null) : this.bbox.equals(that.bbox())) - && (this.coordinates.equals(that.coordinates())); - } - return false; + public boolean equals(Object o) { + if (!(o instanceof LineString)) return false; + LineString that = (LineString) o; + return Objects.equals(type, that.type) && Objects.equals(bbox, that.bbox) && Objects.deepEquals(flattenLatLngCoordinates, that.flattenLatLngCoordinates) && Objects.deepEquals(altitudes, that.altitudes); } @Override public int hashCode() { - int hashCode = 1; - hashCode *= 1000003; - hashCode ^= type.hashCode(); - hashCode *= 1000003; - hashCode ^= (bbox == null) ? 0 : bbox.hashCode(); - hashCode *= 1000003; - hashCode ^= coordinates.hashCode(); - return hashCode; + return Objects.hash(type, bbox, Arrays.hashCode(flattenLatLngCoordinates), Arrays.hashCode(altitudes)); + } + + /** + * Returns two arrays of doubles: + * - The first one is a flatten array of all the coordinates (lat, lng) in the line string: [lat1, lng1, lat2, lng2, ...]. + * - The second (nullable) one is an array of all the altitudes in the line string (or null if no altitudes are present). + */ + @Override + public double[][] coordinatesPrimitives() { + return new double[][]{flattenLatLngCoordinates, altitudes}; } /** diff --git a/services-geojson/src/main/java/com/mapbox/geojson/Point.java b/services-geojson/src/main/java/com/mapbox/geojson/Point.java index 713ef7882..ba6ae1818 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Point.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Point.java @@ -51,7 +51,7 @@ @Keep public final class Point implements PrimitiveCoordinateContainer, double[]> { - private static final String TYPE = "Point"; + static final String TYPE = "Point"; @NonNull private final String type; diff --git a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java index c319c32a6..c635c9d7a 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import org.junit.Rule; import org.junit.Test; @@ -104,19 +105,28 @@ public void bbox_doesDeserializeWhenPresent() throws Exception { assertEquals(2.0, lineString.bbox().southwest().latitude(), DELTA); assertEquals(3.0, lineString.bbox().northeast().longitude(), DELTA); assertEquals(4.0, lineString.bbox().northeast().latitude(), DELTA); - assertNotNull(lineString.coordinates()); - assertEquals(1, lineString.coordinates().get(0).longitude(), DELTA); - assertEquals(2, lineString.coordinates().get(0).latitude(), DELTA); - assertEquals(2, lineString.coordinates().get(1).longitude(), DELTA); - assertEquals(3, lineString.coordinates().get(1).latitude(), DELTA); - assertEquals(3, lineString.coordinates().get(2).longitude(), DELTA); - assertEquals(4, lineString.coordinates().get(2).latitude(), DELTA); + List coordinates = lineString.coordinates(); + assertNotNull(coordinates); + assertEquals(1, coordinates.get(0).longitude(), DELTA); + assertEquals(2, coordinates.get(0).latitude(), DELTA); + assertEquals(2, coordinates.get(1).longitude(), DELTA); + assertEquals(3, coordinates.get(1).latitude(), DELTA); + assertEquals(3, coordinates.get(2).longitude(), DELTA); + assertEquals(4, coordinates.get(2).latitude(), DELTA); + + double[] coordinatesPrimitive = lineString.coordinatesPrimitives()[0]; + assertEquals(1, coordinatesPrimitive[0], DELTA); + assertEquals(2, coordinatesPrimitive[1], DELTA); + assertEquals(2, coordinatesPrimitive[2], DELTA); + assertEquals(3, coordinatesPrimitive[3], DELTA); + assertEquals(3, coordinatesPrimitive[4], DELTA); + assertEquals(4, coordinatesPrimitive[5], DELTA); } @Test public void testSerializable() throws Exception { List points = new ArrayList<>(); - points.add(Point.fromLngLat(1.0, 1.0)); + points.add(Point.fromLngLat(1.0, 1.0, 1.0)); points.add(Point.fromLngLat(2.0, 2.0)); points.add(Point.fromLngLat(3.0, 3.0)); BoundingBox bbox = BoundingBox.fromLngLats(1.0, 2.0, 3.0, 4.0); @@ -129,18 +139,36 @@ public void testSerializable() throws Exception { @Test public void fromJson() throws IOException { final String json = "{\"type\": \"LineString\"," + - " \"coordinates\": [[ 100, 0], [101, 1]]} "; + " \"coordinates\": [[ 100, 0, 1000], [101, 1]]} "; LineString geo = LineString.fromJson(json); - assertEquals(geo.type(), "LineString"); - assertEquals(geo.coordinates().get(0).longitude(), 100.0, 0.0); - assertEquals(geo.coordinates().get(0).latitude(), 0.0, 0.0); - assertFalse(geo.coordinates().get(0).hasAltitude()); + assertEquals("LineString", geo.type()); + List points = geo.coordinates(); + Point firstPoint = points.get(0); + assertEquals(100.0, firstPoint.longitude(), 0.0); + assertEquals(0.0, firstPoint.latitude(), 0.0); + assertTrue(firstPoint.hasAltitude()); + assertEquals(1000.0, firstPoint.altitude(), 0.0); + + Point secondPoint = points.get(1); + assertEquals(101.0, secondPoint.longitude(), 0.0); + assertEquals(1.0, secondPoint.latitude(), 0.0); + assertFalse(secondPoint.hasAltitude()); + + double[][] coordinatesPrimitives = geo.coordinatesPrimitives(); + double[] coordinates = coordinatesPrimitives[0]; + double[] altitudes = coordinatesPrimitives[1]; + assertEquals(100.0, coordinates[0], 0.0); + assertEquals(0.0, coordinates[1], 0.0); + assertEquals(1000.0, altitudes[0], 0.0); + assertEquals(101.0, coordinates[2], 0.0); + assertEquals(1.0, coordinates[3], 0.0); + assertEquals(Double.NaN, altitudes[1], 0.0); } @Test public void toJson() throws IOException { final String json = "{\"type\": \"LineString\"," + - " \"coordinates\": [[ 100, 0], [101, 1]]} "; + " \"coordinates\": [[ 100, 0, 1], [101, 1]]} "; LineString geo = LineString.fromJson(json); String geoJsonString = geo.toJson(); compareJson(geoJsonString, json); From 03133c445fba95d66b257a9e33b7b62c8022f528 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 00:10:27 +0200 Subject: [PATCH 03/31] Use class to store flatten list of points --- .../mapbox/geojson/FlattenListOfPoints.java | 124 ++++++++++++++++++ .../FlattenListOfPointsTypeAdapter.java | 70 ++++++++++ .../java/com/mapbox/geojson/LineString.java | 109 +++------------ .../com/mapbox/geojson/LineStringTest.java | 4 +- 4 files changed, 215 insertions(+), 92 deletions(-) create mode 100644 services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java create mode 100644 services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java new file mode 100644 index 000000000..ff799b7e2 --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java @@ -0,0 +1,124 @@ +package com.mapbox.geojson; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +@Keep +public class FlattenListOfPoints implements Serializable { + /** + * A one-dimensional array to store the flattened coordinates: [lat1, lng1, lat2, lng2, ...]. + *

+ * Note: we use one-dimensional array for performance reasons related to JNI access ( + * Android JNI Tips + * - Primitive arrays) + * + * @see #coordinatesPrimitives() + */ + @NonNull + private final double[] flattenLatLngCoordinates; + /** + * An array to store the altitudes of each coordinate or {@link Double#NaN} if the coordinate + * does not have altitude. + * + * @see #coordinatesPrimitives() + */ + @Nullable + private final double[] altitudes; + /** + * An array to store the {@link BoundingBox} of each coordinate or null if the coordinate does + * not have bounding box. + */ + @Nullable + private BoundingBox[] coordinatesBoundingBoxes; + + FlattenListOfPoints(List coordinates) { + double[] flattenLatLngCoordinates = new double[coordinates.size() * 2]; + double[] altitudes = null; + for (int i = 0; i < coordinates.size(); i++) { + Point point = coordinates.get(i); + flattenLatLngCoordinates[i * 2] = point.longitude(); + flattenLatLngCoordinates[(i * 2) + 1] = point.latitude(); + + // It is quite common to not have altitude in Point. Therefore only if we have points + // with altitude then we create an array to store those. + if (point.hasAltitude()) { + // If one point has altitude we create an array of double to store the altitudes. + if (altitudes == null) { + altitudes = new double[coordinates.size()]; + // Fill in any previous altitude as NaN + for (int j = 0; j < i; j++) { + altitudes[j] = Double.NaN; + } + } + altitudes[i] = point.altitude(); + } else if (altitudes != null) { + // If we are storing altitudes but this point doesn't have it then set it to NaN + altitudes[i] = Double.NaN; + } + + // Similarly to altitudes, if one point has bound we create an array to store those. + if (point.bbox() != null) { + if (coordinatesBoundingBoxes == null) { + coordinatesBoundingBoxes = new BoundingBox[coordinates.size()]; + } + coordinatesBoundingBoxes[i] = point.bbox(); + } + } + this.flattenLatLngCoordinates = flattenLatLngCoordinates; + this.altitudes = altitudes; + } + + /** + * Returns two arrays of doubles: + * - The first one is a flatten array of all the coordinates (lat, lng) in the line string: [lat1, lng1, lat2, lng2, ...]. + * - The second (nullable) one is an array of all the altitudes in the line string (or null if no altitudes are present). + */ + public double[][] coordinatesPrimitives() { + return new double[][]{flattenLatLngCoordinates, altitudes}; + } + + @Nullable + public BoundingBox[] getCoordinatesBoundingBoxes() { + return coordinatesBoundingBoxes; + } + + public List coordinates() { + ArrayList points = new ArrayList<>(flattenLatLngCoordinates.length / 2); + for (int i = 0; i < flattenLatLngCoordinates.length / 2; i++) { + double[] coordinates; + if (altitudes != null && !Double.isNaN(altitudes[i])) { + coordinates = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1], altitudes[i]}; + } else { + coordinates = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1]}; + } + BoundingBox pointBbox = null; + if (coordinatesBoundingBoxes != null) { + pointBbox = coordinatesBoundingBoxes[i]; + } + // We create the Point directly instead of static factory method to avoid double coordinate + // shifting. + Point point = new Point(Point.TYPE, pointBbox, coordinates); + points.add(point); + } + return points; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof FlattenListOfPoints)) return false; + FlattenListOfPoints that = (FlattenListOfPoints) o; + return Objects.deepEquals(flattenLatLngCoordinates, that.flattenLatLngCoordinates) && Objects.deepEquals(altitudes, that.altitudes) && Objects.deepEquals(coordinatesBoundingBoxes, that.coordinatesBoundingBoxes); + } + + @Override + public int hashCode() { + return Objects.hash(Arrays.hashCode(flattenLatLngCoordinates), Arrays.hashCode(altitudes), Arrays.hashCode(coordinatesBoundingBoxes)); + } +} diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java new file mode 100644 index 000000000..17e15aad5 --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java @@ -0,0 +1,70 @@ +package com.mapbox.geojson; + +import androidx.annotation.Keep; + +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.mapbox.geojson.exception.GeoJsonException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Type Adapter to serialize/deserialize List<Point> into/from two dimentional double array. + * + * @since 4.6.0 + */ +@Keep +class FlattenListOfPointsTypeAdapter extends BaseCoordinatesTypeAdapter { + + @Override + public void write(JsonWriter out, FlattenListOfPoints flattenListOfPoints) throws IOException { + + if (flattenListOfPoints == null) { + out.nullValue(); + return; + } + + out.beginArray(); + double[][] coordinatesPrimitives = flattenListOfPoints.coordinatesPrimitives(); + double[] flattenLatLngCoordinates = coordinatesPrimitives[0]; + double[] altitudes = coordinatesPrimitives[1]; + + for (int i = 0; i < flattenLatLngCoordinates.length / 2; i++) { + double[] value; + if (altitudes != null && !Double.isNaN(altitudes[i])) { + value = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1], altitudes[i]}; + } else { + value = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1]}; + } + + writePointList(out, value); + } + + out.endArray(); + } + + @Override + public FlattenListOfPoints read(JsonReader in) throws IOException { + + if (in.peek() == JsonToken.NULL) { + throw new NullPointerException(); + } + + if (in.peek() == JsonToken.BEGIN_ARRAY) { + List points = new ArrayList<>(); + in.beginArray(); + + while (in.peek() == JsonToken.BEGIN_ARRAY) { + points.add(readPoint(in)); + } + in.endArray(); + + return new FlattenListOfPoints(points); + } + + throw new GeoJsonException("coordinates should be non-null array of array of double"); + } +} diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index e97bd5971..1cbef57e5 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -13,7 +13,6 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -51,7 +50,7 @@ * @since 1.0.0 */ @Keep -public final class LineString implements PrimitiveCoordinateContainer, double[][]> { +public final class LineString implements PrimitiveCoordinateContainer, FlattenListOfPoints> { private static final String TYPE = "LineString"; @@ -59,30 +58,8 @@ public final class LineString implements PrimitiveCoordinateContainer - * Note: we use one-dimensional array for performance reasons related to JNI access ( - * Android JNI Tips - * - Primitive arrays) - * @see #coordinatesPrimitives() - */ @NonNull - private final double[] flattenLatLngCoordinates; - /** - * An array to store the altitudes of each coordinate or {@link Double#NaN} if the coordinate - * does not have altitude. - * - * @see #coordinatesPrimitives() - */ - @Nullable - private final double[] altitudes; - /** - * An array to store the {@link BoundingBox} of each coordinate or null if the coordinate does - * not have bounding box. - */ - @Nullable - private BoundingBox[] coordinatesBoundingBoxes; + private final FlattenListOfPoints flattenListOfPoints; /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are @@ -167,45 +144,19 @@ public static LineString fromLngLats(@NonNull MultiPoint multiPoint, @Nullable B } LineString(String type, @Nullable BoundingBox bbox, List coordinates) { + this(type, bbox, new FlattenListOfPoints(coordinates)); + } + + LineString(String type, @Nullable BoundingBox bbox, FlattenListOfPoints flattenListOfPoints) { if (type == null) { throw new NullPointerException("Null type"); } - double[] flattenLatLngCoordinates = new double[coordinates.size() * 2]; - double[] altitudes = null; - for (int i = 0; i < coordinates.size(); i++) { - Point point = coordinates.get(i); - flattenLatLngCoordinates[i*2] = point.longitude(); - flattenLatLngCoordinates[(i*2)+1] = point.latitude(); - - // It is quite common to not have altitude in Point. Therefore only if we have points - // with altitude then we create an array to store those. - if (point.hasAltitude()) { - // If one point has altitude we create an array of double to store the altitudes. - if (altitudes == null) { - altitudes = new double[coordinates.size()]; - // Fill in any previous altitude as NaN - for (int j = 0; j < i; j++) { - altitudes[j] = Double.NaN; - } - } - altitudes[i] = point.altitude(); - } else if (altitudes != null) { - // If we are storing altitudes but this point doesn't have it then set it to NaN - altitudes[i] = Double.NaN; - } - - // Similarly to altitudes, if one point has bound we create an array to store those. - if (point.bbox() != null) { - if (coordinatesBoundingBoxes == null) { - coordinatesBoundingBoxes = new BoundingBox[coordinates.size()]; - } - coordinatesBoundingBoxes[i] = point.bbox(); - } + if (flattenListOfPoints == null) { + throw new NullPointerException("Null coordinates"); } + this.flattenListOfPoints = flattenListOfPoints; this.type = type; this.bbox = bbox; - this.flattenLatLngCoordinates = flattenLatLngCoordinates; - this.altitudes = altitudes; } static LineString fromLngLats(double[][] coordinates) { @@ -275,24 +226,7 @@ public BoundingBox bbox() { @NonNull @Override public List coordinates() { - ArrayList points = new ArrayList<>(flattenLatLngCoordinates.length / 2); - for (int i = 0; i < flattenLatLngCoordinates.length / 2; i++) { - double[] coordinates; - if (altitudes != null && !Double.isNaN(altitudes[i])) { - coordinates = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1], altitudes[i]}; - } else { - coordinates = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1]}; - } - BoundingBox pointBbox = null; - if (coordinatesBoundingBoxes != null) { - pointBbox = coordinatesBoundingBoxes[i]; - } - // We create the Point directly instead of static factory method to avoid double coordinate - // shifting. - Point point = new Point(Point.TYPE, pointBbox, coordinates); - points.add(point); - } - return points; + return flattenListOfPoints.coordinates(); } /** @@ -346,22 +280,17 @@ public String toString() { public boolean equals(Object o) { if (!(o instanceof LineString)) return false; LineString that = (LineString) o; - return Objects.equals(type, that.type) && Objects.equals(bbox, that.bbox) && Objects.deepEquals(flattenLatLngCoordinates, that.flattenLatLngCoordinates) && Objects.deepEquals(altitudes, that.altitudes); + return Objects.equals(type, that.type) && Objects.equals(bbox, that.bbox) && Objects.equals(flattenListOfPoints, that.flattenListOfPoints); } @Override public int hashCode() { - return Objects.hash(type, bbox, Arrays.hashCode(flattenLatLngCoordinates), Arrays.hashCode(altitudes)); + return Objects.hash(type, bbox, flattenListOfPoints); } - /** - * Returns two arrays of doubles: - * - The first one is a flatten array of all the coordinates (lat, lng) in the line string: [lat1, lng1, lat2, lng2, ...]. - * - The second (nullable) one is an array of all the altitudes in the line string (or null if no altitudes are present). - */ @Override - public double[][] coordinatesPrimitives() { - return new double[][]{flattenLatLngCoordinates, altitudes}; + public FlattenListOfPoints coordinatesPrimitives() { + return flattenListOfPoints; } /** @@ -369,15 +298,15 @@ public double[][] coordinatesPrimitives() { * * @since 4.6.0 */ - static final class GsonTypeAdapter extends BaseGeometryTypeAdapter, List> { + static final class GsonTypeAdapter extends BaseGeometryTypeAdapter, FlattenListOfPoints> { GsonTypeAdapter(Gson gson) { - super(gson, new ListOfPointCoordinatesTypeAdapter()); + super(gson, new FlattenListOfPointsTypeAdapter()); } @Override public void write(JsonWriter jsonWriter, LineString object) throws IOException { - writeCoordinateContainer(jsonWriter, object); + writeCoordinateContainerPrimitive(jsonWriter, object); } @Override @@ -388,8 +317,8 @@ public LineString read(JsonReader jsonReader) throws IOException { @Override CoordinateContainer> createCoordinateContainer(String type, BoundingBox bbox, - List coordinates) { - return new LineString(type == null ? "LineString" : type, bbox, coordinates); + FlattenListOfPoints flattenListOfPoints) { + return new LineString(type == null ? "LineString" : type, bbox, flattenListOfPoints); } } } diff --git a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java index c635c9d7a..e9d0d424a 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -114,7 +114,7 @@ public void bbox_doesDeserializeWhenPresent() throws Exception { assertEquals(3, coordinates.get(2).longitude(), DELTA); assertEquals(4, coordinates.get(2).latitude(), DELTA); - double[] coordinatesPrimitive = lineString.coordinatesPrimitives()[0]; + double[] coordinatesPrimitive = lineString.coordinatesPrimitives().coordinatesPrimitives()[0]; assertEquals(1, coordinatesPrimitive[0], DELTA); assertEquals(2, coordinatesPrimitive[1], DELTA); assertEquals(2, coordinatesPrimitive[2], DELTA); @@ -154,7 +154,7 @@ public void fromJson() throws IOException { assertEquals(1.0, secondPoint.latitude(), 0.0); assertFalse(secondPoint.hasAltitude()); - double[][] coordinatesPrimitives = geo.coordinatesPrimitives(); + double[][] coordinatesPrimitives = geo.coordinatesPrimitives().coordinatesPrimitives(); double[] coordinates = coordinatesPrimitives[0]; double[] altitudes = coordinatesPrimitives[1]; assertEquals(100.0, coordinates[0], 0.0); From 8cd1b9c7b187824aa5e6fc8beb780af4d9d1b3fd Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 00:13:51 +0200 Subject: [PATCH 04/31] Rename PrimitiveCoordinateContainer to FlattenedCoordinateContainer --- .../geojson/BaseCoordinatesTypeAdapter.java | 2 +- .../geojson/BaseGeometryTypeAdapter.java | 4 +-- .../geojson/FlattenedCoordinateContainer.java | 5 +++ .../java/com/mapbox/geojson/LineString.java | 6 ++-- .../main/java/com/mapbox/geojson/Point.java | 6 ++-- .../geojson/PrimitiveCoordinateContainer.java | 5 --- .../com/mapbox/geojson/LineStringTest.java | 4 +-- .../java/com/mapbox/turf/TurfMiscTest.java | 36 +++++++++---------- 8 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 services-geojson/src/main/java/com/mapbox/geojson/FlattenedCoordinateContainer.java delete mode 100644 services-geojson/src/main/java/com/mapbox/geojson/PrimitiveCoordinateContainer.java diff --git a/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java index 7eb27c2b6..cb7fbe47e 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java @@ -27,7 +27,7 @@ protected void writePoint(JsonWriter out, Point point) throws IOException { if (point == null) { return; } - writePointList(out, point.coordinatesPrimitives()); + writePointList(out, point.flattenCoordinates()); } protected Point readPoint(JsonReader in) throws IOException { diff --git a/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java index e3cf635e9..69343c6a2 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java @@ -35,7 +35,7 @@ abstract class BaseGeometryTypeAdapter extends TypeAdapter { this.boundingBoxAdapter = new BoundingBoxTypeAdapter(); } - public void writeCoordinateContainerPrimitive(JsonWriter jsonWriter, PrimitiveCoordinateContainer object) throws IOException { + public void writeCoordinateContainerPrimitive(JsonWriter jsonWriter, FlattenedCoordinateContainer object) throws IOException { if (object == null) { jsonWriter.nullValue(); return; @@ -49,7 +49,7 @@ public void writeCoordinateContainerPrimitive(JsonWriter jsonWriter, PrimitiveCo if (coordinatesAdapter == null) { throw new GeoJsonException("Coordinates type adapter is null"); } - coordinatesAdapter.write(jsonWriter, object.coordinatesPrimitives()); + coordinatesAdapter.write(jsonWriter, object.flattenCoordinates()); } jsonWriter.endObject(); } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenedCoordinateContainer.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenedCoordinateContainer.java new file mode 100644 index 000000000..2bb8050d9 --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenedCoordinateContainer.java @@ -0,0 +1,5 @@ +package com.mapbox.geojson; + +interface FlattenedCoordinateContainer extends CoordinateContainer { + P flattenCoordinates(); +} diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index 1cbef57e5..a159beb92 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -50,7 +50,7 @@ * @since 1.0.0 */ @Keep -public final class LineString implements PrimitiveCoordinateContainer, FlattenListOfPoints> { +public final class LineString implements FlattenedCoordinateContainer, FlattenListOfPoints> { private static final String TYPE = "LineString"; @@ -218,7 +218,7 @@ public BoundingBox bbox() { /** * Provides the list of {@link Point}s that make up the LineString geometry. *

- * Please consider using {@link #coordinatesPrimitives()} instead for better performance. + * Please consider using {@link #flattenCoordinates()} instead for better performance. * * @return a list of points * @since 3.0.0 @@ -289,7 +289,7 @@ public int hashCode() { } @Override - public FlattenListOfPoints coordinatesPrimitives() { + public FlattenListOfPoints flattenCoordinates() { return flattenListOfPoints; } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/Point.java b/services-geojson/src/main/java/com/mapbox/geojson/Point.java index ba6ae1818..38a7f890f 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Point.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Point.java @@ -49,7 +49,7 @@ * @since 1.0.0 */ @Keep -public final class Point implements PrimitiveCoordinateContainer, double[]> { +public final class Point implements FlattenedCoordinateContainer, double[]> { static final String TYPE = "Point"; @@ -277,7 +277,7 @@ public BoundingBox bbox() { * * @return a double array which holds this points coordinates * @since 3.0.0 - * @deprecated Please use {@link #coordinatesPrimitives()} instead. + * @deprecated Please use {@link #flattenCoordinates()} instead. */ @NonNull @Override @@ -299,7 +299,7 @@ public List coordinates() { * @since 3.0.0 */ @Override - public double[] coordinatesPrimitives() { + public double[] flattenCoordinates() { return coordinates; } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/PrimitiveCoordinateContainer.java b/services-geojson/src/main/java/com/mapbox/geojson/PrimitiveCoordinateContainer.java deleted file mode 100644 index f1591b17a..000000000 --- a/services-geojson/src/main/java/com/mapbox/geojson/PrimitiveCoordinateContainer.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.mapbox.geojson; - -interface PrimitiveCoordinateContainer extends CoordinateContainer { - P coordinatesPrimitives(); -} diff --git a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java index e9d0d424a..9bf8246f3 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -114,7 +114,7 @@ public void bbox_doesDeserializeWhenPresent() throws Exception { assertEquals(3, coordinates.get(2).longitude(), DELTA); assertEquals(4, coordinates.get(2).latitude(), DELTA); - double[] coordinatesPrimitive = lineString.coordinatesPrimitives().coordinatesPrimitives()[0]; + double[] coordinatesPrimitive = lineString.flattenCoordinates().coordinatesPrimitives()[0]; assertEquals(1, coordinatesPrimitive[0], DELTA); assertEquals(2, coordinatesPrimitive[1], DELTA); assertEquals(2, coordinatesPrimitive[2], DELTA); @@ -154,7 +154,7 @@ public void fromJson() throws IOException { assertEquals(1.0, secondPoint.latitude(), 0.0); assertFalse(secondPoint.hasAltitude()); - double[][] coordinatesPrimitives = geo.coordinatesPrimitives().coordinatesPrimitives(); + double[][] coordinatesPrimitives = geo.flattenCoordinates().coordinatesPrimitives(); double[] coordinates = coordinatesPrimitives[0]; double[] altitudes = coordinatesPrimitives[1]; assertEquals(100.0, coordinates[0], 0.0); diff --git a/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java b/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java index 9c7b47f2e..ab87ad030 100644 --- a/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java +++ b/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java @@ -454,11 +454,11 @@ public void testLineSliceAlongLine1() throws IOException, TurfException { LineString sliced = TurfMisc.lineSliceAlong(line1, start, stop, TurfConstants.UNIT_MILES); assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), end_point.coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), - end_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), + end_point.flattenCoordinates()); } @Test @@ -474,11 +474,11 @@ public void testLineSliceAlongOvershootLine1() throws IOException, TurfException LineString sliced = TurfMisc.lineSliceAlong(line1, start, stop, TurfConstants.UNIT_MILES); assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), end_point.coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), - end_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), + end_point.flattenCoordinates()); } @Test @@ -495,11 +495,11 @@ public void testLineSliceAlongRoute1() throws IOException, TurfException { LineString sliced = TurfMisc.lineSliceAlong(route1, start, stop, TurfConstants.UNIT_MILES); assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), end_point.coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), - end_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), + end_point.flattenCoordinates()); } @Test @@ -515,11 +515,11 @@ public void testLineSliceAlongRoute2() throws IOException, TurfException { LineString sliced = TurfMisc.lineSliceAlong(route2, start, stop, TurfConstants.UNIT_MILES); assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), end_point.coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), - end_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), + end_point.flattenCoordinates()); } @Test @@ -545,12 +545,12 @@ public void testLineAlongStopLongerThanLength() throws IOException, TurfExceptio List lineCoordinates = lineStringLine1.coordinates(); LineString sliced = TurfMisc.lineSliceAlong(line1, start, stop, TurfConstants.UNIT_MILES); assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), lineCoordinates.get(lineCoordinates.size() - 1).coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), - lineCoordinates.get(lineCoordinates.size() - 1).coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), + lineCoordinates.get(lineCoordinates.size() - 1).flattenCoordinates()); } @Test @@ -569,10 +569,10 @@ public void testShortLine() throws IOException, TurfException { LineString sliced = TurfMisc.lineSliceAlong(lineStringLine1, start, stop, TurfConstants.UNIT_MILES); assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).coordinatesPrimitives(), start_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), end_point.coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinatesPrimitives(), - end_point.coordinatesPrimitives()); + assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), + end_point.flattenCoordinates()); } } \ No newline at end of file From c182c29685f22b77b9851df940b6fcd00407b255 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 00:26:19 +0200 Subject: [PATCH 05/31] Minor refactor FlattenListOfPoints --- .../mapbox/geojson/FlattenListOfPoints.java | 24 ++++++++++--------- .../FlattenListOfPointsTypeAdapter.java | 5 ++-- .../com/mapbox/geojson/LineStringTest.java | 8 +++---- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java index ff799b7e2..f6ef2b090 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java @@ -10,6 +10,9 @@ import java.util.List; import java.util.Objects; +/** + * A class that contains the required data to store a list of {@link Point}s as a flat structure. + */ @Keep public class FlattenListOfPoints implements Serializable { /** @@ -18,16 +21,12 @@ public class FlattenListOfPoints implements Serializable { * Note: we use one-dimensional array for performance reasons related to JNI access ( * Android JNI Tips * - Primitive arrays) - * - * @see #coordinatesPrimitives() */ @NonNull private final double[] flattenLatLngCoordinates; /** * An array to store the altitudes of each coordinate or {@link Double#NaN} if the coordinate * does not have altitude. - * - * @see #coordinatesPrimitives() */ @Nullable private final double[] altitudes; @@ -76,17 +75,20 @@ public class FlattenListOfPoints implements Serializable { } /** - * Returns two arrays of doubles: - * - The first one is a flatten array of all the coordinates (lat, lng) in the line string: [lat1, lng1, lat2, lng2, ...]. - * - The second (nullable) one is an array of all the altitudes in the line string (or null if no altitudes are present). + * @return a flatten array of all the coordinates (lat, lng): [lat1, lng1, lat2, lng2, ...]. */ - public double[][] coordinatesPrimitives() { - return new double[][]{flattenLatLngCoordinates, altitudes}; + @NonNull + public double[] getFlattenLatLngArray() { + return flattenLatLngCoordinates; } + /** + * @return an array of all the altitudes (or null if no altitudes are present at all). If a + * coordinate does not contain altitude it's represented as {@link Double#NaN} + */ @Nullable - public BoundingBox[] getCoordinatesBoundingBoxes() { - return coordinatesBoundingBoxes; + public double[] getAltitudes() { + return altitudes; } public List coordinates() { diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java index 17e15aad5..e22d898ed 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java @@ -28,9 +28,8 @@ public void write(JsonWriter out, FlattenListOfPoints flattenListOfPoints) throw } out.beginArray(); - double[][] coordinatesPrimitives = flattenListOfPoints.coordinatesPrimitives(); - double[] flattenLatLngCoordinates = coordinatesPrimitives[0]; - double[] altitudes = coordinatesPrimitives[1]; + double[] flattenLatLngCoordinates = flattenListOfPoints.getFlattenLatLngArray(); + double[] altitudes = flattenListOfPoints.getAltitudes(); for (int i = 0; i < flattenLatLngCoordinates.length / 2; i++) { double[] value; diff --git a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java index 9bf8246f3..dad5aa4d3 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -114,7 +114,7 @@ public void bbox_doesDeserializeWhenPresent() throws Exception { assertEquals(3, coordinates.get(2).longitude(), DELTA); assertEquals(4, coordinates.get(2).latitude(), DELTA); - double[] coordinatesPrimitive = lineString.flattenCoordinates().coordinatesPrimitives()[0]; + double[] coordinatesPrimitive = lineString.flattenCoordinates().getFlattenLatLngArray(); assertEquals(1, coordinatesPrimitive[0], DELTA); assertEquals(2, coordinatesPrimitive[1], DELTA); assertEquals(2, coordinatesPrimitive[2], DELTA); @@ -154,11 +154,11 @@ public void fromJson() throws IOException { assertEquals(1.0, secondPoint.latitude(), 0.0); assertFalse(secondPoint.hasAltitude()); - double[][] coordinatesPrimitives = geo.flattenCoordinates().coordinatesPrimitives(); - double[] coordinates = coordinatesPrimitives[0]; - double[] altitudes = coordinatesPrimitives[1]; + double[] coordinates = geo.flattenCoordinates().getFlattenLatLngArray(); + double[] altitudes = geo.flattenCoordinates().getAltitudes(); assertEquals(100.0, coordinates[0], 0.0); assertEquals(0.0, coordinates[1], 0.0); + assertNotNull(altitudes); assertEquals(1000.0, altitudes[0], 0.0); assertEquals(101.0, coordinates[2], 0.0); assertEquals(1.0, coordinates[3], 0.0); From b2754df97562e81ce1f5220e26af31911be35482 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 00:27:52 +0200 Subject: [PATCH 06/31] Add missing Keep to FlattenedCoordinateContainer --- .../java/com/mapbox/geojson/FlattenedCoordinateContainer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenedCoordinateContainer.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenedCoordinateContainer.java index 2bb8050d9..438d06b34 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenedCoordinateContainer.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenedCoordinateContainer.java @@ -1,5 +1,8 @@ package com.mapbox.geojson; +import androidx.annotation.Keep; + +@Keep interface FlattenedCoordinateContainer extends CoordinateContainer { P flattenCoordinates(); } From b107ac983e940d9b4b9fb0c1d6207d960532ac67 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 00:37:31 +0200 Subject: [PATCH 07/31] Convert MultiPoint to use flatten structure --- .../java/com/mapbox/geojson/MultiPoint.java | 56 +++++++++---------- .../com/mapbox/geojson/MultiPointTest.java | 31 +++++++--- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java index ba170041f..36c8dcb43 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * A MultiPoint represents two or more geographic points that share a relationship and is one of the @@ -35,7 +36,7 @@ * @since 1.0.0 */ @Keep -public final class MultiPoint implements CoordinateContainer> { +public final class MultiPoint implements FlattenedCoordinateContainer, FlattenListOfPoints> { private static final String TYPE = "MultiPoint"; @@ -43,7 +44,8 @@ public final class MultiPoint implements CoordinateContainer> { private final BoundingBox bbox; - private final List coordinates; + @NonNull + private final FlattenListOfPoints flattenListOfPoints; /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are @@ -103,15 +105,18 @@ static MultiPoint fromLngLats(@NonNull double[][] coordinates) { } MultiPoint(String type, @Nullable BoundingBox bbox, List coordinates) { + this(type, bbox, new FlattenListOfPoints(coordinates)); + } + MultiPoint(String type, @Nullable BoundingBox bbox, FlattenListOfPoints flattenListOfPoints) { if (type == null) { throw new NullPointerException("Null type"); } this.type = type; this.bbox = bbox; - if (coordinates == null) { + if (flattenListOfPoints == null) { throw new NullPointerException("Null coordinates"); } - this.coordinates = coordinates; + this.flattenListOfPoints = flattenListOfPoints; } /** @@ -153,7 +158,7 @@ public BoundingBox bbox() { @NonNull @Override public List coordinates() { - return coordinates; + return flattenListOfPoints.coordinates(); } /** @@ -186,34 +191,25 @@ public String toString() { return "MultiPoint{" + "type=" + type + ", " + "bbox=" + bbox + ", " - + "coordinates=" + coordinates + + "coordinates=" + flattenListOfPoints.coordinates() + "}"; } @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof MultiPoint) { - MultiPoint that = (MultiPoint) obj; - return (this.type.equals(that.type())) - && ((this.bbox == null) ? (that.bbox() == null) : this.bbox.equals(that.bbox())) - && (this.coordinates.equals(that.coordinates())); - } - return false; + public boolean equals(Object o) { + if (!(o instanceof MultiPoint)) return false; + MultiPoint that = (MultiPoint) o; + return Objects.equals(type, that.type) && Objects.equals(bbox, that.bbox) && Objects.equals(flattenListOfPoints, that.flattenListOfPoints); } @Override public int hashCode() { - int hashCode = 1; - hashCode *= 1000003; - hashCode ^= type.hashCode(); - hashCode *= 1000003; - hashCode ^= (bbox == null) ? 0 : bbox.hashCode(); - hashCode *= 1000003; - hashCode ^= coordinates.hashCode(); - return hashCode; + return Objects.hash(type, bbox, flattenListOfPoints); + } + + @Override + public FlattenListOfPoints flattenCoordinates() { + return flattenListOfPoints; } /** @@ -221,15 +217,15 @@ public int hashCode() { * * @since 4.6.0 */ - static final class GsonTypeAdapter extends BaseGeometryTypeAdapter, List> { + static final class GsonTypeAdapter extends BaseGeometryTypeAdapter, FlattenListOfPoints> { GsonTypeAdapter(Gson gson) { - super(gson, new ListOfPointCoordinatesTypeAdapter()); + super(gson, new FlattenListOfPointsTypeAdapter()); } @Override public void write(JsonWriter jsonWriter, MultiPoint object) throws IOException { - writeCoordinateContainer(jsonWriter, object); + writeCoordinateContainerPrimitive(jsonWriter, object); } @Override @@ -240,8 +236,8 @@ public MultiPoint read(JsonReader jsonReader) throws IOException { @Override CoordinateContainer> createCoordinateContainer(String type, BoundingBox bbox, - List coordinates) { - return new MultiPoint(type == null ? "MultiPoint" : type, bbox, coordinates); + FlattenListOfPoints flattenListOfPoints) { + return new MultiPoint(type == null ? "MultiPoint" : type, bbox, flattenListOfPoints); } } } diff --git a/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java b/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java index 3e93a76f5..75318cae9 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import org.junit.Rule; import org.junit.Test; @@ -93,15 +94,29 @@ public void testSerializable() throws Exception { @Test public void fromJson() throws IOException { final String json = "{ \"type\": \"MultiPoint\"," + - "\"coordinates\": [ [100, 0], [101, 1] ] } "; + "\"coordinates\": [ [100, 0, 1000], [101, 1] ] } "; MultiPoint geo = MultiPoint.fromJson(json); - assertEquals(geo.type(), "MultiPoint"); - assertEquals(geo.coordinates().get(0).longitude(), 100.0, DELTA); - assertEquals(geo.coordinates().get(0).latitude(), 0.0, DELTA); - assertEquals(geo.coordinates().get(1).longitude(), 101.0, DELTA); - assertEquals(geo.coordinates().get(1).latitude(), 1.0, DELTA); - assertFalse(geo.coordinates().get(0).hasAltitude()); - assertEquals(Double.NaN, geo.coordinates().get(0).altitude(), DELTA); + assertEquals("MultiPoint", geo.type()); + List coordinates = geo.coordinates(); + Point firstPoint = coordinates.get(0); + assertEquals(100.0, firstPoint.longitude(), DELTA); + assertEquals(0.0, firstPoint.latitude(), DELTA); + assertTrue(firstPoint.hasAltitude()); + assertEquals(1000.0, firstPoint.altitude(), DELTA); + + double[] flattenLatLngArray = geo.flattenCoordinates().getFlattenLatLngArray(); + assertEquals(100.0, flattenLatLngArray[0], DELTA); + assertEquals(0.0, flattenLatLngArray[1], DELTA); + + + Point secondPoint = coordinates.get(1); + assertEquals(101.0, secondPoint.longitude(), DELTA); + assertEquals(1.0, secondPoint.latitude(), DELTA); + assertFalse(secondPoint.hasAltitude()); + assertEquals(Double.NaN, secondPoint.altitude(), DELTA); + + assertEquals(101.0, flattenLatLngArray[2], DELTA); + assertEquals(1.0, flattenLatLngArray[3], DELTA); } @Test From c5cdda7eb891b3a4148c3972f58e784ed7242bc4 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 00:38:51 +0200 Subject: [PATCH 08/31] Remove unused ListOfPointCoordinatesTypeAdapter.java --- .../ListOfPointCoordinatesTypeAdapter.java | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 services-geojson/src/main/java/com/mapbox/geojson/ListOfPointCoordinatesTypeAdapter.java diff --git a/services-geojson/src/main/java/com/mapbox/geojson/ListOfPointCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/ListOfPointCoordinatesTypeAdapter.java deleted file mode 100644 index 215aec083..000000000 --- a/services-geojson/src/main/java/com/mapbox/geojson/ListOfPointCoordinatesTypeAdapter.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.mapbox.geojson; - -import androidx.annotation.Keep; - -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; -import com.mapbox.geojson.exception.GeoJsonException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * Type Adapter to serialize/deserialize List<Point> into/from two dimentional double array. - * - * @since 4.6.0 - */ -@Keep -class ListOfPointCoordinatesTypeAdapter extends BaseCoordinatesTypeAdapter> { - - @Override - public void write(JsonWriter out, List points) throws IOException { - - if (points == null) { - out.nullValue(); - return; - } - - out.beginArray(); - - for (Point point : points) { - writePoint(out, point); - } - - out.endArray(); - } - - @Override - public List read(JsonReader in) throws IOException { - - if (in.peek() == JsonToken.NULL) { - throw new NullPointerException(); - } - - if (in.peek() == JsonToken.BEGIN_ARRAY) { - List points = new ArrayList<>(); - in.beginArray(); - - while (in.peek() == JsonToken.BEGIN_ARRAY) { - points.add(readPoint(in)); - } - in.endArray(); - - return points; - } - - throw new GeoJsonException("coordinates should be non-null array of array of double"); - } -} From 981ea481fb21f41481cc75a512d8c2c20339994a Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 00:53:15 +0200 Subject: [PATCH 09/31] Minor rename and fix empty list of points --- .../mapbox/geojson/FlattenListOfPoints.java | 59 ++++++++++++------- .../java/com/mapbox/geojson/LineString.java | 2 +- .../java/com/mapbox/geojson/MultiPoint.java | 4 +- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java index f6ef2b090..6f0c959e9 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java @@ -23,7 +23,7 @@ public class FlattenListOfPoints implements Serializable { * - Primitive arrays) */ @NonNull - private final double[] flattenLatLngCoordinates; + private final double[] flattenLatLngPoints; /** * An array to store the altitudes of each coordinate or {@link Double#NaN} if the coordinate * does not have altitude. @@ -35,13 +35,19 @@ public class FlattenListOfPoints implements Serializable { * not have bounding box. */ @Nullable - private BoundingBox[] coordinatesBoundingBoxes; + private BoundingBox[] boundingBoxes; - FlattenListOfPoints(List coordinates) { - double[] flattenLatLngCoordinates = new double[coordinates.size() * 2]; + FlattenListOfPoints(List points) { + if (points.isEmpty()) { + this.flattenLatLngPoints = new double[0]; + this.altitudes = null; + this.boundingBoxes = null; + return; + } + double[] flattenLatLngCoordinates = new double[points.size() * 2]; double[] altitudes = null; - for (int i = 0; i < coordinates.size(); i++) { - Point point = coordinates.get(i); + for (int i = 0; i < points.size(); i++) { + Point point = points.get(i); flattenLatLngCoordinates[i * 2] = point.longitude(); flattenLatLngCoordinates[(i * 2) + 1] = point.latitude(); @@ -50,7 +56,7 @@ public class FlattenListOfPoints implements Serializable { if (point.hasAltitude()) { // If one point has altitude we create an array of double to store the altitudes. if (altitudes == null) { - altitudes = new double[coordinates.size()]; + altitudes = new double[points.size()]; // Fill in any previous altitude as NaN for (int j = 0; j < i; j++) { altitudes[j] = Double.NaN; @@ -64,13 +70,13 @@ public class FlattenListOfPoints implements Serializable { // Similarly to altitudes, if one point has bound we create an array to store those. if (point.bbox() != null) { - if (coordinatesBoundingBoxes == null) { - coordinatesBoundingBoxes = new BoundingBox[coordinates.size()]; + if (boundingBoxes == null) { + boundingBoxes = new BoundingBox[points.size()]; } - coordinatesBoundingBoxes[i] = point.bbox(); + boundingBoxes[i] = point.bbox(); } } - this.flattenLatLngCoordinates = flattenLatLngCoordinates; + this.flattenLatLngPoints = flattenLatLngCoordinates; this.altitudes = altitudes; } @@ -79,7 +85,7 @@ public class FlattenListOfPoints implements Serializable { */ @NonNull public double[] getFlattenLatLngArray() { - return flattenLatLngCoordinates; + return flattenLatLngPoints; } /** @@ -91,18 +97,29 @@ public double[] getAltitudes() { return altitudes; } - public List coordinates() { - ArrayList points = new ArrayList<>(flattenLatLngCoordinates.length / 2); - for (int i = 0; i < flattenLatLngCoordinates.length / 2; i++) { + /** + * Creates a list of {@link Point}s and returns it. + *

+ * If possible consider using {@link #getFlattenLatLngArray()} and {@link #getAltitudes()} instead. + * + * @return a list of {@link Point}s + */ + @NonNull + public List points() { + if (flattenLatLngPoints.length == 0) { + return new ArrayList<>(); + } + ArrayList points = new ArrayList<>(flattenLatLngPoints.length / 2); + for (int i = 0; i < flattenLatLngPoints.length / 2; i++) { double[] coordinates; if (altitudes != null && !Double.isNaN(altitudes[i])) { - coordinates = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1], altitudes[i]}; + coordinates = new double[]{flattenLatLngPoints[i * 2], flattenLatLngPoints[(i * 2) + 1], altitudes[i]}; } else { - coordinates = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1]}; + coordinates = new double[]{flattenLatLngPoints[i * 2], flattenLatLngPoints[(i * 2) + 1]}; } BoundingBox pointBbox = null; - if (coordinatesBoundingBoxes != null) { - pointBbox = coordinatesBoundingBoxes[i]; + if (boundingBoxes != null) { + pointBbox = boundingBoxes[i]; } // We create the Point directly instead of static factory method to avoid double coordinate // shifting. @@ -116,11 +133,11 @@ public List coordinates() { public boolean equals(Object o) { if (!(o instanceof FlattenListOfPoints)) return false; FlattenListOfPoints that = (FlattenListOfPoints) o; - return Objects.deepEquals(flattenLatLngCoordinates, that.flattenLatLngCoordinates) && Objects.deepEquals(altitudes, that.altitudes) && Objects.deepEquals(coordinatesBoundingBoxes, that.coordinatesBoundingBoxes); + return Objects.deepEquals(flattenLatLngPoints, that.flattenLatLngPoints) && Objects.deepEquals(altitudes, that.altitudes) && Objects.deepEquals(boundingBoxes, that.boundingBoxes); } @Override public int hashCode() { - return Objects.hash(Arrays.hashCode(flattenLatLngCoordinates), Arrays.hashCode(altitudes), Arrays.hashCode(coordinatesBoundingBoxes)); + return Objects.hash(Arrays.hashCode(flattenLatLngPoints), Arrays.hashCode(altitudes), Arrays.hashCode(boundingBoxes)); } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index a159beb92..8fe55d45f 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -226,7 +226,7 @@ public BoundingBox bbox() { @NonNull @Override public List coordinates() { - return flattenListOfPoints.coordinates(); + return flattenListOfPoints.points(); } /** diff --git a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java index 36c8dcb43..c71b9310e 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java @@ -158,7 +158,7 @@ public BoundingBox bbox() { @NonNull @Override public List coordinates() { - return flattenListOfPoints.coordinates(); + return flattenListOfPoints.points(); } /** @@ -191,7 +191,7 @@ public String toString() { return "MultiPoint{" + "type=" + type + ", " + "bbox=" + bbox + ", " - + "coordinates=" + flattenListOfPoints.coordinates() + + "coordinates=" + flattenListOfPoints.points() + "}"; } From a6e7feb37c4b50ee0e0d0329da7cd5e6710bb5da Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 08:31:42 +0200 Subject: [PATCH 10/31] Small javadoc fixes --- .../src/main/java/com/mapbox/geojson/FlattenListOfPoints.java | 2 +- .../src/main/java/com/mapbox/geojson/LineString.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java index 6f0c959e9..5cd842b28 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java @@ -99,7 +99,7 @@ public double[] getAltitudes() { /** * Creates a list of {@link Point}s and returns it. - *

+ *

* If possible consider using {@link #getFlattenLatLngArray()} and {@link #getAltitudes()} instead. * * @return a list of {@link Point}s diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index 8fe55d45f..57d278ed7 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -217,7 +217,7 @@ public BoundingBox bbox() { /** * Provides the list of {@link Point}s that make up the LineString geometry. - *

+ *

* Please consider using {@link #flattenCoordinates()} instead for better performance. * * @return a list of points From a715c3885e63ede99b6e677927a7523ca30d7db6 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 13:50:12 +0200 Subject: [PATCH 11/31] Fix code style --- .../geojson/BaseCoordinatesTypeAdapter.java | 18 +++++++------ .../geojson/BaseGeometryTypeAdapter.java | 6 ++++- .../mapbox/geojson/FlattenListOfPoints.java | 25 ++++++++++++++----- .../FlattenListOfPointsTypeAdapter.java | 11 ++++++-- .../java/com/mapbox/geojson/LineString.java | 21 ++++++++++------ .../java/com/mapbox/geojson/MultiPoint.java | 24 ++++++++++++------ .../java/com/mapbox/geojson/MultiPolygon.java | 4 +-- .../main/java/com/mapbox/geojson/Point.java | 22 +++++++++++----- .../main/java/com/mapbox/geojson/Polygon.java | 3 ++- .../geojson/shifter/CoordinateShifter.java | 23 +++++++++++++++++ .../shifter/CoordinateShifterManager.java | 2 +- 11 files changed, 117 insertions(+), 42 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java index cb7fbe47e..fc9c9ecb6 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java @@ -13,8 +13,8 @@ import java.io.IOException; /** - * Base class for converting {@code T} instance of coordinates to JSON and - * JSON to instance of {@code T}. + * Base class for converting {@code T} instance of coordinates to JSON and + * JSON to instance of {@code T}. * * @param Type of coordinates * @since 4.6.0 @@ -23,7 +23,7 @@ abstract class BaseCoordinatesTypeAdapter extends TypeAdapter { - protected void writePoint(JsonWriter out, Point point) throws IOException { + protected void writePoint(JsonWriter out, Point point) throws IOException { if (point == null) { return; } @@ -31,7 +31,7 @@ protected void writePoint(JsonWriter out, Point point) throws IOException { } protected Point readPoint(JsonReader in) throws IOException { - return new Point("Point",null, readPointList(in)); + return new Point("Point", null, readPointList(in)); } @@ -44,8 +44,8 @@ protected void writePointList(JsonWriter out, double[] value) throws IOException out.beginArray(); // Unshift coordinates - double[] unshiftedCoordinates = - CoordinateShifterManager.getCoordinateShifter().unshiftPointArray(value); + double[] unshiftedCoordinates = CoordinateShifterManager.getCoordinateShifter() + .unshiftPointArray(value); out.value(GeoJsonUtils.trim(unshiftedCoordinates[0])); out.value(GeoJsonUtils.trim(unshiftedCoordinates[1])); @@ -84,10 +84,12 @@ protected double[] readPointList(JsonReader in) throws IOException { in.skipValue(); } in.endArray(); - return CoordinateShifterManager.getCoordinateShifter().shift(coordinate0, coordinate1, coordinate2); + return CoordinateShifterManager.getCoordinateShifter() + .shift(coordinate0, coordinate1, coordinate2); } else { in.endArray(); - return CoordinateShifterManager.getCoordinateShifter().shift(coordinate0, coordinate1); + return CoordinateShifterManager.getCoordinateShifter() + .shift(coordinate0, coordinate1); } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java index 69343c6a2..56d1d726f 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java @@ -35,7 +35,10 @@ abstract class BaseGeometryTypeAdapter extends TypeAdapter { this.boundingBoxAdapter = new BoundingBoxTypeAdapter(); } - public void writeCoordinateContainerPrimitive(JsonWriter jsonWriter, FlattenedCoordinateContainer object) throws IOException { + public void writeCoordinateContainerPrimitive( + JsonWriter jsonWriter, + FlattenedCoordinateContainer object + ) throws IOException { if (object == null) { jsonWriter.nullValue(); return; @@ -53,6 +56,7 @@ public void writeCoordinateContainerPrimitive(JsonWriter jsonWriter, FlattenedCo } jsonWriter.endObject(); } + public void writeCoordinateContainer(JsonWriter jsonWriter, CoordinateContainer object) throws IOException { if (object == null) { diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java index 5cd842b28..25972599b 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java @@ -90,7 +90,7 @@ public double[] getFlattenLatLngArray() { /** * @return an array of all the altitudes (or null if no altitudes are present at all). If a - * coordinate does not contain altitude it's represented as {@link Double#NaN} + * coordinate does not contain altitude it's represented as {@link Double#NaN} */ @Nullable public double[] getAltitudes() { @@ -100,7 +100,8 @@ public double[] getAltitudes() { /** * Creates a list of {@link Point}s and returns it. *

- * If possible consider using {@link #getFlattenLatLngArray()} and {@link #getAltitudes()} instead. + * If possible consider using {@link #getFlattenLatLngArray()} and {@link #getAltitudes()} + * instead. * * @return a list of {@link Point}s */ @@ -113,7 +114,11 @@ public List points() { for (int i = 0; i < flattenLatLngPoints.length / 2; i++) { double[] coordinates; if (altitudes != null && !Double.isNaN(altitudes[i])) { - coordinates = new double[]{flattenLatLngPoints[i * 2], flattenLatLngPoints[(i * 2) + 1], altitudes[i]}; + coordinates = new double[]{ + flattenLatLngPoints[i * 2], + flattenLatLngPoints[(i * 2) + 1], + altitudes[i] + }; } else { coordinates = new double[]{flattenLatLngPoints[i * 2], flattenLatLngPoints[(i * 2) + 1]}; } @@ -131,13 +136,21 @@ public List points() { @Override public boolean equals(Object o) { - if (!(o instanceof FlattenListOfPoints)) return false; + if (!(o instanceof FlattenListOfPoints)) { + return false; + } FlattenListOfPoints that = (FlattenListOfPoints) o; - return Objects.deepEquals(flattenLatLngPoints, that.flattenLatLngPoints) && Objects.deepEquals(altitudes, that.altitudes) && Objects.deepEquals(boundingBoxes, that.boundingBoxes); + return Objects.deepEquals(flattenLatLngPoints, that.flattenLatLngPoints) + && Objects.deepEquals(altitudes, that.altitudes) + && Objects.deepEquals(boundingBoxes, that.boundingBoxes); } @Override public int hashCode() { - return Objects.hash(Arrays.hashCode(flattenLatLngPoints), Arrays.hashCode(altitudes), Arrays.hashCode(boundingBoxes)); + return Objects.hash( + Arrays.hashCode(flattenLatLngPoints), + Arrays.hashCode(altitudes), + Arrays.hashCode(boundingBoxes) + ); } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java index e22d898ed..6a7a5cb12 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java @@ -34,9 +34,16 @@ public void write(JsonWriter out, FlattenListOfPoints flattenListOfPoints) throw for (int i = 0; i < flattenLatLngCoordinates.length / 2; i++) { double[] value; if (altitudes != null && !Double.isNaN(altitudes[i])) { - value = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1], altitudes[i]}; + value = new double[]{ + flattenLatLngCoordinates[i * 2], + flattenLatLngCoordinates[(i * 2) + 1], + altitudes[i] + }; } else { - value = new double[]{flattenLatLngCoordinates[i * 2], flattenLatLngCoordinates[(i * 2) + 1]}; + value = new double[]{ + flattenLatLngCoordinates[i * 2], + flattenLatLngCoordinates[(i * 2) + 1] + }; } writePointList(out, value); diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index 57d278ed7..c6415e3cc 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -50,7 +50,8 @@ * @since 1.0.0 */ @Keep -public final class LineString implements FlattenedCoordinateContainer, FlattenListOfPoints> { +public final class LineString implements + FlattenedCoordinateContainer, FlattenListOfPoints> { private static final String TYPE = "LineString"; @@ -278,9 +279,13 @@ public String toString() { @Override public boolean equals(Object o) { - if (!(o instanceof LineString)) return false; + if (!(o instanceof LineString)) { + return false; + } LineString that = (LineString) o; - return Objects.equals(type, that.type) && Objects.equals(bbox, that.bbox) && Objects.equals(flattenListOfPoints, that.flattenListOfPoints); + return Objects.equals(type, that.type) + && Objects.equals(bbox, that.bbox) + && Objects.equals(flattenListOfPoints, that.flattenListOfPoints); } @Override @@ -298,7 +303,8 @@ public FlattenListOfPoints flattenCoordinates() { * * @since 4.6.0 */ - static final class GsonTypeAdapter extends BaseGeometryTypeAdapter, FlattenListOfPoints> { + static final class GsonTypeAdapter extends + BaseGeometryTypeAdapter, FlattenListOfPoints> { GsonTypeAdapter(Gson gson) { super(gson, new FlattenListOfPointsTypeAdapter()); @@ -315,9 +321,10 @@ public LineString read(JsonReader jsonReader) throws IOException { } @Override - CoordinateContainer> createCoordinateContainer(String type, - BoundingBox bbox, - FlattenListOfPoints flattenListOfPoints) { + CoordinateContainer> createCoordinateContainer( + String type, + BoundingBox bbox, + FlattenListOfPoints flattenListOfPoints) { return new LineString(type == null ? "LineString" : type, bbox, flattenListOfPoints); } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java index c71b9310e..bfa3a7caa 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java @@ -36,7 +36,8 @@ * @since 1.0.0 */ @Keep -public final class MultiPoint implements FlattenedCoordinateContainer, FlattenListOfPoints> { +public final class MultiPoint implements + FlattenedCoordinateContainer, FlattenListOfPoints> { private static final String TYPE = "MultiPoint"; @@ -107,7 +108,8 @@ static MultiPoint fromLngLats(@NonNull double[][] coordinates) { MultiPoint(String type, @Nullable BoundingBox bbox, List coordinates) { this(type, bbox, new FlattenListOfPoints(coordinates)); } - MultiPoint(String type, @Nullable BoundingBox bbox, FlattenListOfPoints flattenListOfPoints) { + + MultiPoint(String type, @Nullable BoundingBox bbox, FlattenListOfPoints flattenListOfPoints) { if (type == null) { throw new NullPointerException("Null type"); } @@ -197,9 +199,13 @@ public String toString() { @Override public boolean equals(Object o) { - if (!(o instanceof MultiPoint)) return false; + if (!(o instanceof MultiPoint)) { + return false; + } MultiPoint that = (MultiPoint) o; - return Objects.equals(type, that.type) && Objects.equals(bbox, that.bbox) && Objects.equals(flattenListOfPoints, that.flattenListOfPoints); + return Objects.equals(type, that.type) + && Objects.equals(bbox, that.bbox) + && Objects.equals(flattenListOfPoints, that.flattenListOfPoints); } @Override @@ -217,7 +223,8 @@ public FlattenListOfPoints flattenCoordinates() { * * @since 4.6.0 */ - static final class GsonTypeAdapter extends BaseGeometryTypeAdapter, FlattenListOfPoints> { + static final class GsonTypeAdapter extends + BaseGeometryTypeAdapter, FlattenListOfPoints> { GsonTypeAdapter(Gson gson) { super(gson, new FlattenListOfPointsTypeAdapter()); @@ -234,9 +241,10 @@ public MultiPoint read(JsonReader jsonReader) throws IOException { } @Override - CoordinateContainer> createCoordinateContainer(String type, - BoundingBox bbox, - FlattenListOfPoints flattenListOfPoints) { + CoordinateContainer> createCoordinateContainer( + String type, + BoundingBox bbox, + FlattenListOfPoints flattenListOfPoints) { return new MultiPoint(type == null ? "MultiPoint" : type, bbox, flattenListOfPoints); } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/MultiPolygon.java b/services-geojson/src/main/java/com/mapbox/geojson/MultiPolygon.java index e9f6ef053..0688a7be1 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiPolygon.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiPolygon.java @@ -344,8 +344,8 @@ public int hashCode() { * * @since 4.6.0 */ - static final class GsonTypeAdapter - extends BaseGeometryTypeAdapter>>, List>>> { + static final class GsonTypeAdapter extends + BaseGeometryTypeAdapter>>, List>>> { GsonTypeAdapter(Gson gson) { super(gson, new ListofListofListOfPointCoordinatesTypeAdapter()); diff --git a/services-geojson/src/main/java/com/mapbox/geojson/Point.java b/services-geojson/src/main/java/com/mapbox/geojson/Point.java index 38a7f890f..24a57c78f 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Point.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Point.java @@ -331,10 +331,15 @@ public static TypeAdapter typeAdapter(Gson gson) { @Override public String toString() { String coordinatesStr; - if (coordinates.length > 2) - coordinatesStr = "[" + this.coordinates[0] + ", " + this.coordinates[1] + ", " + this.coordinates[2] + "]"; - else + if (coordinates.length > 2) { + coordinatesStr = "[" + + this.coordinates[0] + ", " + + this.coordinates[1] + ", " + + this.coordinates[2] + + "]"; + } else { coordinatesStr = "[" + this.coordinates[0] + ", " + this.coordinates[1] + "]"; + } return "Point{" + "type=" + type + ", " + "bbox=" + bbox + ", " @@ -344,9 +349,13 @@ public String toString() { @Override public boolean equals(Object o) { - if (!(o instanceof Point)) return false; + if (!(o instanceof Point)) { + return false; + } Point point = (Point) o; - return Objects.equals(type, point.type) && Objects.equals(bbox, point.bbox) && Objects.deepEquals(coordinates, point.coordinates); + return Objects.equals(type, point.type) + && Objects.equals(bbox, point.bbox) + && Objects.deepEquals(coordinates, point.coordinates); } @Override @@ -366,7 +375,8 @@ public int hashCode() { * * @since 4.6.0 */ - static final class GsonTypeAdapter extends BaseGeometryTypeAdapter, double[]> { + static final class GsonTypeAdapter extends + BaseGeometryTypeAdapter, double[]> { GsonTypeAdapter(Gson gson) { super(gson, new ListOfDoublesCoordinatesTypeAdapter()); diff --git a/services-geojson/src/main/java/com/mapbox/geojson/Polygon.java b/services-geojson/src/main/java/com/mapbox/geojson/Polygon.java index df9d02974..e8970c064 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Polygon.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Polygon.java @@ -432,7 +432,8 @@ public int hashCode() { * * @since 4.6.0 */ - static final class GsonTypeAdapter extends BaseGeometryTypeAdapter>, List>> { + static final class GsonTypeAdapter extends + BaseGeometryTypeAdapter>, List>> { GsonTypeAdapter(Gson gson) { super(gson, new ListOfListOfPointCoordinatesTypeAdapter()); diff --git a/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifter.java b/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifter.java index 75d7541bf..fc3571d84 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifter.java @@ -55,7 +55,30 @@ public interface CoordinateShifter { */ List unshiftPoint(List shiftedCoordinates); + /** + * Shifted coordinate values according to its algorithm. + * + * @param lon unshifted longitude + * @param lat unshifted latitude + * @param altitude unshifted altitude + * @return shifted longitude, shifted latitude, shifted altitude + */ double[] shift(double lon, double lat, double altitude); + + /** + * Shifted coordinate values according to its algorithm. + * + * @param lon unshifted longitude + * @param lat unshifted latitude + * @return shifted longitude, shifted latitude + */ double[] shift(double lon, double lat); + + /** + * Unshifted coordinate values according to its algorithm. + * + * @param shiftedCoordinates shifted point + * @return unshifted longitude, shifted latitude, and altitude (if present) + */ double[] unshiftPointArray(double[] shiftedCoordinates); } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifterManager.java b/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifterManager.java index 40ac56e0d..b5cc12d44 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifterManager.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/shifter/CoordinateShifterManager.java @@ -42,7 +42,7 @@ public double[] shift(double lon, double lat) { @Override public double[] shift(double lon, double lat, double altitude) { - if (Double.isNaN(altitude)){ + if (Double.isNaN(altitude)) { return shift(lon, lat); } else { return new double[]{lon, lat, altitude}; From cfa95b9731e6f420da241c9986c1567348ff3de2 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 15:10:21 +0200 Subject: [PATCH 12/31] Better names when reading point from JSON --- .../geojson/BaseCoordinatesTypeAdapter.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java index fc9c9ecb6..38c165f2e 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java @@ -63,33 +63,31 @@ protected double[] readPointList(JsonReader in) throws IOException { throw new NullPointerException(); } - double coordinate0; - double coordinate1; - double coordinate2; + double lon; + double lat; + double altitude; in.beginArray(); if (in.hasNext()) { - coordinate0 = in.nextDouble(); + lon = in.nextDouble(); } else { throw new IndexOutOfBoundsException("Point coordinates should contain at least two values"); } if (in.hasNext()) { - coordinate1 = in.nextDouble(); + lat = in.nextDouble(); } else { throw new IndexOutOfBoundsException("Point coordinates should contain at least two values"); } if (in.hasNext()) { - coordinate2 = in.nextDouble(); + altitude = in.nextDouble(); // Consume any extra value but don't store it while (in.hasNext()) { in.skipValue(); } in.endArray(); - return CoordinateShifterManager.getCoordinateShifter() - .shift(coordinate0, coordinate1, coordinate2); + return CoordinateShifterManager.getCoordinateShifter().shift(lon, lat, altitude); } else { in.endArray(); - return CoordinateShifterManager.getCoordinateShifter() - .shift(coordinate0, coordinate1); + return CoordinateShifterManager.getCoordinateShifter().shift(lon, lat); } } From 8db98e82ced842b2f032c56572081eb9779050db Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 15:11:21 +0200 Subject: [PATCH 13/31] Fix order of lng,lat in various names and improved tests --- .../mapbox/geojson/FlattenListOfPoints.java | 46 +++++++++++-------- .../main/java/com/mapbox/geojson/Point.java | 4 +- .../com/mapbox/geojson/LineStringTest.java | 4 +- .../com/mapbox/geojson/MultiPointTest.java | 16 ++++--- .../java/com/mapbox/geojson/PointTest.java | 25 ++++++++++ 5 files changed, 64 insertions(+), 31 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java index 25972599b..2a27d9fe9 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java @@ -16,14 +16,14 @@ @Keep public class FlattenListOfPoints implements Serializable { /** - * A one-dimensional array to store the flattened coordinates: [lat1, lng1, lat2, lng2, ...]. + * A one-dimensional array to store the flattened coordinates: [lng1, lat1, lng2, lat2, ...]. *

* Note: we use one-dimensional array for performance reasons related to JNI access ( * Android JNI Tips * - Primitive arrays) */ @NonNull - private final double[] flattenLatLngPoints; + private final double[] flattenLngLatPoints; /** * An array to store the altitudes of each coordinate or {@link Double#NaN} if the coordinate * does not have altitude. @@ -37,19 +37,24 @@ public class FlattenListOfPoints implements Serializable { @Nullable private BoundingBox[] boundingBoxes; - FlattenListOfPoints(List points) { + FlattenListOfPoints(@NonNull double[] flattenLngLatPoints, @Nullable double[] altitudes) { + this.flattenLngLatPoints = flattenLngLatPoints; + this.altitudes = altitudes; + } + + FlattenListOfPoints(@NonNull List points) { if (points.isEmpty()) { - this.flattenLatLngPoints = new double[0]; + this.flattenLngLatPoints = new double[0]; this.altitudes = null; this.boundingBoxes = null; return; } - double[] flattenLatLngCoordinates = new double[points.size() * 2]; + double[] flattenLngLatCoordinates = new double[points.size() * 2]; double[] altitudes = null; for (int i = 0; i < points.size(); i++) { Point point = points.get(i); - flattenLatLngCoordinates[i * 2] = point.longitude(); - flattenLatLngCoordinates[(i * 2) + 1] = point.latitude(); + flattenLngLatCoordinates[i * 2] = point.longitude(); + flattenLngLatCoordinates[(i * 2) + 1] = point.latitude(); // It is quite common to not have altitude in Point. Therefore only if we have points // with altitude then we create an array to store those. @@ -76,16 +81,17 @@ public class FlattenListOfPoints implements Serializable { boundingBoxes[i] = point.bbox(); } } - this.flattenLatLngPoints = flattenLatLngCoordinates; + this.flattenLngLatPoints = flattenLngLatCoordinates; this.altitudes = altitudes; } /** - * @return a flatten array of all the coordinates (lat, lng): [lat1, lng1, lat2, lng2, ...]. + * @return a flatten array of all the coordinates (longitude, latitude): + * [lng1, lat1, lng2, lat2, ...]. */ @NonNull - public double[] getFlattenLatLngArray() { - return flattenLatLngPoints; + public double[] getFlattenLngLatArray() { + return flattenLngLatPoints; } /** @@ -100,27 +106,27 @@ public double[] getAltitudes() { /** * Creates a list of {@link Point}s and returns it. *

- * If possible consider using {@link #getFlattenLatLngArray()} and {@link #getAltitudes()} + * If possible consider using {@link #getFlattenLngLatArray()} and {@link #getAltitudes()} * instead. * * @return a list of {@link Point}s */ @NonNull public List points() { - if (flattenLatLngPoints.length == 0) { + if (flattenLngLatPoints.length == 0) { return new ArrayList<>(); } - ArrayList points = new ArrayList<>(flattenLatLngPoints.length / 2); - for (int i = 0; i < flattenLatLngPoints.length / 2; i++) { + ArrayList points = new ArrayList<>(flattenLngLatPoints.length / 2); + for (int i = 0; i < flattenLngLatPoints.length / 2; i++) { double[] coordinates; if (altitudes != null && !Double.isNaN(altitudes[i])) { coordinates = new double[]{ - flattenLatLngPoints[i * 2], - flattenLatLngPoints[(i * 2) + 1], + flattenLngLatPoints[i * 2], + flattenLngLatPoints[(i * 2) + 1], altitudes[i] }; } else { - coordinates = new double[]{flattenLatLngPoints[i * 2], flattenLatLngPoints[(i * 2) + 1]}; + coordinates = new double[]{flattenLngLatPoints[i * 2], flattenLngLatPoints[(i * 2) + 1]}; } BoundingBox pointBbox = null; if (boundingBoxes != null) { @@ -140,7 +146,7 @@ public boolean equals(Object o) { return false; } FlattenListOfPoints that = (FlattenListOfPoints) o; - return Objects.deepEquals(flattenLatLngPoints, that.flattenLatLngPoints) + return Objects.deepEquals(flattenLngLatPoints, that.flattenLngLatPoints) && Objects.deepEquals(altitudes, that.altitudes) && Objects.deepEquals(boundingBoxes, that.boundingBoxes); } @@ -148,7 +154,7 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash( - Arrays.hashCode(flattenLatLngPoints), + Arrays.hashCode(flattenLngLatPoints), Arrays.hashCode(altitudes), Arrays.hashCode(boundingBoxes) ); diff --git a/services-geojson/src/main/java/com/mapbox/geojson/Point.java b/services-geojson/src/main/java/com/mapbox/geojson/Point.java index 24a57c78f..1ee3b9a18 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Point.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Point.java @@ -271,7 +271,7 @@ public BoundingBox bbox() { } /** - * Provide a single double array containing the longitude, latitude, and optionally an + * Provide a list of Doubles containing the longitude, latitude, and optionally an * altitude/elevation. {@link #longitude()}, {@link #latitude()}, and {@link #altitude()} are all * available which make getting specific coordinates more direct. * @@ -292,7 +292,7 @@ public List coordinates() { /** * Provide a single double array containing the longitude, latitude, and optionally an - * altitude/elevation. {@link #longitude()}, {@link #latitude()}, and {@link #altitude()} are all + * altitude. {@link #longitude()}, {@link #latitude()}, and {@link #altitude()} are all * available which make getting specific coordinates more direct. * * @return a double array which holds this points coordinates diff --git a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java index dad5aa4d3..efbc0298f 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -114,7 +114,7 @@ public void bbox_doesDeserializeWhenPresent() throws Exception { assertEquals(3, coordinates.get(2).longitude(), DELTA); assertEquals(4, coordinates.get(2).latitude(), DELTA); - double[] coordinatesPrimitive = lineString.flattenCoordinates().getFlattenLatLngArray(); + double[] coordinatesPrimitive = lineString.flattenCoordinates().getFlattenLngLatArray(); assertEquals(1, coordinatesPrimitive[0], DELTA); assertEquals(2, coordinatesPrimitive[1], DELTA); assertEquals(2, coordinatesPrimitive[2], DELTA); @@ -154,7 +154,7 @@ public void fromJson() throws IOException { assertEquals(1.0, secondPoint.latitude(), 0.0); assertFalse(secondPoint.hasAltitude()); - double[] coordinates = geo.flattenCoordinates().getFlattenLatLngArray(); + double[] coordinates = geo.flattenCoordinates().getFlattenLngLatArray(); double[] altitudes = geo.flattenCoordinates().getAltitudes(); assertEquals(100.0, coordinates[0], 0.0); assertEquals(0.0, coordinates[1], 0.0); diff --git a/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java b/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java index 75318cae9..dee5add5b 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java @@ -94,19 +94,21 @@ public void testSerializable() throws Exception { @Test public void fromJson() throws IOException { final String json = "{ \"type\": \"MultiPoint\"," + - "\"coordinates\": [ [100, 0, 1000], [101, 1] ] } "; + "\"coordinates\": [ [100, 90, 1000], [101, 1] ] } "; MultiPoint geo = MultiPoint.fromJson(json); assertEquals("MultiPoint", geo.type()); List coordinates = geo.coordinates(); Point firstPoint = coordinates.get(0); assertEquals(100.0, firstPoint.longitude(), DELTA); - assertEquals(0.0, firstPoint.latitude(), DELTA); + assertEquals(90.0, firstPoint.latitude(), DELTA); assertTrue(firstPoint.hasAltitude()); assertEquals(1000.0, firstPoint.altitude(), DELTA); - double[] flattenLatLngArray = geo.flattenCoordinates().getFlattenLatLngArray(); - assertEquals(100.0, flattenLatLngArray[0], DELTA); - assertEquals(0.0, flattenLatLngArray[1], DELTA); + double[] flattenLngLatArray = geo.flattenCoordinates().getFlattenLngLatArray(); + assertEquals(100.0, flattenLngLatArray[0], DELTA); + assertEquals(firstPoint.longitude(), flattenLngLatArray[0], DELTA); + assertEquals(90.0, flattenLngLatArray[1], DELTA); + assertEquals(firstPoint.latitude(), flattenLngLatArray[1], DELTA); Point secondPoint = coordinates.get(1); @@ -115,8 +117,8 @@ public void fromJson() throws IOException { assertFalse(secondPoint.hasAltitude()); assertEquals(Double.NaN, secondPoint.altitude(), DELTA); - assertEquals(101.0, flattenLatLngArray[2], DELTA); - assertEquals(1.0, flattenLatLngArray[3], DELTA); + assertEquals(101.0, flattenLngLatArray[2], DELTA); + assertEquals(1.0, flattenLngLatArray[3], DELTA); } @Test diff --git a/services-geojson/src/test/java/com/mapbox/geojson/PointTest.java b/services-geojson/src/test/java/com/mapbox/geojson/PointTest.java index 1b9335fe7..66de019b1 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/PointTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/PointTest.java @@ -25,6 +25,18 @@ public class PointTest extends TestUtils { public void sanity() throws Exception { Point point = Point.fromLngLat(1.0, 2.0); assertNotNull(point); + assertEquals("Point", point.type()); + assertEquals(1.0, point.longitude(), DELTA); + assertEquals(2.0, point.latitude(), DELTA); + assertEquals(Double.NaN, point.altitude(), DELTA); + List coordinates = point.coordinates(); + assertEquals(2, coordinates.size()); + assertEquals(1.0, coordinates.get(0), DELTA); + assertEquals(2.0, coordinates.get(1), DELTA); + double[] doubles = point.flattenCoordinates(); + assertEquals(2, doubles.length); + assertEquals(1.0, doubles[0], DELTA); + assertEquals(2.0, doubles[1], DELTA); } @Test @@ -37,6 +49,19 @@ public void hasAltitude_returnsFalseWhenAltitudeNotPresent() throws Exception { public void hasAltitude_returnsTrueWhenAltitudeIsPresent() throws Exception { Point point = Point.fromLngLat(1.0, 2.0, 5.0); assertTrue(point.hasAltitude()); + assertEquals(1.0, point.longitude(), DELTA); + assertEquals(2.0, point.latitude(), DELTA); + assertEquals(5.0, point.altitude(), DELTA); + List coordinates = point.coordinates(); + assertEquals(3, coordinates.size()); + assertEquals(1.0, coordinates.get(0), DELTA); + assertEquals(2.0, coordinates.get(1), DELTA); + assertEquals(5.0, coordinates.get(2), DELTA); + double[] doubles = point.flattenCoordinates(); + assertEquals(3, doubles.length); + assertEquals(1.0, doubles[0], DELTA); + assertEquals(2.0, doubles[1], DELTA); + assertEquals(5.0, doubles[2], DELTA); } @Test From eb9672100121436982cd8fb56dbb6f692a992fbd Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 15:11:38 +0200 Subject: [PATCH 14/31] Improve reading list of points from JSON --- .../FlattenListOfPointsTypeAdapter.java | 86 ++++++++++++++++--- 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java index 6a7a5cb12..e12333789 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java @@ -8,8 +8,6 @@ import com.mapbox.geojson.exception.GeoJsonException; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; /** * Type Adapter to serialize/deserialize List<Point> into/from two dimentional double array. @@ -19,6 +17,8 @@ @Keep class FlattenListOfPointsTypeAdapter extends BaseCoordinatesTypeAdapter { + private static final int INITIAL_CAPACITY = 100; + @Override public void write(JsonWriter out, FlattenListOfPoints flattenListOfPoints) throws IOException { @@ -28,21 +28,21 @@ public void write(JsonWriter out, FlattenListOfPoints flattenListOfPoints) throw } out.beginArray(); - double[] flattenLatLngCoordinates = flattenListOfPoints.getFlattenLatLngArray(); + double[] flattenLngLatCoordinates = flattenListOfPoints.getFlattenLngLatArray(); double[] altitudes = flattenListOfPoints.getAltitudes(); - for (int i = 0; i < flattenLatLngCoordinates.length / 2; i++) { + for (int i = 0; i < flattenLngLatCoordinates.length / 2; i++) { double[] value; if (altitudes != null && !Double.isNaN(altitudes[i])) { value = new double[]{ - flattenLatLngCoordinates[i * 2], - flattenLatLngCoordinates[(i * 2) + 1], + flattenLngLatCoordinates[i * 2], + flattenLngLatCoordinates[(i * 2) + 1], altitudes[i] }; } else { value = new double[]{ - flattenLatLngCoordinates[i * 2], - flattenLatLngCoordinates[(i * 2) + 1] + flattenLngLatCoordinates[i * 2], + flattenLngLatCoordinates[(i * 2) + 1] }; } @@ -54,21 +54,83 @@ public void write(JsonWriter out, FlattenListOfPoints flattenListOfPoints) throw @Override public FlattenListOfPoints read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { throw new NullPointerException(); } if (in.peek() == JsonToken.BEGIN_ARRAY) { - List points = new ArrayList<>(); in.beginArray(); + double[] flattenLngLats = new double[INITIAL_CAPACITY * 2]; + double[] altitudes = null; + int currentIdx = 0; while (in.peek() == JsonToken.BEGIN_ARRAY) { - points.add(readPoint(in)); + in.beginArray(); + // Read longitude + if (in.hasNext()) { + flattenLngLats[currentIdx * 2] = in.nextDouble(); + } else { + throw new IndexOutOfBoundsException( + "Point coordinates should contain at least two values" + ); + } + + // Read latitude + if (in.hasNext()) { + flattenLngLats[currentIdx * 2 + 1] = in.nextDouble(); + } else { + throw new IndexOutOfBoundsException( + "Point coordinates should contain at least two values" + ); + } + + // Finally altitude if present + if (in.hasNext()) { + if (altitudes == null) { + altitudes = new double[flattenLngLats.length / 2]; + // Fill in any previous altitude as NaN + for (int j = 0; j < currentIdx; j++) { + altitudes[j] = Double.NaN; + } + } + altitudes[currentIdx] = in.nextDouble(); + // Consume any extra value but don't store it + while (in.hasNext()) { + in.skipValue(); + } + in.endArray(); + } else { + in.endArray(); + if (altitudes != null) { + // If we are storing altitudes but this point doesn't have it then set it to NaN + altitudes[currentIdx] = Double.NaN; + } + } + currentIdx++; + // If we run out of space we grow the the arrays + if (currentIdx * 2 >= flattenLngLats.length) { + double[] newFlattenLngLats = new double[flattenLngLats.length * 2]; + System.arraycopy(flattenLngLats, 0, newFlattenLngLats, 0, flattenLngLats.length); + flattenLngLats = newFlattenLngLats; + if (altitudes != null) { + double[] newAltitudes = new double[altitudes.length * 2]; + System.arraycopy(altitudes, 0, newAltitudes, 0, altitudes.length); + altitudes = newAltitudes; + } + } } in.endArray(); - return new FlattenListOfPoints(points); + int totalPoints = currentIdx; + double[] trimmedFlattenLngLats = new double[totalPoints * 2]; + System.arraycopy(flattenLngLats, 0, trimmedFlattenLngLats, 0, totalPoints * 2); + double[] trimmedAltitudes = null; + if (altitudes != null) { + trimmedAltitudes = new double[totalPoints]; + System.arraycopy(altitudes, 0, trimmedAltitudes, 0, totalPoints); + } + + return new FlattenListOfPoints(trimmedFlattenLngLats, trimmedAltitudes); } throw new GeoJsonException("coordinates should be non-null array of array of double"); From 63fc1577dbe69c1a321c293228515502e4f926b7 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 15:42:37 +0200 Subject: [PATCH 15/31] Expose LineString constructor with FlattenListOfPoints --- .../mapbox/geojson/FlattenListOfPoints.java | 10 ++++- .../java/com/mapbox/geojson/LineString.java | 38 +++++++++++++++++++ .../com/mapbox/geojson/LineStringTest.java | 8 ++++ .../mapbox/geojson/shifter/ShifterTest.java | 9 +++++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java index 2a27d9fe9..aafcb4c98 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java @@ -37,7 +37,15 @@ public class FlattenListOfPoints implements Serializable { @Nullable private BoundingBox[] boundingBoxes; - FlattenListOfPoints(@NonNull double[] flattenLngLatPoints, @Nullable double[] altitudes) { + /** + * + * @param flattenLngLatPoints A one-dimensional array coordinates: [lng1, lat1, lng2, lat2, ...]. + * It is stored as is, no copy or shifting is done. + * @param altitudes An array of altitudes of each coordinate or {@link Double#NaN} if the + * coordinate does not have altitude. It is stored as is, no copy or shifting is + * done. + */ + public FlattenListOfPoints(@NonNull double[] flattenLngLatPoints, @Nullable double[] altitudes) { this.flattenLngLatPoints = flattenLngLatPoints; this.altitudes = altitudes; } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index c6415e3cc..be480c598 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -9,6 +9,8 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import com.mapbox.geojson.gson.GeoJsonAdapterFactory; +import com.mapbox.geojson.shifter.CoordinateShifter; +import com.mapbox.geojson.shifter.CoordinateShifterManager; import com.mapbox.geojson.utils.PolylineUtils; import java.io.IOException; @@ -144,6 +146,42 @@ public static LineString fromLngLats(@NonNull MultiPoint multiPoint, @Nullable B return new LineString(TYPE, bbox, multiPoint.coordinates()); } + /** + * Create a new instance of this class by defining a {@link FlattenListOfPoints} object. + * The multipoint object should comply with the GeoJson specifications described in the + * documentation. + * + * @param flattenListOfPoints which will make up the LineString geometry. The points will be + * shifted according to the current + * {@link CoordinateShifterManager#getCoordinateShifter()} + * @param bbox optionally include a bbox definition + * @return a new instance of this class defined by the values passed inside this static factory + * method + */ + public static LineString fromFlattenListOfPoints( + FlattenListOfPoints flattenListOfPoints, + @Nullable BoundingBox bbox + ) { + double[] flattenLngLatArray = flattenListOfPoints.getFlattenLngLatArray(); + double[] altitudes = flattenListOfPoints.getAltitudes(); + CoordinateShifter coordinateShifter = CoordinateShifterManager.getCoordinateShifter(); + // Iterate all the points and shift them + for (int i = 0; i < flattenLngLatArray.length / 2; i++) { + if (altitudes != null && !Double.isNaN(altitudes[i])) { + double[] shifted = coordinateShifter.shift(flattenLngLatArray[i * 2], flattenLngLatArray[(i * 2) + 1], altitudes[i]); + flattenLngLatArray[i * 2] = shifted[0]; + flattenLngLatArray[(i * 2) + 1] = shifted[1]; + altitudes[i] = shifted[2]; + } else { + double[] shifted = coordinateShifter.shift(flattenLngLatArray[i * 2], flattenLngLatArray[(i * 2) + 1]); + flattenLngLatArray[i * 2] = shifted[0]; + flattenLngLatArray[(i * 2) + 1] = shifted[1]; + } + } + + return new LineString(TYPE, bbox, flattenListOfPoints); + } + LineString(String type, @Nullable BoundingBox bbox, List coordinates) { this(type, bbox, new FlattenListOfPoints(coordinates)); } diff --git a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java index efbc0298f..8717509c0 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -44,6 +44,14 @@ public void fromLngLats_generatedFromMultipoint() throws Exception { assertEquals("_gayB_c`|@_wemJ_kbvD", lineString.toPolyline(PRECISION_6)); } + @Test + public void fromFlattenListOfPoints() throws Exception { + double[] flattenLngLatPoints= new double[]{1.0, 2.0, 4.0, 8.0}; + FlattenListOfPoints flattenListOfPoints = new FlattenListOfPoints(flattenLngLatPoints, null); + LineString lineString = LineString.fromFlattenListOfPoints(flattenListOfPoints, null); + assertEquals("_gayB_c`|@_wemJ_kbvD", lineString.toPolyline(PRECISION_6)); + } + @Test public void bbox_nullWhenNotSet() throws Exception { List points = new ArrayList<>(); diff --git a/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java b/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java index 6475e592f..67353d3cd 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java @@ -2,6 +2,7 @@ import com.google.gson.JsonParser; import com.mapbox.geojson.BoundingBox; +import com.mapbox.geojson.FlattenListOfPoints; import com.mapbox.geojson.LineString; import com.mapbox.geojson.Point; @@ -222,6 +223,14 @@ public void linestring_basic_shift_with_bbox() { + "\"type\":\"LineString\",\"bbox\":[1.0,2.0,3.0,4.0]}", jsonString); + double[] flattenLngLatPoints= new double[]{1.0, 1.0, 2.0, 2.0, 3.0, 3.0}; + FlattenListOfPoints flattenListOfPoints = new FlattenListOfPoints(flattenLngLatPoints, null); + LineString lineString2 = LineString.fromFlattenListOfPoints(flattenListOfPoints, bbox); + String jsonString2 = lineString2.toJson(); + compareJson("{\"coordinates\":[[1,1],[2,2],[3,3]]," + + "\"type\":\"LineString\",\"bbox\":[1.0,2.0,3.0,4.0]}", + jsonString2); + CoordinateShifterManager.setCoordinateShifter(null); } From 6d564fbe0fc30d092b8fed7a7b63d4316f05edfe Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 16:29:15 +0200 Subject: [PATCH 16/31] Added encode/decode to PolylineUtils --- .../java/com/mapbox/geojson/LineString.java | 6 +- .../mapbox/geojson/utils/PolylineUtils.java | 99 +++++++++++++++++++ 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index be480c598..b7e19e4dd 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -221,7 +221,8 @@ static LineString fromLngLats(double[][] coordinates) { * @since 1.0.0 */ public static LineString fromPolyline(@NonNull String polyline, int precision) { - return LineString.fromLngLats(PolylineUtils.decode(polyline, precision), null); + FlattenListOfPoints points = PolylineUtils.decodeToFlattenListOfPoints(polyline, precision); + return LineString.fromFlattenListOfPoints(points, null); } /** @@ -264,6 +265,7 @@ public BoundingBox bbox() { */ @NonNull @Override + @Deprecated public List coordinates() { return flattenListOfPoints.points(); } @@ -292,7 +294,7 @@ public String toJson() { * @since 1.0.0 */ public String toPolyline(int precision) { - return PolylineUtils.encode(coordinates(), precision); + return PolylineUtils.encode(flattenListOfPoints, precision); } /** diff --git a/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java b/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java index 90a9b3b3a..f811453cb 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java @@ -1,6 +1,8 @@ package com.mapbox.geojson.utils; import androidx.annotation.NonNull; + +import com.mapbox.geojson.FlattenListOfPoints; import com.mapbox.geojson.Point; import java.util.ArrayList; @@ -79,6 +81,67 @@ public static List decode(@NonNull final String encodedPath, int precisio return path.subList(0, itemsCount); } + /** + * Decodes an encoded path string into a {@link FlattenListOfPoints}. + * + * @param encodedPath a String representing an encoded path string + * @param precision OSRMv4 uses 6, OSRMv5 and Google uses 5 + * @return a {@link FlattenListOfPoints} making up the line + * @see Part of algorithm came from this source + * @see Part of algorithm came from this source. + */ + @NonNull + public static FlattenListOfPoints decodeToFlattenListOfPoints( + @NonNull + final String encodedPath, + int precision + ) { + int len = encodedPath.length(); + + // OSRM uses precision=6, the default Polyline spec divides by 1E5, capping at precision=5 + double factor = Math.pow(10, precision); + + // For speed we preallocate to an upper bound on the final length, then + // truncate the array before returning. + double[] flattenLngLatCoordinates = new double[len]; + int index = 0; + int lat = 0; + int lng = 0; + int itemsCount = 0; + + while (index < len) { + int result = 1; + int shift = 0; + int temp; + do { + temp = encodedPath.charAt(index++) - 63 - 1; + result += temp << shift; + shift += 5; + } + while (temp >= 0x1f); + lat += (result & 1) != 0 ? ~(result >> 1) : (result >> 1); + + result = 1; + shift = 0; + do { + temp = encodedPath.charAt(index++) - 63 - 1; + result += temp << shift; + shift += 5; + } + while (temp >= 0x1f); + lng += (result & 1) != 0 ? ~(result >> 1) : (result >> 1); + + flattenLngLatCoordinates[itemsCount*2] = lng / factor; + flattenLngLatCoordinates[itemsCount*2+1] = lat / factor; + + itemsCount++; + } + + double[] trimmedFlattenLngLatCoordinates = new double[itemsCount * 2]; + System.arraycopy(flattenLngLatCoordinates, 0, trimmedFlattenLngLatCoordinates, 0, itemsCount * 2); + return new FlattenListOfPoints(trimmedFlattenLngLatCoordinates, null); + } + /** * Encodes a sequence of Points into an encoded path string. * @@ -113,6 +176,42 @@ public static String encode(@NonNull final List path, int precision) { return result.toString(); } + /** + * Encodes a {@link FlattenListOfPoints} into an encoded path string. + * + * @param flattenListOfPoints a {@link FlattenListOfPoints} making up the line + * @param precision OSRMv4 uses 6, OSRMv5 and Google uses 5 + * @return a String representing a path string + */ + @NonNull + public static String encode(@NonNull final FlattenListOfPoints flattenListOfPoints, int precision) { + long lastLat = 0; + long lastLng = 0; + + final StringBuilder result = new StringBuilder(); + + // OSRM uses precision=6, the default Polyline spec divides by 1E5, capping at precision=5 + double factor = Math.pow(10, precision); + + double[] flattenLngLatArray = flattenListOfPoints.getFlattenLngLatArray(); + for (int i = 0; i < flattenLngLatArray.length / 2; i++) { + double longitude = flattenLngLatArray[i * 2]; + double latitude = flattenLngLatArray[i * 2 + 1]; + long lat = Math.round(latitude * factor); + long lng = Math.round(longitude * factor); + + long varLat = lat - lastLat; + long varLng = lng - lastLng; + + encode(varLat, result); + encode(varLng, result); + + lastLat = lat; + lastLng = lng; + } + return result.toString(); + } + private static void encode(long variable, StringBuilder result) { variable = variable < 0 ? ~(variable << 1) : variable << 1; while (variable >= 0x20) { From b0fb8f77377a3f2517f34ed1d7e153ff5a2878d7 Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 26 Jan 2026 16:56:14 +0200 Subject: [PATCH 17/31] Build LineString.toString using the FlattenListOfPoints --- .../mapbox/geojson/FlattenListOfPoints.java | 40 +++++++++++++++++++ .../java/com/mapbox/geojson/LineString.java | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java index aafcb4c98..34b4e267b 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java @@ -33,6 +33,7 @@ public class FlattenListOfPoints implements Serializable { /** * An array to store the {@link BoundingBox} of each coordinate or null if the coordinate does * not have bounding box. + * In practice is very unlikely that the points have bounding box when inside a list of points. */ @Nullable private BoundingBox[] boundingBoxes; @@ -167,4 +168,43 @@ public int hashCode() { Arrays.hashCode(boundingBoxes) ); } + + @Override + public String toString() { + int totalPoints = flattenLngLatPoints.length / 2; + + int iMax = totalPoints - 1; + if (iMax == -1) { + return "[]"; + } + + StringBuilder b = new StringBuilder(); + b.append("["); + + for (int i = 0; ; i++) { + b.append("Point{type=Point, bbox="); + if (boundingBoxes != null) { + BoundingBox boundingBox = boundingBoxes[i]; + b.append(boundingBox); + } else { + b.append("null"); + } + b.append(", coordinates=["); + b.append(flattenLngLatPoints[i * 2]); + b.append(", "); + b.append(flattenLngLatPoints[i * 2 + 1]); + if (altitudes != null && !Double.isNaN(altitudes[i])) { + b.append(", "); + b.append(altitudes[i]); + } + b.append("]}"); + if (i == iMax) { + b.append("]"); + break; + } + b.append(", "); + } + + return b.toString(); + } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index b7e19e4dd..80901f1b9 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -313,7 +313,7 @@ public String toString() { return "LineString{" + "type=" + type + ", " + "bbox=" + bbox + ", " - + "coordinates=" + coordinates() + + "coordinates=" + flattenCoordinates() + "}"; } From c3d7bab37e470ec48a167c69c958ebddfe92223c Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 27 Jan 2026 16:47:00 +0200 Subject: [PATCH 18/31] Code style fixes --- .../mapbox/geojson/FlattenListOfPoints.java | 6 +- .../java/com/mapbox/geojson/LineString.java | 80 ++++++++++--------- .../mapbox/geojson/utils/PolylineUtils.java | 15 +++- 3 files changed, 55 insertions(+), 46 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java index 34b4e267b..0913d609e 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java @@ -181,7 +181,7 @@ public String toString() { StringBuilder b = new StringBuilder(); b.append("["); - for (int i = 0; ; i++) { + for (int i = 0;; i++) { b.append("Point{type=Point, bbox="); if (boundingBoxes != null) { BoundingBox boundingBox = boundingBoxes[i]; @@ -199,8 +199,8 @@ public String toString() { } b.append("]}"); if (i == iMax) { - b.append("]"); - break; + b.append("]"); + break; } b.append(", "); } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index 80901f1b9..1908b5c69 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -95,6 +95,20 @@ public static LineString fromLngLats(@NonNull MultiPoint multiPoint) { return new LineString(TYPE, null, multiPoint.coordinates()); } + /** + * Create a new instance of this class by defining a {@link MultiPoint} object and passing. The + * multipoint object should comply with the GeoJson specifications described in the documentation. + * + * @param multiPoint which will make up the LineString geometry + * @param bbox optionally include a bbox definition as a double array + * @return a new instance of this class defined by the values passed inside this static factory + * method + * @since 3.0.0 + */ + public static LineString fromLngLats(@NonNull MultiPoint multiPoint, @Nullable BoundingBox bbox) { + return new LineString(TYPE, bbox, multiPoint.coordinates()); + } + /** * Create a new instance of this class by defining a list of {@link Point}s which follow the * correct specifications described in the Point documentation. Note that there should not be any @@ -132,18 +146,12 @@ public static LineString fromLngLats(@NonNull List points, @Nullable Boun return new LineString(TYPE, bbox, points); } - /** - * Create a new instance of this class by defining a {@link MultiPoint} object and passing. The - * multipoint object should comply with the GeoJson specifications described in the documentation. - * - * @param multiPoint which will make up the LineString geometry - * @param bbox optionally include a bbox definition as a double array - * @return a new instance of this class defined by the values passed inside this static factory - * method - * @since 3.0.0 - */ - public static LineString fromLngLats(@NonNull MultiPoint multiPoint, @Nullable BoundingBox bbox) { - return new LineString(TYPE, bbox, multiPoint.coordinates()); + static LineString fromLngLats(double[][] coordinates) { + ArrayList converted = new ArrayList<>(coordinates.length); + for (int i = 0; i < coordinates.length; i++) { + converted.add(Point.fromLngLat(coordinates[i])); + } + return LineString.fromLngLats(converted); } /** @@ -167,13 +175,15 @@ public static LineString fromFlattenListOfPoints( CoordinateShifter coordinateShifter = CoordinateShifterManager.getCoordinateShifter(); // Iterate all the points and shift them for (int i = 0; i < flattenLngLatArray.length / 2; i++) { + double lon = flattenLngLatArray[i * 2]; + double lat = flattenLngLatArray[(i * 2) + 1]; if (altitudes != null && !Double.isNaN(altitudes[i])) { - double[] shifted = coordinateShifter.shift(flattenLngLatArray[i * 2], flattenLngLatArray[(i * 2) + 1], altitudes[i]); + double[] shifted = coordinateShifter.shift(lon, lat, altitudes[i]); flattenLngLatArray[i * 2] = shifted[0]; flattenLngLatArray[(i * 2) + 1] = shifted[1]; altitudes[i] = shifted[2]; } else { - double[] shifted = coordinateShifter.shift(flattenLngLatArray[i * 2], flattenLngLatArray[(i * 2) + 1]); + double[] shifted = coordinateShifter.shift(lon, lat); flattenLngLatArray[i * 2] = shifted[0]; flattenLngLatArray[(i * 2) + 1] = shifted[1]; } @@ -182,30 +192,6 @@ public static LineString fromFlattenListOfPoints( return new LineString(TYPE, bbox, flattenListOfPoints); } - LineString(String type, @Nullable BoundingBox bbox, List coordinates) { - this(type, bbox, new FlattenListOfPoints(coordinates)); - } - - LineString(String type, @Nullable BoundingBox bbox, FlattenListOfPoints flattenListOfPoints) { - if (type == null) { - throw new NullPointerException("Null type"); - } - if (flattenListOfPoints == null) { - throw new NullPointerException("Null coordinates"); - } - this.flattenListOfPoints = flattenListOfPoints; - this.type = type; - this.bbox = bbox; - } - - static LineString fromLngLats(double[][] coordinates) { - ArrayList converted = new ArrayList<>(coordinates.length); - for (int i = 0; i < coordinates.length; i++) { - converted.add(Point.fromLngLat(coordinates[i])); - } - return LineString.fromLngLats(converted); - } - /** * Create a new instance of this class by convert a polyline string into a lineString. This is * handy when an API provides you with an encoded string representing the line geometry and you'd @@ -225,6 +211,22 @@ public static LineString fromPolyline(@NonNull String polyline, int precision) { return LineString.fromFlattenListOfPoints(points, null); } + LineString(String type, @Nullable BoundingBox bbox, List coordinates) { + this(type, bbox, new FlattenListOfPoints(coordinates)); + } + + LineString(String type, @Nullable BoundingBox bbox, FlattenListOfPoints flattenListOfPoints) { + if (type == null) { + throw new NullPointerException("Null type"); + } + if (flattenListOfPoints == null) { + throw new NullPointerException("Null coordinates"); + } + this.flattenListOfPoints = flattenListOfPoints; + this.type = type; + this.bbox = bbox; + } + /** * This describes the TYPE of GeoJson geometry this object is, thus this will always return * {@link LineString}. @@ -258,10 +260,10 @@ public BoundingBox bbox() { /** * Provides the list of {@link Point}s that make up the LineString geometry. *

- * Please consider using {@link #flattenCoordinates()} instead for better performance. * * @return a list of points * @since 3.0.0 + * @deprecated Please consider using {@link #flattenCoordinates()} instead for better performance. */ @NonNull @Override diff --git a/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java b/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java index f811453cb..763e6ed6b 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java @@ -131,14 +131,17 @@ public static FlattenListOfPoints decodeToFlattenListOfPoints( while (temp >= 0x1f); lng += (result & 1) != 0 ? ~(result >> 1) : (result >> 1); - flattenLngLatCoordinates[itemsCount*2] = lng / factor; - flattenLngLatCoordinates[itemsCount*2+1] = lat / factor; + flattenLngLatCoordinates[itemsCount * 2] = lng / factor; + flattenLngLatCoordinates[itemsCount * 2 + 1] = lat / factor; itemsCount++; } double[] trimmedFlattenLngLatCoordinates = new double[itemsCount * 2]; - System.arraycopy(flattenLngLatCoordinates, 0, trimmedFlattenLngLatCoordinates, 0, itemsCount * 2); + System.arraycopy(flattenLngLatCoordinates, 0, + trimmedFlattenLngLatCoordinates, 0, + itemsCount * 2 + ); return new FlattenListOfPoints(trimmedFlattenLngLatCoordinates, null); } @@ -184,7 +187,11 @@ public static String encode(@NonNull final List path, int precision) { * @return a String representing a path string */ @NonNull - public static String encode(@NonNull final FlattenListOfPoints flattenListOfPoints, int precision) { + public static String encode( + @NonNull + final FlattenListOfPoints flattenListOfPoints, + int precision + ) { long lastLat = 0; long lastLng = 0; From e264d6f500a16b5367252474ea5ca57eaa356705 Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 27 Jan 2026 17:03:59 +0200 Subject: [PATCH 19/31] Use flattenListOfPoints for the `toString` --- .../src/main/java/com/mapbox/geojson/LineString.java | 2 +- .../src/main/java/com/mapbox/geojson/MultiPoint.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index 1908b5c69..91b4679b7 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -315,7 +315,7 @@ public String toString() { return "LineString{" + "type=" + type + ", " + "bbox=" + bbox + ", " - + "coordinates=" + flattenCoordinates() + + "coordinates=" + flattenListOfPoints + "}"; } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java index bfa3a7caa..5bd16bb7c 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java @@ -193,7 +193,7 @@ public String toString() { return "MultiPoint{" + "type=" + type + ", " + "bbox=" + bbox + ", " - + "coordinates=" + flattenListOfPoints.points() + + "coordinates=" + flattenListOfPoints + "}"; } From e375321f56c5e0a420c3ad55761f81ac278d64b5 Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 27 Jan 2026 17:04:34 +0200 Subject: [PATCH 20/31] Update services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java --- .../src/main/java/com/mapbox/geojson/FlattenListOfPoints.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java index 0913d609e..51f4d34b7 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java @@ -82,7 +82,7 @@ public FlattenListOfPoints(@NonNull double[] flattenLngLatPoints, @Nullable doub altitudes[i] = Double.NaN; } - // Similarly to altitudes, if one point has bound we create an array to store those. + // Similarly to altitudes, if one point has bounding box we create an array to store those. if (point.bbox() != null) { if (boundingBoxes == null) { boundingBoxes = new BoundingBox[points.size()]; From aa51c393134dc045cd3ddfe7a61b1e0d4f91a23d Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 29 Jan 2026 17:11:43 +0200 Subject: [PATCH 21/31] PolylineUtils#decodeToFlattenListOfPoints returns double[] --- .../java/com/mapbox/geojson/LineString.java | 35 +++++++------------ .../mapbox/geojson/utils/PolylineUtils.java | 7 ++-- .../com/mapbox/geojson/LineStringTest.java | 3 +- .../mapbox/geojson/shifter/ShifterTest.java | 3 +- 4 files changed, 19 insertions(+), 29 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index 91b4679b7..f81a4bd49 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -155,41 +155,32 @@ static LineString fromLngLats(double[][] coordinates) { } /** - * Create a new instance of this class by defining a {@link FlattenListOfPoints} object. - * The multipoint object should comply with the GeoJson specifications described in the + * Create a new instance by providing a flatten array of [lng1, lat1, lng2, lat2, ...]. + * The flatten array object should comply with the GeoJson specifications described in the * documentation. * - * @param flattenListOfPoints which will make up the LineString geometry. The points will be - * shifted according to the current - * {@link CoordinateShifterManager#getCoordinateShifter()} + * @param flattenLngLatArray which will make up the LineString geometry. WARNING: The points will + * be shifted according to the current + * {@link CoordinateShifterManager#getCoordinateShifter()} in place! * @param bbox optionally include a bbox definition * @return a new instance of this class defined by the values passed inside this static factory * method */ - public static LineString fromFlattenListOfPoints( - FlattenListOfPoints flattenListOfPoints, + public static LineString fromFlattenArrayOfPoints( + double[] flattenLngLatArray, @Nullable BoundingBox bbox ) { - double[] flattenLngLatArray = flattenListOfPoints.getFlattenLngLatArray(); - double[] altitudes = flattenListOfPoints.getAltitudes(); CoordinateShifter coordinateShifter = CoordinateShifterManager.getCoordinateShifter(); // Iterate all the points and shift them for (int i = 0; i < flattenLngLatArray.length / 2; i++) { double lon = flattenLngLatArray[i * 2]; double lat = flattenLngLatArray[(i * 2) + 1]; - if (altitudes != null && !Double.isNaN(altitudes[i])) { - double[] shifted = coordinateShifter.shift(lon, lat, altitudes[i]); - flattenLngLatArray[i * 2] = shifted[0]; - flattenLngLatArray[(i * 2) + 1] = shifted[1]; - altitudes[i] = shifted[2]; - } else { - double[] shifted = coordinateShifter.shift(lon, lat); - flattenLngLatArray[i * 2] = shifted[0]; - flattenLngLatArray[(i * 2) + 1] = shifted[1]; - } + double[] shifted = coordinateShifter.shift(lon, lat); + flattenLngLatArray[i * 2] = shifted[0]; + flattenLngLatArray[(i * 2) + 1] = shifted[1]; } - return new LineString(TYPE, bbox, flattenListOfPoints); + return new LineString(TYPE, bbox, new FlattenListOfPoints(flattenLngLatArray, null)); } /** @@ -207,8 +198,8 @@ public static LineString fromFlattenListOfPoints( * @since 1.0.0 */ public static LineString fromPolyline(@NonNull String polyline, int precision) { - FlattenListOfPoints points = PolylineUtils.decodeToFlattenListOfPoints(polyline, precision); - return LineString.fromFlattenListOfPoints(points, null); + double[] points = PolylineUtils.decodeToFlattenListOfPoints(polyline, precision); + return LineString.fromFlattenArrayOfPoints(points, null); } LineString(String type, @Nullable BoundingBox bbox, List coordinates) { diff --git a/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java b/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java index 763e6ed6b..ecdb35c14 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java @@ -86,12 +86,13 @@ public static List decode(@NonNull final String encodedPath, int precisio * * @param encodedPath a String representing an encoded path string * @param precision OSRMv4 uses 6, OSRMv5 and Google uses 5 - * @return a {@link FlattenListOfPoints} making up the line + * @return an array of doubles representing a line geometry with flattened points + * in the form: [lng1, lat1, lng2, lat2, ...] * @see Part of algorithm came from this source * @see Part of algorithm came from this source. */ @NonNull - public static FlattenListOfPoints decodeToFlattenListOfPoints( + public static double[] decodeToFlattenListOfPoints( @NonNull final String encodedPath, int precision @@ -142,7 +143,7 @@ public static FlattenListOfPoints decodeToFlattenListOfPoints( trimmedFlattenLngLatCoordinates, 0, itemsCount * 2 ); - return new FlattenListOfPoints(trimmedFlattenLngLatCoordinates, null); + return trimmedFlattenLngLatCoordinates; } /** diff --git a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java index 8717509c0..b38e64dba 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -47,8 +47,7 @@ public void fromLngLats_generatedFromMultipoint() throws Exception { @Test public void fromFlattenListOfPoints() throws Exception { double[] flattenLngLatPoints= new double[]{1.0, 2.0, 4.0, 8.0}; - FlattenListOfPoints flattenListOfPoints = new FlattenListOfPoints(flattenLngLatPoints, null); - LineString lineString = LineString.fromFlattenListOfPoints(flattenListOfPoints, null); + LineString lineString = LineString.fromFlattenArrayOfPoints(flattenLngLatPoints, null); assertEquals("_gayB_c`|@_wemJ_kbvD", lineString.toPolyline(PRECISION_6)); } diff --git a/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java b/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java index 67353d3cd..b81a46e2c 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java @@ -224,8 +224,7 @@ public void linestring_basic_shift_with_bbox() { jsonString); double[] flattenLngLatPoints= new double[]{1.0, 1.0, 2.0, 2.0, 3.0, 3.0}; - FlattenListOfPoints flattenListOfPoints = new FlattenListOfPoints(flattenLngLatPoints, null); - LineString lineString2 = LineString.fromFlattenListOfPoints(flattenListOfPoints, bbox); + LineString lineString2 = LineString.fromFlattenArrayOfPoints(flattenLngLatPoints, bbox); String jsonString2 = lineString2.toJson(); compareJson("{\"coordinates\":[[1,1],[2,2],[3,3]]," + "\"type\":\"LineString\",\"bbox\":[1.0,2.0,3.0,4.0]}", From 9c85c7d0a4d544b543c411da67b941298b951939 Mon Sep 17 00:00:00 2001 From: Ramon Date: Thu, 29 Jan 2026 21:44:00 +0200 Subject: [PATCH 22/31] Fixed TurfMiscTest tests --- .../java/com/mapbox/turf/TurfMiscTest.java | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java b/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java index ab87ad030..266bc3a54 100644 --- a/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java +++ b/services-turf/src/test/java/com/mapbox/turf/TurfMiscTest.java @@ -15,6 +15,7 @@ import static junit.framework.TestCase.assertNotNull; import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; @@ -453,12 +454,14 @@ public void testLineSliceAlongLine1() throws IOException, TurfException { Point end_point = TurfMeasurement.along(lineStringLine1, stop, TurfConstants.UNIT_MILES); LineString sliced = TurfMisc.lineSliceAlong(line1, start, stop, TurfConstants.UNIT_MILES); - assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), + List slicedCoordinates = sliced.coordinates(); + assertEquals(slicedCoordinates.get(0).coordinates(), start_point.coordinates()); + assertArrayEquals(slicedCoordinates.get(0).flattenCoordinates(), + start_point.flattenCoordinates(), DELTA); + assertEquals(slicedCoordinates.get(slicedCoordinates.size() - 1).coordinates(), end_point.coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), - end_point.flattenCoordinates()); + assertArrayEquals(slicedCoordinates.get(slicedCoordinates.size() - 1).flattenCoordinates(), + end_point.flattenCoordinates(), DELTA); } @Test @@ -473,12 +476,14 @@ public void testLineSliceAlongOvershootLine1() throws IOException, TurfException Point end_point = TurfMeasurement.along(lineStringLine1, stop, TurfConstants.UNIT_MILES); LineString sliced = TurfMisc.lineSliceAlong(line1, start, stop, TurfConstants.UNIT_MILES); - assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), + List slicedCoordinates = sliced.coordinates(); + assertEquals(slicedCoordinates.get(0).coordinates(), start_point.coordinates()); + assertArrayEquals(slicedCoordinates.get(0).flattenCoordinates(), + start_point.flattenCoordinates(), DELTA); + assertEquals(slicedCoordinates.get(slicedCoordinates.size() - 1).coordinates(), end_point.coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), - end_point.flattenCoordinates()); + assertArrayEquals(slicedCoordinates.get(slicedCoordinates.size() - 1).flattenCoordinates(), + end_point.flattenCoordinates(), DELTA); } @Test @@ -494,12 +499,14 @@ public void testLineSliceAlongRoute1() throws IOException, TurfException { LineString sliced = TurfMisc.lineSliceAlong(route1, start, stop, TurfConstants.UNIT_MILES); - assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), + List slicedCoordinates = sliced.coordinates(); + assertEquals(slicedCoordinates.get(0).coordinates(), start_point.coordinates()); + assertArrayEquals(slicedCoordinates.get(0).flattenCoordinates(), + start_point.flattenCoordinates(), DELTA); + assertEquals(slicedCoordinates.get(slicedCoordinates.size() - 1).coordinates(), end_point.coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), - end_point.flattenCoordinates()); + assertArrayEquals(slicedCoordinates.get(slicedCoordinates.size() - 1).flattenCoordinates(), + end_point.flattenCoordinates(), DELTA); } @Test @@ -515,11 +522,12 @@ public void testLineSliceAlongRoute2() throws IOException, TurfException { LineString sliced = TurfMisc.lineSliceAlong(route2, start, stop, TurfConstants.UNIT_MILES); assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); + assertArrayEquals(sliced.coordinates().get(0).flattenCoordinates(), + start_point.flattenCoordinates(), DELTA); assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), end_point.coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), - end_point.flattenCoordinates()); + assertArrayEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), + end_point.flattenCoordinates(), DELTA); } @Test @@ -544,13 +552,15 @@ public void testLineAlongStopLongerThanLength() throws IOException, TurfExceptio Point start_point = TurfMeasurement.along(lineStringLine1, start, TurfConstants.UNIT_MILES); List lineCoordinates = lineStringLine1.coordinates(); LineString sliced = TurfMisc.lineSliceAlong(line1, start, stop, TurfConstants.UNIT_MILES); - assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); + List slicedCoordinates = sliced.coordinates(); + assertEquals(slicedCoordinates.get(0).coordinates(), start_point.coordinates()); + assertArrayEquals(slicedCoordinates.get(0).flattenCoordinates(), + start_point.flattenCoordinates(), DELTA); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), + assertEquals(slicedCoordinates.get(slicedCoordinates.size() - 1).coordinates(), lineCoordinates.get(lineCoordinates.size() - 1).coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), - lineCoordinates.get(lineCoordinates.size() - 1).flattenCoordinates()); + assertArrayEquals(slicedCoordinates.get(slicedCoordinates.size() - 1).flattenCoordinates(), + lineCoordinates.get(lineCoordinates.size() - 1).flattenCoordinates(), DELTA); } @Test @@ -568,11 +578,13 @@ public void testShortLine() throws IOException, TurfException { Point end_point = TurfMeasurement.along(lineStringLine1, stop, TurfConstants.UNIT_MILES); LineString sliced = TurfMisc.lineSliceAlong(lineStringLine1, start, stop, TurfConstants.UNIT_MILES); - assertEquals(sliced.coordinates().get(0).coordinates(), start_point.coordinates()); - assertEquals(sliced.coordinates().get(0).flattenCoordinates(), start_point.flattenCoordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).coordinates(), + List slicedCoordinates = sliced.coordinates(); + assertEquals(slicedCoordinates.get(0).coordinates(), start_point.coordinates()); + assertArrayEquals(slicedCoordinates.get(0).flattenCoordinates(), + start_point.flattenCoordinates(), DELTA); + assertEquals(slicedCoordinates.get(slicedCoordinates.size() - 1).coordinates(), end_point.coordinates()); - assertEquals(sliced.coordinates().get(sliced.coordinates().size() - 1).flattenCoordinates(), - end_point.flattenCoordinates()); + assertArrayEquals(slicedCoordinates.get(slicedCoordinates.size() - 1).flattenCoordinates(), + end_point.flattenCoordinates(), DELTA); } } \ No newline at end of file From be6d8b83aaa64026c87c3f43bab42794de6ac310 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 30 Jan 2026 07:48:37 +0200 Subject: [PATCH 23/31] Add FlattenListOfPoints unit tests --- .../geojson/FlattenListOfPointsTest.java | 419 ++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 services-geojson/src/test/java/com/mapbox/geojson/FlattenListOfPointsTest.java diff --git a/services-geojson/src/test/java/com/mapbox/geojson/FlattenListOfPointsTest.java b/services-geojson/src/test/java/com/mapbox/geojson/FlattenListOfPointsTest.java new file mode 100644 index 000000000..76af95bfb --- /dev/null +++ b/services-geojson/src/test/java/com/mapbox/geojson/FlattenListOfPointsTest.java @@ -0,0 +1,419 @@ +package com.mapbox.geojson; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class FlattenListOfPointsTest extends TestUtils { + + @Test + public void constructor_withArrays_storesDataCorrectly() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + double[] altitudes = new double[]{5.0, 6.0}; + + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, altitudes); + + assertNotNull(flatten); + assertArrayEquals(lngLatArray, flatten.getFlattenLngLatArray(), DELTA); + assertArrayEquals(altitudes, flatten.getAltitudes(), DELTA); + } + + @Test + public void constructor_withArrays_nullAltitudes() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, null); + + assertNotNull(flatten); + assertArrayEquals(lngLatArray, flatten.getFlattenLngLatArray(), DELTA); + assertNull(flatten.getAltitudes()); + } + + @Test + public void constructor_withEmptyList_createsEmptyFlatten() { + List points = new ArrayList<>(); + + FlattenListOfPoints flatten = new FlattenListOfPoints(points); + + assertNotNull(flatten); + assertEquals(0, flatten.getFlattenLngLatArray().length); + assertNull(flatten.getAltitudes()); + } + + @Test + public void constructor_withPointsNoAltitude_storesLngLatOnly() { + List points = new ArrayList<>(); + points.add(Point.fromLngLat(1.0, 2.0)); + points.add(Point.fromLngLat(3.0, 4.0)); + points.add(Point.fromLngLat(5.0, 6.0)); + + FlattenListOfPoints flatten = new FlattenListOfPoints(points); + + assertNotNull(flatten); + double[] expected = new double[]{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + assertArrayEquals(expected, flatten.getFlattenLngLatArray(), DELTA); + assertNull(flatten.getAltitudes()); + } + + @Test + public void constructor_withPointsWithAltitude_storesAllData() { + List points = new ArrayList<>(); + points.add(Point.fromLngLat(1.0, 2.0, 10.0)); + points.add(Point.fromLngLat(3.0, 4.0, 20.0)); + points.add(Point.fromLngLat(5.0, 6.0, 30.0)); + + FlattenListOfPoints flatten = new FlattenListOfPoints(points); + + assertNotNull(flatten); + double[] expectedLngLat = new double[]{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + double[] expectedAlt = new double[]{10.0, 20.0, 30.0}; + assertArrayEquals(expectedLngLat, flatten.getFlattenLngLatArray(), DELTA); + assertArrayEquals(expectedAlt, flatten.getAltitudes(), DELTA); + } + + @Test + public void constructor_withMixedAltitudes_storesNaNForMissingAltitudes() { + List points = new ArrayList<>(); + points.add(Point.fromLngLat(1.0, 2.0)); // no altitude + points.add(Point.fromLngLat(3.0, 4.0, 20.0)); // with altitude + points.add(Point.fromLngLat(5.0, 6.0)); // no altitude + points.add(Point.fromLngLat(7.0, 8.0, 40.0)); // with altitude + + FlattenListOfPoints flatten = new FlattenListOfPoints(points); + + assertNotNull(flatten); + double[] expectedLngLat = new double[]{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0}; + assertArrayEquals(expectedLngLat, flatten.getFlattenLngLatArray(), DELTA); + + double[] altitudes = flatten.getAltitudes(); + assertNotNull(altitudes); + assertEquals(4, altitudes.length); + assertTrue(Double.isNaN(altitudes[0])); + assertEquals(20.0, altitudes[1], DELTA); + assertTrue(Double.isNaN(altitudes[2])); + assertEquals(40.0, altitudes[3], DELTA); + } + + @Test + public void constructor_withSinglePoint_worksCorrectly() { + List points = new ArrayList<>(); + points.add(Point.fromLngLat(1.0, 2.0, 10.0)); + + FlattenListOfPoints flatten = new FlattenListOfPoints(points); + + assertNotNull(flatten); + double[] expectedLngLat = new double[]{1.0, 2.0}; + double[] expectedAlt = new double[]{10.0}; + assertArrayEquals(expectedLngLat, flatten.getFlattenLngLatArray(), DELTA); + assertArrayEquals(expectedAlt, flatten.getAltitudes(), DELTA); + } + + @Test + public void points_returnsEmptyListForEmptyFlatten() { + double[] lngLatArray = new double[]{}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, null); + + List points = flatten.points(); + + assertNotNull(points); + assertEquals(0, points.size()); + } + + @Test + public void points_reconstructsPointsWithoutAltitude() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, null); + + List points = flatten.points(); + + assertNotNull(points); + assertEquals(2, points.size()); + assertEquals(1.0, points.get(0).longitude(), DELTA); + assertEquals(2.0, points.get(0).latitude(), DELTA); + assertFalse(points.get(0).hasAltitude()); + assertEquals(3.0, points.get(1).longitude(), DELTA); + assertEquals(4.0, points.get(1).latitude(), DELTA); + assertFalse(points.get(1).hasAltitude()); + } + + @Test + public void points_reconstructsPointsWithAltitude() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + double[] altitudes = new double[]{10.0, 20.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, altitudes); + + List points = flatten.points(); + + assertNotNull(points); + assertEquals(2, points.size()); + assertEquals(1.0, points.get(0).longitude(), DELTA); + assertEquals(2.0, points.get(0).latitude(), DELTA); + assertEquals(10.0, points.get(0).altitude(), DELTA); + assertTrue(points.get(0).hasAltitude()); + assertEquals(3.0, points.get(1).longitude(), DELTA); + assertEquals(4.0, points.get(1).latitude(), DELTA); + assertEquals(20.0, points.get(1).altitude(), DELTA); + assertTrue(points.get(1).hasAltitude()); + } + + @Test + public void points_reconstructsPointsWithMixedAltitudes() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + double[] altitudes = new double[]{Double.NaN, 20.0, 30.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, altitudes); + + List points = flatten.points(); + + assertNotNull(points); + assertEquals(3, points.size()); + assertFalse(points.get(0).hasAltitude()); + assertTrue(points.get(1).hasAltitude()); + assertEquals(20.0, points.get(1).altitude(), DELTA); + assertTrue(points.get(2).hasAltitude()); + assertEquals(30.0, points.get(2).altitude(), DELTA); + } + + @Test + public void points_roundTrip_preservesData() { + List originalPoints = new ArrayList<>(); + originalPoints.add(Point.fromLngLat(1.0, 2.0, 10.0)); + originalPoints.add(Point.fromLngLat(3.0, 4.0)); + originalPoints.add(Point.fromLngLat(5.0, 6.0, 30.0)); + + FlattenListOfPoints flatten = new FlattenListOfPoints(originalPoints); + List reconstructedPoints = flatten.points(); + + assertEquals(originalPoints.size(), reconstructedPoints.size()); + for (int i = 0; i < originalPoints.size(); i++) { + Point original = originalPoints.get(i); + Point reconstructed = reconstructedPoints.get(i); + assertEquals(original.longitude(), reconstructed.longitude(), DELTA); + assertEquals(original.latitude(), reconstructed.latitude(), DELTA); + assertEquals(original.hasAltitude(), reconstructed.hasAltitude()); + if (original.hasAltitude()) { + assertEquals(original.altitude(), reconstructed.altitude(), DELTA); + } + } + } + + @Test + public void equals_sameFlatten_returnsTrue() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + double[] altitudes = new double[]{10.0, 20.0}; + FlattenListOfPoints flatten1 = new FlattenListOfPoints(lngLatArray, altitudes); + FlattenListOfPoints flatten2 = new FlattenListOfPoints(lngLatArray, altitudes); + + assertTrue(flatten1.equals(flatten2)); + assertTrue(flatten2.equals(flatten1)); + } + + @Test + public void equals_differentLngLat_returnsFalse() { + double[] lngLatArray1 = new double[]{1.0, 2.0, 3.0, 4.0}; + double[] lngLatArray2 = new double[]{1.0, 2.0, 5.0, 6.0}; + double[] altitudes = new double[]{10.0, 20.0}; + FlattenListOfPoints flatten1 = new FlattenListOfPoints(lngLatArray1, altitudes); + FlattenListOfPoints flatten2 = new FlattenListOfPoints(lngLatArray2, altitudes); + + assertFalse(flatten1.equals(flatten2)); + } + + @Test + public void equals_differentAltitudes_returnsFalse() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + double[] altitudes1 = new double[]{10.0, 20.0}; + double[] altitudes2 = new double[]{10.0, 30.0}; + FlattenListOfPoints flatten1 = new FlattenListOfPoints(lngLatArray, altitudes1); + FlattenListOfPoints flatten2 = new FlattenListOfPoints(lngLatArray, altitudes2); + + assertFalse(flatten1.equals(flatten2)); + } + + @Test + public void equals_oneWithAltitudeOneWithout_returnsFalse() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + double[] altitudes = new double[]{10.0, 20.0}; + FlattenListOfPoints flatten1 = new FlattenListOfPoints(lngLatArray, altitudes); + FlattenListOfPoints flatten2 = new FlattenListOfPoints(lngLatArray, null); + + assertFalse(flatten1.equals(flatten2)); + } + + @Test + public void equals_withNull_returnsFalse() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, null); + + assertFalse(flatten.equals(null)); + } + + @Test + public void equals_withDifferentType_returnsFalse() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, null); + + assertFalse(flatten.equals("not a FlattenListOfPoints")); + } + + @Test + public void hashCode_sameData_returnsSameHashCode() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + double[] altitudes = new double[]{10.0, 20.0}; + FlattenListOfPoints flatten1 = new FlattenListOfPoints(lngLatArray, altitudes); + FlattenListOfPoints flatten2 = new FlattenListOfPoints(lngLatArray, altitudes); + + assertEquals(flatten1.hashCode(), flatten2.hashCode()); + } + + @Test + public void hashCode_differentData_returnsDifferentHashCode() { + double[] lngLatArray1 = new double[]{1.0, 2.0, 3.0, 4.0}; + double[] lngLatArray2 = new double[]{1.0, 2.0, 5.0, 6.0}; + double[] altitudes = new double[]{10.0, 20.0}; + FlattenListOfPoints flatten1 = new FlattenListOfPoints(lngLatArray1, altitudes); + FlattenListOfPoints flatten2 = new FlattenListOfPoints(lngLatArray2, altitudes); + + assertNotEquals(flatten1.hashCode(), flatten2.hashCode()); + } + + @Test + public void toString_emptyFlatten_returnsEmptyBrackets() { + double[] lngLatArray = new double[]{}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, null); + + String result = flatten.toString(); + + assertEquals("[]", result); + } + + @Test + public void toString_singlePointWithoutAltitude_returnsCorrectFormat() { + double[] lngLatArray = new double[]{1.0, 2.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, null); + + String result = flatten.toString(); + + assertEquals("[Point{type=Point, bbox=null, coordinates=[1.0, 2.0]}]", result); + } + + @Test + public void toString_singlePointWithAltitude_returnsCorrectFormat() { + double[] lngLatArray = new double[]{1.0, 2.0}; + double[] altitudes = new double[]{10.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, altitudes); + + String result = flatten.toString(); + + assertEquals("[Point{type=Point, bbox=null, coordinates=[1.0, 2.0, 10.0]}]", result); + } + + @Test + public void toString_multiplePointsWithoutAltitude_returnsCorrectFormat() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, null); + + String result = flatten.toString(); + + String expected = "[Point{type=Point, bbox=null, coordinates=[1.0, 2.0]}, " + + "Point{type=Point, bbox=null, coordinates=[3.0, 4.0]}]"; + assertEquals(expected, result); + } + + @Test + public void toString_multiplePointsWithAltitude_returnsCorrectFormat() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0}; + double[] altitudes = new double[]{10.0, 20.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, altitudes); + + String result = flatten.toString(); + + String expected = "[Point{type=Point, bbox=null, coordinates=[1.0, 2.0, 10.0]}, " + + "Point{type=Point, bbox=null, coordinates=[3.0, 4.0, 20.0]}]"; + assertEquals(expected, result); + } + + @Test + public void toString_mixedAltitudes_returnsCorrectFormat() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + double[] altitudes = new double[]{10.0, Double.NaN, 30.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, altitudes); + + String result = flatten.toString(); + + String expected = "[Point{type=Point, bbox=null, coordinates=[1.0, 2.0, 10.0]}, " + + "Point{type=Point, bbox=null, coordinates=[3.0, 4.0]}, " + + "Point{type=Point, bbox=null, coordinates=[5.0, 6.0, 30.0]}]"; + assertEquals(expected, result); + } + + @Test + public void testSerializable() throws IOException, ClassNotFoundException { + List points = new ArrayList<>(); + points.add(Point.fromLngLat(1.0, 2.0, 10.0)); + points.add(Point.fromLngLat(3.0, 4.0)); + points.add(Point.fromLngLat(5.0, 6.0, 30.0)); + + FlattenListOfPoints flatten = new FlattenListOfPoints(points); + + byte[] bytes = serialize(flatten); + FlattenListOfPoints deserialized = deserialize(bytes, FlattenListOfPoints.class); + + assertEquals(flatten, deserialized); + } + + @Test + public void constructor_withPointsWithBoundingBox_storesBoundingBoxes() { + List points = new ArrayList<>(); + BoundingBox bbox1 = BoundingBox.fromLngLats(0.0, 0.0, 1.0, 1.0); + BoundingBox bbox2 = BoundingBox.fromLngLats(2.0, 2.0, 3.0, 3.0); + points.add(Point.fromLngLat(1.0, 2.0, bbox1)); + points.add(Point.fromLngLat(3.0, 4.0, bbox2)); + + FlattenListOfPoints flatten = new FlattenListOfPoints(points); + List reconstructedPoints = flatten.points(); + + assertNotNull(reconstructedPoints.get(0).bbox()); + assertNotNull(reconstructedPoints.get(1).bbox()); + assertEquals(bbox1, reconstructedPoints.get(0).bbox()); + assertEquals(bbox2, reconstructedPoints.get(1).bbox()); + } + + @Test + public void constructor_withMixedBoundingBoxes_handlesCorrectly() { + List points = new ArrayList<>(); + BoundingBox bbox = BoundingBox.fromLngLats(0.0, 0.0, 1.0, 1.0); + points.add(Point.fromLngLat(1.0, 2.0, bbox)); + points.add(Point.fromLngLat(3.0, 4.0)); // no bbox + + FlattenListOfPoints flatten = new FlattenListOfPoints(points); + List reconstructedPoints = flatten.points(); + + assertNotNull(reconstructedPoints.get(0).bbox()); + assertNull(reconstructedPoints.get(1).bbox()); + assertEquals(bbox, reconstructedPoints.get(0).bbox()); + } + + @Test + public void toString_withBoundingBox_includesBoundingBoxInOutput() { + List points = new ArrayList<>(); + BoundingBox bbox = BoundingBox.fromLngLats(0.0, 0.0, 1.0, 1.0); + points.add(Point.fromLngLat(1.0, 2.0, bbox)); + + FlattenListOfPoints flatten = new FlattenListOfPoints(points); + String result = flatten.toString(); + + assertTrue(result.contains("BoundingBox")); + assertTrue(result.contains("1.0")); + assertTrue(result.contains("2.0")); + } +} From ef71786768dd285a83ac18f11b666b31934bfe53 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 30 Jan 2026 07:50:49 +0200 Subject: [PATCH 24/31] Add size for FlattenListOfPoints --- .../mapbox/geojson/FlattenListOfPoints.java | 9 +++++ .../geojson/FlattenListOfPointsTest.java | 37 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java index 51f4d34b7..7442ecc1f 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPoints.java @@ -112,6 +112,15 @@ public double[] getAltitudes() { return altitudes; } + /** + * Returns the total number of points stored in this flattened structure. + * + * @return the total number of points. + */ + public int size() { + return flattenLngLatPoints.length / 2; + } + /** * Creates a list of {@link Point}s and returns it. *

diff --git a/services-geojson/src/test/java/com/mapbox/geojson/FlattenListOfPointsTest.java b/services-geojson/src/test/java/com/mapbox/geojson/FlattenListOfPointsTest.java index 76af95bfb..0ff05da7f 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/FlattenListOfPointsTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/FlattenListOfPointsTest.java @@ -39,6 +39,43 @@ public void constructor_withArrays_nullAltitudes() { assertNull(flatten.getAltitudes()); } + @Test + public void size_emptyFlatten_returnsZero() { + double[] lngLatArray = new double[]{}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, null); + + assertEquals(0, flatten.size()); + } + + @Test + public void size_singlePoint_returnsOne() { + double[] lngLatArray = new double[]{1.0, 2.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, null); + + assertEquals(1, flatten.size()); + } + + @Test + public void size_multiplePoints_returnsCorrectCount() { + double[] lngLatArray = new double[]{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; + FlattenListOfPoints flatten = new FlattenListOfPoints(lngLatArray, null); + + assertEquals(3, flatten.size()); + } + + @Test + public void size_fromListOfPoints_returnsCorrectCount() { + List points = new ArrayList<>(); + points.add(Point.fromLngLat(1.0, 2.0)); + points.add(Point.fromLngLat(3.0, 4.0)); + points.add(Point.fromLngLat(5.0, 6.0)); + points.add(Point.fromLngLat(7.0, 8.0)); + + FlattenListOfPoints flatten = new FlattenListOfPoints(points); + + assertEquals(4, flatten.size()); + } + @Test public void constructor_withEmptyList_createsEmptyFlatten() { List points = new ArrayList<>(); From ffde2c793533c785f164fc0d9db0c518ad9c1152 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 30 Jan 2026 08:51:34 +0200 Subject: [PATCH 25/31] Add more shifter test for default one --- .../mapbox/geojson/shifter/ShifterTest.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java b/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java index b81a46e2c..3940d8fb7 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/shifter/ShifterTest.java @@ -17,6 +17,8 @@ public class ShifterTest { + private static final double DELTA = 0.0001; + static class TestCoordinateShifter implements CoordinateShifter { @Override public List shiftLonLat(double lon, double lat) { @@ -89,6 +91,72 @@ public void default_shifter(){ CoordinateShifterManager.setCoordinateShifter(null); assertTrue(CoordinateShifterManager.isUsingDefaultShifter()); + + // Test default shifter behavior - it should return coordinates unchanged + CoordinateShifter defaultShifter = CoordinateShifterManager.getCoordinateShifter(); + assertNotNull(defaultShifter); + + // Test shiftLonLat - should return coordinates unchanged + List shiftedLonLat = defaultShifter.shiftLonLat(10.5, 20.3); + assertNotNull(shiftedLonLat); + assertEquals(2, shiftedLonLat.size()); + assertEquals(10.5, shiftedLonLat.get(0), DELTA); + assertEquals(20.3, shiftedLonLat.get(1), DELTA); + + // Test shiftLonLatAlt with valid altitude - should return all three coordinates + List shiftedWithAlt = defaultShifter.shiftLonLatAlt(10.5, 20.3, 30.7); + assertNotNull(shiftedWithAlt); + assertEquals(3, shiftedWithAlt.size()); + assertEquals(10.5, shiftedWithAlt.get(0), DELTA); + assertEquals(20.3, shiftedWithAlt.get(1), DELTA); + assertEquals(30.7, shiftedWithAlt.get(2), DELTA); + + // Test shiftLonLatAlt with NaN altitude - should return only lon, lat + List shiftedNoAlt = defaultShifter.shiftLonLatAlt(10.5, 20.3, Double.NaN); + assertNotNull(shiftedNoAlt); + assertEquals(2, shiftedNoAlt.size()); + assertEquals(10.5, shiftedNoAlt.get(0), DELTA); + assertEquals(20.3, shiftedNoAlt.get(1), DELTA); + + // Test shift with lon, lat - should return array unchanged + double[] shiftedArray = defaultShifter.shift(15.2, 25.8); + assertNotNull(shiftedArray); + assertEquals(2, shiftedArray.length); + assertEquals(15.2, shiftedArray[0], DELTA); + assertEquals(25.8, shiftedArray[1], DELTA); + + // Test shift with lon, lat, altitude - should return all three + double[] shiftedArrayWithAlt = defaultShifter.shift(15.2, 25.8, 35.4); + assertNotNull(shiftedArrayWithAlt); + assertEquals(3, shiftedArrayWithAlt.length); + assertEquals(15.2, shiftedArrayWithAlt[0], DELTA); + assertEquals(25.8, shiftedArrayWithAlt[1], DELTA); + assertEquals(35.4, shiftedArrayWithAlt[2], DELTA); + + // Test shift with lon, lat, NaN altitude - should return only lon, lat + double[] shiftedArrayNoAlt = defaultShifter.shift(15.2, 25.8, Double.NaN); + assertNotNull(shiftedArrayNoAlt); + assertEquals(2, shiftedArrayNoAlt.length); + assertEquals(15.2, shiftedArrayNoAlt[0], DELTA); + assertEquals(25.8, shiftedArrayNoAlt[1], DELTA); + + // Test unshiftPoint with Point object - should return point coordinates unchanged + Point testPoint = Point.fromLngLat(12.3, 45.6, 78.9); + List unshiftedPoint = defaultShifter.unshiftPoint(testPoint); + assertNotNull(unshiftedPoint); + assertEquals(testPoint.coordinates(), unshiftedPoint); + + // Test unshiftPoint with coordinates list - should return list unchanged + List testCoordinates = Arrays.asList(5.5, 6.6, 7.7); + List unshiftedCoordinates = defaultShifter.unshiftPoint(testCoordinates); + assertNotNull(unshiftedCoordinates); + assertEquals(testCoordinates, unshiftedCoordinates); + + // Test unshiftPointArray - should return array unchanged + double[] testArray = new double[]{8.8, 9.9, 10.1}; + double[] unshiftedArray = defaultShifter.unshiftPointArray(testArray); + assertNotNull(unshiftedArray); + assertArrayEquals(testArray, unshiftedArray, DELTA); } @Test From 2ee2d0402c7bfc5d6763749ebd84eae1fa9fa9b9 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 30 Jan 2026 09:14:35 +0200 Subject: [PATCH 26/31] Read JSON with extra coordinate values --- .../com/mapbox/geojson/LineStringTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java index b38e64dba..31f3b5786 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -172,6 +172,38 @@ public void fromJson() throws IOException { assertEquals(Double.NaN, altitudes[1], 0.0); } + @Test + public void fromJsonWithExtraValuesAreIgnored() throws IOException { + final String json = "{\"type\": \"LineString\"," + + " \"coordinates\": [[ 100, 0, 1000, 2, 3], [101, 1]]} "; + LineString geo = LineString.fromJson(json); + assertEquals("LineString", geo.type()); + List points = geo.coordinates(); + Point firstPoint = points.get(0); + assertEquals(100.0, firstPoint.longitude(), 0.0); + assertEquals(0.0, firstPoint.latitude(), 0.0); + assertTrue(firstPoint.hasAltitude()); + assertEquals(1000.0, firstPoint.altitude(), 0.0); + + Point secondPoint = points.get(1); + assertEquals(101.0, secondPoint.longitude(), 0.0); + assertEquals(1.0, secondPoint.latitude(), 0.0); + assertFalse(secondPoint.hasAltitude()); + + double[] coordinates = geo.flattenCoordinates().getFlattenLngLatArray(); + assertEquals(4, geo.flattenCoordinates().getFlattenLngLatArray().length); + double[] altitudes = geo.flattenCoordinates().getAltitudes(); + assertEquals(100.0, coordinates[0], 0.0); + assertEquals(0.0, coordinates[1], 0.0); + assertNotNull(altitudes); + assertEquals(1000.0, altitudes[0], 0.0); + + // Second point + assertEquals(101.0, coordinates[2], 0.0); + assertEquals(1.0, coordinates[3], 0.0); + assertEquals(Double.NaN, altitudes[1], 0.0); + } + @Test public void toJson() throws IOException { final String json = "{\"type\": \"LineString\"," + From a3c8f4c3a48bb3b93be25a03ee64f1b2b2c98e5d Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 30 Jan 2026 09:32:42 +0200 Subject: [PATCH 27/31] Test initial capacity for the line string JSON logic --- .../com/mapbox/geojson/LineStringTest.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java index 31f3b5786..982f86d97 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -204,6 +204,85 @@ public void fromJsonWithExtraValuesAreIgnored() throws IOException { assertEquals(Double.NaN, altitudes[1], 0.0); } + /** + * Test to trigger reading a JSON that needs to extend the array capacity while parsing. + * {@link FlattenListOfPointsTypeAdapter#INITIAL_CAPACITY}. + * + * @throws IOException + */ + @SuppressWarnings("JavadocReference") + @Test + public void readJsonWithMoreThan_INITIAL_CAPACITY() throws IOException { + final StringBuilder json = new StringBuilder("{\"type\": \"LineString\"," + + " \"coordinates\": ["); + int totalPoints = 100 * 2; + for (int i = 0; i < totalPoints; i++) { + json.append("[ "); + double lng = 100 + i; + double lat = i; + json.append(lng).append(","); + json.append(lat); + // Only add altitude for half of the points + if (i >= 100) { + double alt = 1000 + i; + json.append(",").append(alt); + } + json.append("], "); + } + // Trim the last `, ` + json.deleteCharAt(json.length() - 2); + json.append("]}"); + + LineString geo = LineString.fromJson(json.toString()); + assertEquals("LineString", geo.type()); + List points = geo.coordinates(); + assertEquals(totalPoints, points.size()); + + // Verify the list of points contents + for (int i = 0; i < totalPoints; i++) { + Point point = points.get(i); + double expectedLng = 100 + i; + double expectedLat = i; + assertEquals(expectedLng, point.longitude(), DELTA); + assertEquals(expectedLat, point.latitude(), DELTA); + + if (i >= 100) { + // Second half should have altitude + assertTrue(point.hasAltitude()); + double expectedAlt = 1000 + i; + assertEquals(expectedAlt, point.altitude(), DELTA); + } else { + // First half should not have altitude + assertFalse(point.hasAltitude()); + } + } + + FlattenListOfPoints flattenListOfPoints = geo.flattenCoordinates(); + assertEquals(totalPoints, flattenListOfPoints.size()); + double[] flattenLngLatArray = flattenListOfPoints.getFlattenLngLatArray(); + assertEquals(totalPoints * 2, flattenLngLatArray.length); + double[] altitudes = flattenListOfPoints.getAltitudes(); + assertNotNull(altitudes); + assertEquals(totalPoints, altitudes.length); + + // Verify the contents of flattenLngLatArray and altitudes + for (int i = 0; i < totalPoints; i++) { + double expectedLng = 100 + i; + double expectedLat = i; + assertEquals(expectedLng, flattenLngLatArray[i * 2], DELTA); + assertEquals(expectedLat, flattenLngLatArray[i * 2 + 1], DELTA); + + if (i >= 100) { + // Second half should have altitude + double expectedAlt = 1000 + i; + assertEquals(expectedAlt, altitudes[i], DELTA); + } else { + // First half should have NaN for altitude + assertTrue(Double.isNaN(altitudes[i])); + } + } + } + @Test public void toJson() throws IOException { final String json = "{\"type\": \"LineString\"," + From b978c918d2bf7cdb17a7998ef1c1e9c1b307cde0 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 30 Jan 2026 10:43:53 +0200 Subject: [PATCH 28/31] Changes to docs and minor API alignments --- .../geojson/BaseGeometryTypeAdapter.java | 33 +++++++++---------- .../FlattenListOfPointsTypeAdapter.java | 14 +++++--- .../geojson/FlattenedCoordinateContainer.java | 20 +++++++++++ .../java/com/mapbox/geojson/LineString.java | 18 ++-------- .../java/com/mapbox/geojson/MultiPoint.java | 2 +- .../main/java/com/mapbox/geojson/Point.java | 2 +- .../mapbox/geojson/utils/PolylineUtils.java | 9 ++--- .../com/mapbox/geojson/LineStringTest.java | 16 +++++++++ .../com/mapbox/geojson/MultiPointTest.java | 16 +++++++++ .../geojson/utils/PolylineUtilsTest.java | 7 ++++ 10 files changed, 93 insertions(+), 44 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java index 56d1d726f..9c435869c 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java @@ -18,6 +18,7 @@ * * @param Geometry * @param Type of coordinates + * @param The type of coordinates adapter * @since 4.6.0 */ @Keep @@ -25,17 +26,20 @@ abstract class BaseGeometryTypeAdapter extends TypeAdapter { private volatile TypeAdapter stringAdapter; private volatile TypeAdapter boundingBoxAdapter; - private volatile TypeAdapter coordinatesAdapter; + private final BaseCoordinatesTypeAdapter coordinatesAdapter; private final Gson gson; - BaseGeometryTypeAdapter(Gson gson, TypeAdapter coordinatesAdapter) { + BaseGeometryTypeAdapter(Gson gson, BaseCoordinatesTypeAdapter coordinatesAdapter) { + if (coordinatesAdapter == null) { + throw new GeoJsonException("Coordinates type adapter is null"); + } this.gson = gson; this.coordinatesAdapter = coordinatesAdapter; this.boundingBoxAdapter = new BoundingBoxTypeAdapter(); } - public void writeCoordinateContainerPrimitive( + public void writeFlattenedCoordinateContainer( JsonWriter jsonWriter, FlattenedCoordinateContainer object ) throws IOException { @@ -45,15 +49,7 @@ public void writeCoordinateContainerPrimitive( } writeCommon(jsonWriter, object); jsonWriter.name("coordinates"); - if (object.coordinates() == null) { - jsonWriter.nullValue(); - } else { - TypeAdapter coordinatesAdapter = this.coordinatesAdapter; - if (coordinatesAdapter == null) { - throw new GeoJsonException("Coordinates type adapter is null"); - } - coordinatesAdapter.write(jsonWriter, object.flattenCoordinates()); - } + coordinatesAdapter.write(jsonWriter, object.flattenCoordinates()); jsonWriter.endObject(); } @@ -70,17 +66,20 @@ public void writeCoordinateContainer(JsonWriter jsonWriter, CoordinateContainer< if (object.coordinates() == null) { jsonWriter.nullValue(); } else { - TypeAdapter coordinatesAdapter = this.coordinatesAdapter; - if (coordinatesAdapter == null) { - throw new GeoJsonException("Coordinates type adapter is null"); - } coordinatesAdapter.write(jsonWriter, object.coordinates()); } jsonWriter.endObject(); } - private void writeCommon(JsonWriter jsonWriter, CoordinateContainer object) throws IOException { + /** + * Write the common part of the coordinate container: "type" and "bbox". + */ + private void writeCommon( + JsonWriter jsonWriter, + @SuppressWarnings("rawtypes") + CoordinateContainer object + ) throws IOException { jsonWriter.beginObject(); jsonWriter.name("type"); if (object.type() == null) { diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java index e12333789..4c0c3a700 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FlattenListOfPointsTypeAdapter.java @@ -10,9 +10,8 @@ import java.io.IOException; /** - * Type Adapter to serialize/deserialize List<Point> into/from two dimentional double array. - * - * @since 4.6.0 + * Type Adapter to serialize/deserialize {@link FlattenListOfPoints} into/from two dimensional + * JSON array. */ @Keep class FlattenListOfPointsTypeAdapter extends BaseCoordinatesTypeAdapter { @@ -21,17 +20,22 @@ class FlattenListOfPointsTypeAdapter extends BaseCoordinatesTypeAdapter + * This interface extends {@link CoordinateContainer} to provide access to coordinates in their + * standard form (typically as {@link Point} objects) while also offering a flattened form + * optimized for low-level operations. + * + * @param the standard coordinate container type (e.g., {@code List}) + * @param

the flattened coordinate representation type (e.g., {@link FlattenListOfPoints}) + */ @Keep interface FlattenedCoordinateContainer extends CoordinateContainer { + /** + * Returns the flattened coordinate representation of this geometry. + *

+ * The flattened form stores coordinates in primitive arrays, which provides better performance + * for operations that require direct array access, particularly in JNI contexts. + * + * @return the flattened coordinate representation. + */ P flattenCoordinates(); } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java index f81a4bd49..a033a4f36 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -95,20 +95,6 @@ public static LineString fromLngLats(@NonNull MultiPoint multiPoint) { return new LineString(TYPE, null, multiPoint.coordinates()); } - /** - * Create a new instance of this class by defining a {@link MultiPoint} object and passing. The - * multipoint object should comply with the GeoJson specifications described in the documentation. - * - * @param multiPoint which will make up the LineString geometry - * @param bbox optionally include a bbox definition as a double array - * @return a new instance of this class defined by the values passed inside this static factory - * method - * @since 3.0.0 - */ - public static LineString fromLngLats(@NonNull MultiPoint multiPoint, @Nullable BoundingBox bbox) { - return new LineString(TYPE, bbox, multiPoint.coordinates()); - } - /** * Create a new instance of this class by defining a list of {@link Point}s which follow the * correct specifications described in the Point documentation. Note that there should not be any @@ -287,7 +273,7 @@ public String toJson() { * @since 1.0.0 */ public String toPolyline(int precision) { - return PolylineUtils.encode(flattenListOfPoints, precision); + return PolylineUtils.encode(flattenListOfPoints.getFlattenLngLatArray(), precision); } /** @@ -345,7 +331,7 @@ static final class GsonTypeAdapter extends @Override public void write(JsonWriter jsonWriter, LineString object) throws IOException { - writeCoordinateContainerPrimitive(jsonWriter, object); + writeFlattenedCoordinateContainer(jsonWriter, object); } @Override diff --git a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java index 5bd16bb7c..8eb69a104 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java @@ -232,7 +232,7 @@ static final class GsonTypeAdapter extends @Override public void write(JsonWriter jsonWriter, MultiPoint object) throws IOException { - writeCoordinateContainerPrimitive(jsonWriter, object); + writeFlattenedCoordinateContainer(jsonWriter, object); } @Override diff --git a/services-geojson/src/main/java/com/mapbox/geojson/Point.java b/services-geojson/src/main/java/com/mapbox/geojson/Point.java index 1ee3b9a18..60e463898 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Point.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Point.java @@ -385,7 +385,7 @@ static final class GsonTypeAdapter extends @Override @SuppressWarnings("unchecked") public void write(JsonWriter jsonWriter, Point object) throws IOException { - writeCoordinateContainerPrimitive(jsonWriter, object); + writeFlattenedCoordinateContainer(jsonWriter, object); } @Override diff --git a/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java b/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java index ecdb35c14..04e6c4f19 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/utils/PolylineUtils.java @@ -181,16 +181,18 @@ public static String encode(@NonNull final List path, int precision) { } /** - * Encodes a {@link FlattenListOfPoints} into an encoded path string. + * Encodes an array of doubles representing a line geometry with flattened points into an encoded + * path string. * - * @param flattenListOfPoints a {@link FlattenListOfPoints} making up the line + * @param flattenLngLatArray an array of doubles representing a line geometry with flattened + * points in the form: [lng1, lat1, lng2, lat2, ...] * @param precision OSRMv4 uses 6, OSRMv5 and Google uses 5 * @return a String representing a path string */ @NonNull public static String encode( @NonNull - final FlattenListOfPoints flattenListOfPoints, + double[] flattenLngLatArray, int precision ) { long lastLat = 0; @@ -201,7 +203,6 @@ public static String encode( // OSRM uses precision=6, the default Polyline spec divides by 1E5, capping at precision=5 double factor = Math.pow(10, precision); - double[] flattenLngLatArray = flattenListOfPoints.getFlattenLngLatArray(); for (int i = 0; i < flattenLngLatArray.length / 2; i++) { double longitude = flattenLngLatArray[i * 2]; double latitude = flattenLngLatArray[i * 2 + 1]; diff --git a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java index 982f86d97..fd4960122 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -292,6 +292,22 @@ public void toJson() throws IOException { compareJson(geoJsonString, json); } + @Test + public void toJsonEmpty() throws IOException { + final String json = "{\"type\": \"LineString\", \"coordinates\": [ ]} "; + LineString geo = LineString.fromLngLats(new ArrayList<>()); + String geoJsonString = geo.toJson(); + compareJson(geoJsonString, json); + } + + @Test + public void fromEmptyJson() throws IOException { + final String json = "{\"type\": \"LineString\", \"coordinates\": [ ]} "; + LineString geo = LineString.fromJson(json); + String geoJsonString = geo.toJson(); + compareJson(geoJsonString, json); + } + @Test public void fromJson_coordinatesPresent() throws Exception { thrown.expect(NullPointerException.class); diff --git a/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java b/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java index dee5add5b..f3c95a60b 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java @@ -128,6 +128,22 @@ public void toJson() throws IOException { MultiPoint geo = MultiPoint.fromJson(json); compareJson(json, geo.toJson()); } + @Test + public void toJsonEmpty() throws IOException { + final String json = "{ \"type\": \"MultiPoint\"," + + "\"coordinates\": [ ] } "; + MultiPoint geo = MultiPoint.fromLngLats(new ArrayList<>()); + String geoJson = geo.toJson(); + compareJson(json, geoJson); + } + + @Test + public void fromJsonEmpty() throws IOException { + final String json = "{ \"type\": \"MultiPoint\",\"coordinates\": [ ] } "; + MultiPoint geo = MultiPoint.fromJson(json); + String geoJson = geo.toJson(); + compareJson(json, geoJson); + } @Test public void fromJson_coordinatesPresent() throws Exception { diff --git a/services-geojson/src/test/java/com/mapbox/geojson/utils/PolylineUtilsTest.java b/services-geojson/src/test/java/com/mapbox/geojson/utils/PolylineUtilsTest.java index bd0a026b5..47f8f88fb 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/utils/PolylineUtilsTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/utils/PolylineUtilsTest.java @@ -1,12 +1,14 @@ package com.mapbox.geojson.utils; import static com.mapbox.geojson.utils.PolylineUtils.decode; +import static com.mapbox.geojson.utils.PolylineUtils.decodeToFlattenListOfPoints; import static com.mapbox.geojson.utils.PolylineUtils.encode; import static com.mapbox.geojson.utils.PolylineUtils.simplify; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import com.mapbox.geojson.FlattenListOfPoints; import com.mapbox.geojson.LineString; import com.mapbox.geojson.Point; import com.mapbox.geojson.TestUtils; @@ -114,12 +116,17 @@ public void decode_neverReturnsNullButRatherAnEmptyList() throws Exception { List path = decode("", PRECISION_5); assertNotNull(path); assertEquals(0, path.size()); + double[] pathArray = decodeToFlattenListOfPoints("", PRECISION_5); + assertNotNull(pathArray); + assertEquals(0, pathArray.length); } @Test public void encode_neverReturnsNull() throws Exception { String encodedString = encode(new ArrayList(), PRECISION_6); assertNotNull(encodedString); + String encodedArrayString = encode(new double[]{}, PRECISION_6); + assertNotNull(encodedArrayString); } @Test From 6750ed330e9c9445d8705f2199451b72ffab17ac Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 30 Jan 2026 14:30:38 +0200 Subject: [PATCH 29/31] Added changelog entries --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1d098210..ae8779c27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ Mapbox welcomes participation and contributions from everyone. ### main - Added `DirectionsRefreshResponse#fromJson(Reader)`, a static factory method that deserializes a `DirectionsRefreshResponse` from a `java.io.Reader`. +- Added `FlattenListOfPoints` to hold a list of points in a more memory-efficient way. +- Deprecate `LineString#coordinates()` and `Point#coordinates()`. It's encouraged to use the new `flattenCoordinates()` methods. + ### v7.9.0 - November 20, 2025 From 1007965f7926caeef441e347848b2317102faead Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 30 Jan 2026 15:31:13 +0200 Subject: [PATCH 30/31] Squeezed a few more bytes from Point --- .../main/java/com/mapbox/geojson/Point.java | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/Point.java b/services-geojson/src/main/java/com/mapbox/geojson/Point.java index 60e463898..4b151d0d3 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Point.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Point.java @@ -13,7 +13,6 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -59,8 +58,9 @@ public final class Point implements FlattenedCoordinateContainer, d @Nullable private final BoundingBox bbox; - @NonNull - private final double[] coordinates; + private final double longitude; + private final double latitude; + private final double altitude; /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are @@ -183,7 +183,9 @@ static Point fromLngLat(@NonNull double[] coords) { if (coordinates.length == 0) { throw new NullPointerException("Null coordinates"); } - this.coordinates = coordinates; + longitude = coordinates[0]; + latitude = coordinates[1]; + altitude = coordinates.length > 2 ? coordinates[2] : Double.NaN; } /** @@ -196,7 +198,7 @@ static Point fromLngLat(@NonNull double[] coords) { * @since 3.0.0 */ public double longitude() { - return coordinates[0]; + return longitude; } /** @@ -209,7 +211,7 @@ public double longitude() { * @since 3.0.0 */ public double latitude() { - return coordinates[1]; + return latitude; } /** @@ -222,10 +224,7 @@ public double latitude() { * @since 3.0.0 */ public double altitude() { - if (coordinates.length < 3) { - return Double.NaN; - } - return coordinates[2]; + return altitude; } /** @@ -283,9 +282,15 @@ public BoundingBox bbox() { @Override @Deprecated public List coordinates() { - ArrayList list = new ArrayList<>(coordinates.length); - for (double coordinate : coordinates) { - list.add(coordinate); + int size = 2; + if (!Double.isNaN(altitude())) { + size++; + } + ArrayList list = new ArrayList<>(size); + list.add(longitude); + list.add(latitude); + if (!Double.isNaN(altitude())) { + list.add(altitude); } return list; } @@ -300,7 +305,11 @@ public List coordinates() { */ @Override public double[] flattenCoordinates() { - return coordinates; + if (Double.isNaN(altitude)) { + return new double[]{longitude, latitude}; + } else { + return new double[]{longitude, latitude, altitude}; + } } /** @@ -331,14 +340,14 @@ public static TypeAdapter typeAdapter(Gson gson) { @Override public String toString() { String coordinatesStr; - if (coordinates.length > 2) { + if (Double.isNaN(altitude)) { + coordinatesStr = "[" + longitude + ", " + latitude + "]"; + } else { coordinatesStr = "[" - + this.coordinates[0] + ", " - + this.coordinates[1] + ", " - + this.coordinates[2] + + longitude + ", " + + latitude + ", " + + altitude + "]"; - } else { - coordinatesStr = "[" + this.coordinates[0] + ", " + this.coordinates[1] + "]"; } return "Point{" + "type=" + type + ", " @@ -353,21 +362,21 @@ public boolean equals(Object o) { return false; } Point point = (Point) o; - return Objects.equals(type, point.type) - && Objects.equals(bbox, point.bbox) - && Objects.deepEquals(coordinates, point.coordinates); + return Double.compare(longitude, point.longitude) == 0 + && Double.compare(latitude, point.latitude) == 0 + && Double.compare(altitude, point.altitude) == 0 + && Objects.equals(type, point.type) + && Objects.equals(bbox, point.bbox); } @Override public int hashCode() { - int hashCode = 1; - hashCode *= 1000003; - hashCode ^= type.hashCode(); - hashCode *= 1000003; - hashCode ^= (bbox == null) ? 0 : bbox.hashCode(); - hashCode *= 1000003; - hashCode ^= Arrays.hashCode(coordinates); - return hashCode; + int result = type.hashCode(); + result = 31 * result + Objects.hashCode(bbox); + result = 31 * result + Double.hashCode(longitude); + result = 31 * result + Double.hashCode(latitude); + result = 31 * result + Double.hashCode(altitude); + return result; } /** From 164c42d4a3c1cd3f1bff3e0612cb090b04c75041 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 30 Jan 2026 16:29:07 +0200 Subject: [PATCH 31/31] Revert "Squeezed a few more bytes from Point" This reverts commit 1007965f7926caeef441e347848b2317102faead. --- .../main/java/com/mapbox/geojson/Point.java | 71 ++++++++----------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/services-geojson/src/main/java/com/mapbox/geojson/Point.java b/services-geojson/src/main/java/com/mapbox/geojson/Point.java index 4b151d0d3..60e463898 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Point.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Point.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -58,9 +59,8 @@ public final class Point implements FlattenedCoordinateContainer, d @Nullable private final BoundingBox bbox; - private final double longitude; - private final double latitude; - private final double altitude; + @NonNull + private final double[] coordinates; /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are @@ -183,9 +183,7 @@ static Point fromLngLat(@NonNull double[] coords) { if (coordinates.length == 0) { throw new NullPointerException("Null coordinates"); } - longitude = coordinates[0]; - latitude = coordinates[1]; - altitude = coordinates.length > 2 ? coordinates[2] : Double.NaN; + this.coordinates = coordinates; } /** @@ -198,7 +196,7 @@ static Point fromLngLat(@NonNull double[] coords) { * @since 3.0.0 */ public double longitude() { - return longitude; + return coordinates[0]; } /** @@ -211,7 +209,7 @@ public double longitude() { * @since 3.0.0 */ public double latitude() { - return latitude; + return coordinates[1]; } /** @@ -224,7 +222,10 @@ public double latitude() { * @since 3.0.0 */ public double altitude() { - return altitude; + if (coordinates.length < 3) { + return Double.NaN; + } + return coordinates[2]; } /** @@ -282,15 +283,9 @@ public BoundingBox bbox() { @Override @Deprecated public List coordinates() { - int size = 2; - if (!Double.isNaN(altitude())) { - size++; - } - ArrayList list = new ArrayList<>(size); - list.add(longitude); - list.add(latitude); - if (!Double.isNaN(altitude())) { - list.add(altitude); + ArrayList list = new ArrayList<>(coordinates.length); + for (double coordinate : coordinates) { + list.add(coordinate); } return list; } @@ -305,11 +300,7 @@ public List coordinates() { */ @Override public double[] flattenCoordinates() { - if (Double.isNaN(altitude)) { - return new double[]{longitude, latitude}; - } else { - return new double[]{longitude, latitude, altitude}; - } + return coordinates; } /** @@ -340,14 +331,14 @@ public static TypeAdapter typeAdapter(Gson gson) { @Override public String toString() { String coordinatesStr; - if (Double.isNaN(altitude)) { - coordinatesStr = "[" + longitude + ", " + latitude + "]"; - } else { + if (coordinates.length > 2) { coordinatesStr = "[" - + longitude + ", " - + latitude + ", " - + altitude + + this.coordinates[0] + ", " + + this.coordinates[1] + ", " + + this.coordinates[2] + "]"; + } else { + coordinatesStr = "[" + this.coordinates[0] + ", " + this.coordinates[1] + "]"; } return "Point{" + "type=" + type + ", " @@ -362,21 +353,21 @@ public boolean equals(Object o) { return false; } Point point = (Point) o; - return Double.compare(longitude, point.longitude) == 0 - && Double.compare(latitude, point.latitude) == 0 - && Double.compare(altitude, point.altitude) == 0 - && Objects.equals(type, point.type) - && Objects.equals(bbox, point.bbox); + return Objects.equals(type, point.type) + && Objects.equals(bbox, point.bbox) + && Objects.deepEquals(coordinates, point.coordinates); } @Override public int hashCode() { - int result = type.hashCode(); - result = 31 * result + Objects.hashCode(bbox); - result = 31 * result + Double.hashCode(longitude); - result = 31 * result + Double.hashCode(latitude); - result = 31 * result + Double.hashCode(altitude); - return result; + int hashCode = 1; + hashCode *= 1000003; + hashCode ^= type.hashCode(); + hashCode *= 1000003; + hashCode ^= (bbox == null) ? 0 : bbox.hashCode(); + hashCode *= 1000003; + hashCode ^= Arrays.hashCode(coordinates); + return hashCode; } /**