From 8c69ad489edef28ce50fe75435319c65fb0fe535 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 23 Jan 2026 17:04:42 +0200 Subject: [PATCH 01/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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() + "}"; }