diff --git a/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsJsonObject.java b/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsJsonObject.java index 3d3555cbe..cf5c8a599 100644 --- a/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsJsonObject.java +++ b/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsJsonObject.java @@ -3,7 +3,7 @@ import com.google.gson.GsonBuilder; import com.mapbox.api.directions.v5.DirectionsAdapterFactory; import com.mapbox.geojson.Point; -import com.mapbox.geojson.gson.PointSerializer; +import com.mapbox.geojson.PointAsCoordinatesTypeAdapter; import java.io.Serializable; @@ -24,7 +24,7 @@ public class DirectionsJsonObject implements Serializable { public String toJson() { GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(DirectionsAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointSerializer()); + gson.registerTypeAdapter(Point.class, new PointAsCoordinatesTypeAdapter()); return gson.create().toJson(this); } } diff --git a/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsResponse.java b/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsResponse.java index f7b9f18ce..522a189b3 100644 --- a/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsResponse.java +++ b/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsResponse.java @@ -9,7 +9,7 @@ import com.google.gson.TypeAdapter; import com.mapbox.api.directions.v5.DirectionsAdapterFactory; import com.mapbox.geojson.Point; -import com.mapbox.geojson.gson.PointDeserializer; +import com.mapbox.geojson.PointAsCoordinatesTypeAdapter; import java.util.List; @@ -129,7 +129,7 @@ public static TypeAdapter typeAdapter(Gson gson) { public static DirectionsResponse fromJson(String json) { GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(DirectionsAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); + gson.registerTypeAdapter(Point.class, new PointAsCoordinatesTypeAdapter()); return gson.create().fromJson(json, DirectionsResponse.class); } diff --git a/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsRoute.java b/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsRoute.java index 7ed1886bb..882ab4dfe 100644 --- a/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsRoute.java +++ b/services-directions/src/main/java/com/mapbox/api/directions/v5/models/DirectionsRoute.java @@ -10,7 +10,7 @@ import com.mapbox.api.directions.v5.DirectionsAdapterFactory; import com.mapbox.api.directions.v5.MapboxDirections; import com.mapbox.geojson.Point; -import com.mapbox.geojson.gson.PointDeserializer; +import com.mapbox.geojson.PointAsCoordinatesTypeAdapter; import java.util.List; @@ -155,7 +155,7 @@ public static TypeAdapter typeAdapter(Gson gson) { public static DirectionsRoute fromJson(String json) { GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(DirectionsAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); + gson.registerTypeAdapter(Point.class, new PointAsCoordinatesTypeAdapter()); return gson.create().fromJson(json, DirectionsRoute.class); } diff --git a/services-directions/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java b/services-directions/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java index e8c0a32b5..5cc9360b5 100644 --- a/services-directions/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java +++ b/services-directions/src/main/java/com/mapbox/api/directions/v5/models/RouteOptions.java @@ -12,7 +12,7 @@ import com.mapbox.api.directions.v5.DirectionsCriteria; import com.mapbox.api.directions.v5.MapboxDirections; import com.mapbox.geojson.Point; -import com.mapbox.geojson.gson.PointDeserializer; +import com.mapbox.geojson.PointAsCoordinatesTypeAdapter; import java.util.List; @@ -334,7 +334,7 @@ public static TypeAdapter typeAdapter(Gson gson) { public static RouteOptions fromJson(String json) { GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(DirectionsAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); + gson.registerTypeAdapter(Point.class, new PointAsCoordinatesTypeAdapter()); return gson.create().fromJson(json, RouteOptions.class); } diff --git a/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/MapboxGeocoding.java b/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/MapboxGeocoding.java index 5671bca02..8fa06f1ef 100644 --- a/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/MapboxGeocoding.java +++ b/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/MapboxGeocoding.java @@ -17,11 +17,9 @@ import com.mapbox.core.utils.MapboxUtils; import com.mapbox.core.utils.TextUtils; import com.mapbox.geojson.BoundingBox; -import com.mapbox.geojson.Geometry; +import com.mapbox.geojson.GeometryAdapterFactory; import com.mapbox.geojson.Point; -import com.mapbox.geojson.gson.BoundingBoxDeserializer; -import com.mapbox.geojson.gson.GeometryDeserializer; -import com.mapbox.geojson.gson.PointDeserializer; +import com.mapbox.geojson.gson.BoundingBoxTypeAdapter; import java.io.IOException; import java.util.ArrayList; @@ -71,11 +69,11 @@ protected MapboxGeocoding() { @Override protected GsonBuilder getGsonBuilder() { + return new GsonBuilder() .registerTypeAdapterFactory(GeocodingAdapterFactory.create()) - .registerTypeAdapter(Point.class, new PointDeserializer()) - .registerTypeAdapter(Geometry.class, new GeometryDeserializer()) - .registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()); + .registerTypeAdapterFactory(GeometryAdapterFactory.create()) + .registerTypeAdapter(BoundingBox.class, new BoundingBoxTypeAdapter()); } @Override diff --git a/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/models/CarmenFeature.java b/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/models/CarmenFeature.java index a2bf0da00..d9d453380 100644 --- a/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/models/CarmenFeature.java +++ b/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/models/CarmenFeature.java @@ -13,12 +13,9 @@ import com.mapbox.geojson.Feature; import com.mapbox.geojson.GeoJson; import com.mapbox.geojson.Geometry; +import com.mapbox.geojson.GeometryAdapterFactory; import com.mapbox.geojson.Point; -import com.mapbox.geojson.gson.BoundingBoxDeserializer; -import com.mapbox.geojson.gson.BoundingBoxSerializer; -import com.mapbox.geojson.gson.GeometryDeserializer; -import com.mapbox.geojson.gson.GeometryTypeAdapter; -import com.mapbox.geojson.gson.PointDeserializer; +import com.mapbox.geojson.gson.BoundingBoxTypeAdapter; import java.io.Serializable; import java.util.List; @@ -55,10 +52,10 @@ public abstract class CarmenFeature implements GeoJson, Serializable { */ @NonNull public static CarmenFeature fromJson(@NonNull String json) { + Gson gson = new GsonBuilder() - .registerTypeAdapter(Point.class, new PointDeserializer()) - .registerTypeAdapter(Geometry.class, new GeometryDeserializer()) - .registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()) + .registerTypeAdapterFactory(GeometryAdapterFactory.create()) + .registerTypeAdapter(BoundingBox.class, new BoundingBoxTypeAdapter()) .registerTypeAdapterFactory(GeocodingAdapterFactory.create()) .create(); CarmenFeature feature = gson.fromJson(json, CarmenFeature.class); @@ -296,9 +293,10 @@ public static TypeAdapter typeAdapter(Gson gson) { @Override @SuppressWarnings("unused") public String toJson() { + Gson gson = new GsonBuilder() - .registerTypeAdapter(Geometry.class, new GeometryTypeAdapter()) - .registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer()) + .registerTypeAdapterFactory(GeometryAdapterFactory.create()) + .registerTypeAdapter(BoundingBox.class, new BoundingBoxTypeAdapter()) .registerTypeAdapterFactory(GeocodingAdapterFactory.create()) .create(); diff --git a/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/models/GeocodingResponse.java b/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/models/GeocodingResponse.java index c269d0541..f0008418e 100644 --- a/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/models/GeocodingResponse.java +++ b/services-geocoding/src/main/java/com/mapbox/api/geocoding/v5/models/GeocodingResponse.java @@ -7,13 +7,8 @@ import com.google.gson.TypeAdapter; import com.mapbox.geojson.BoundingBox; import com.mapbox.geojson.FeatureCollection; -import com.mapbox.geojson.Geometry; -import com.mapbox.geojson.Point; -import com.mapbox.geojson.gson.BoundingBoxDeserializer; -import com.mapbox.geojson.gson.BoundingBoxSerializer; -import com.mapbox.geojson.gson.GeometryDeserializer; -import com.mapbox.geojson.gson.GeometryTypeAdapter; -import com.mapbox.geojson.gson.PointDeserializer; +import com.mapbox.geojson.GeometryAdapterFactory; +import com.mapbox.geojson.gson.BoundingBoxTypeAdapter; import java.io.Serializable; import java.util.List; @@ -41,9 +36,8 @@ public abstract class GeocodingResponse implements Serializable { @NonNull public static GeocodingResponse fromJson(@NonNull String json) { Gson gson = new GsonBuilder() - .registerTypeAdapter(Point.class, new PointDeserializer()) - .registerTypeAdapter(Geometry.class, new GeometryDeserializer()) - .registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()) + .registerTypeAdapterFactory(GeometryAdapterFactory.create()) + .registerTypeAdapter(BoundingBox.class, new BoundingBoxTypeAdapter()) .registerTypeAdapterFactory(GeocodingAdapterFactory.create()) .create(); return gson.fromJson(json, GeocodingResponse.class); @@ -123,8 +117,8 @@ public static Builder builder() { @NonNull public String toJson() { Gson gson = new GsonBuilder() - .registerTypeAdapter(Geometry.class, new GeometryTypeAdapter()) - .registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer()) + .registerTypeAdapterFactory(GeometryAdapterFactory.create()) + .registerTypeAdapter(BoundingBox.class, new BoundingBoxTypeAdapter()) .registerTypeAdapterFactory(GeocodingAdapterFactory.create()) .create(); return gson.toJson(this, GeocodingResponse.class); diff --git a/services-geocoding/src/test/java/com/mapbox/api/geocoding/v5/models/CarmenFeatureTest.java b/services-geocoding/src/test/java/com/mapbox/api/geocoding/v5/models/CarmenFeatureTest.java index fb8964970..2e6da2237 100644 --- a/services-geocoding/src/test/java/com/mapbox/api/geocoding/v5/models/CarmenFeatureTest.java +++ b/services-geocoding/src/test/java/com/mapbox/api/geocoding/v5/models/CarmenFeatureTest.java @@ -172,7 +172,7 @@ public void testNullProperties() { @Test public void testNullPropertiesJson() { - String jsonString = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-77.0, 38.0]}}"; + String jsonString = "{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[-77.0,38.0]}}"; CarmenFeature feature = CarmenFeature.fromJson(jsonString); // Json( null Properties) -> Feature (empty Properties) -> Json(null Properties) diff --git a/services-geojson/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java b/services-geojson/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java new file mode 100644 index 000000000..2c93f1e3c --- /dev/null +++ b/services-geojson/src/main/java/com/google/gson/typeadapters/RuntimeTypeAdapterFactory.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.typeadapters; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.Streams; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * Adapts values whose runtime type may differ from their declaration type. This + * is necessary when a field's type is not the same type that GSON should create + * when deserializing that field. For example, consider these types: + *
   {@code
+ *   abstract class Shape {
+ *     int x;
+ *     int y;
+ *   }
+ *   class Circle extends Shape {
+ *     int radius;
+ *   }
+ *   class Rectangle extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Diamond extends Shape {
+ *     int width;
+ *     int height;
+ *   }
+ *   class Drawing {
+ *     Shape bottomShape;
+ *     Shape topShape;
+ *   }
+ * }
+ *

Without additional type information, the serialized JSON is ambiguous. Is + * the bottom shape in this drawing a rectangle or a diamond?

   {@code
+ *   {
+ *     "bottomShape": {
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * This class addresses this problem by adding type information to the + * serialized JSON and honoring that type information when the JSON is + * deserialized:
   {@code
+ *   {
+ *     "bottomShape": {
+ *       "type": "Diamond",
+ *       "width": 10,
+ *       "height": 5,
+ *       "x": 0,
+ *       "y": 0
+ *     },
+ *     "topShape": {
+ *       "type": "Circle",
+ *       "radius": 2,
+ *       "x": 4,
+ *       "y": 1
+ *     }
+ *   }}
+ * Both the type field name ({@code "type"}) and the type labels ({@code + * "Rectangle"}) are configurable. + * + *

Registering Types

+ * Create a {@code RuntimeTypeAdapterFactory} by passing the base type and type field + * name to the {@link #of} factory method. If you don't supply an explicit type + * field name, {@code "type"} will be used.
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory
+ *       = RuntimeTypeAdapterFactory.of(Shape.class, "type");
+ * }
+ * Next register all of your subtypes. Every subtype must be explicitly + * registered. This protects your application from injection attacks. If you + * don't supply an explicit type label, the type's simple name will be used. + *
   {@code
+ *   shapeAdapterFactory.registerSubtype(Rectangle.class, "Rectangle");
+ *   shapeAdapterFactory.registerSubtype(Circle.class, "Circle");
+ *   shapeAdapterFactory.registerSubtype(Diamond.class, "Diamond");
+ * }
+ * Finally, register the type adapter factory in your application's GSON builder: + *
   {@code
+ *   Gson gson = new GsonBuilder()
+ *       .registerTypeAdapterFactory(shapeAdapterFactory)
+ *       .create();
+ * }
+ * Like {@code GsonBuilder}, this API supports chaining:
   {@code
+ *   RuntimeTypeAdapterFactory shapeAdapterFactory =
+ *     RuntimeTypeAdapterFactory.of(Shape.class)
+ *       .registerSubtype(Rectangle.class)
+ *       .registerSubtype(Circle.class)
+ *       .registerSubtype(Diamond.class);
+ * }
+ * + * @param base type for this factory + * @since 4.6.0 + */ +public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { + private final Class baseType; + private final String typeFieldName; + private final Map> labelToSubtype = new LinkedHashMap>(); + private final Map, String> subtypeToLabel = new LinkedHashMap, String>(); + private final boolean maintainType; + + private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName, boolean maintainType) { + if (typeFieldName == null || baseType == null) { + throw new NullPointerException(); + } + this.baseType = baseType; + this.typeFieldName = typeFieldName; + this.maintainType = maintainType; + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code + * typeFieldName} as the type field name. Type field names are case sensitive. + * {@code maintainType} flag decide if the type will be stored in pojo or not. + * + * @param base type + * @param baseType class of base type + * @param typeFieldName field name used to distinguish subtypes + * @param maintainType true if the type will be stored in pojo, false - otherwise + */ + public static RuntimeTypeAdapterFactory of(Class baseType, + String typeFieldName, + boolean maintainType) { + return new RuntimeTypeAdapterFactory(baseType, typeFieldName, maintainType); + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code + * typeFieldName} as the type field name. Type field names are case sensitive. + * + * @param base type + * @param baseType class of base type + * @param typeFieldName field name used to distinguish subtypes + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { + return new RuntimeTypeAdapterFactory(baseType, typeFieldName, false); + } + + /** + * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as + * the type field name. + * + * @param base type + * @param baseType class of base type + */ + public static RuntimeTypeAdapterFactory of(Class baseType) { + return new RuntimeTypeAdapterFactory(baseType, "type", false); + } + + /** + * Registers {@code type} identified by {@code label}. Labels are case + * sensitive. + * + * @param type class of subtype of base type + * @param label string value for field that distinguishes subtypes + * @throws IllegalArgumentException if either {@code type} or {@code label} + * have already been registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { + if (type == null || label == null) { + throw new NullPointerException(); + } + if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { + throw new IllegalArgumentException("types and labels must be unique"); + } + labelToSubtype.put(label, type); + subtypeToLabel.put(type, label); + return this; + } + + /** + * Registers {@code type} identified by its {@link Class#getSimpleName simple + * name}. Labels are case sensitive. + * + * @param type type name + * @throws IllegalArgumentException if either {@code type} or its simple name + * have already been registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type) { + return registerSubtype(type, type.getSimpleName()); + } + + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() != baseType) { + return null; + } + + final Map> labelToDelegate + = new LinkedHashMap>(); + final Map, TypeAdapter> subtypeToDelegate + = new LinkedHashMap, TypeAdapter>(); + for (Map.Entry> entry : labelToSubtype.entrySet()) { + TypeAdapter delegate = + gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); + labelToDelegate.put(entry.getKey(), delegate); + subtypeToDelegate.put(entry.getValue(), delegate); + } + + return new TypeAdapter() { + @Override public R read(JsonReader in) throws IOException { + JsonElement jsonElement = Streams.parse(in); + JsonElement labelJsonElement; + if (maintainType) { + labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); + } else { + labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); + } + + if (labelJsonElement == null) { + throw new JsonParseException("cannot deserialize " + baseType + + " because it does not define a field named " + typeFieldName); + } + String label = labelJsonElement.getAsString(); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); + if (delegate == null) { + throw new JsonParseException("cannot deserialize " + baseType + " subtype named " + + label + "; did you forget to register a subtype?"); + } + return delegate.fromJsonTree(jsonElement); + } + + @Override public void write(JsonWriter out, R value) throws IOException { + Class srcType = value.getClass(); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); + if (delegate == null) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + "; did you forget to register a subtype?"); + } + JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); + + if (maintainType) { + Streams.write(jsonObject, out); + return; + } + + JsonObject clone = new JsonObject(); + + if (jsonObject.has(typeFieldName)) { + throw new JsonParseException("cannot serialize " + srcType.getName() + + " because it already defines a field named " + typeFieldName); + } + String label = subtypeToLabel.get(srcType); + clone.add(typeFieldName, new JsonPrimitive(label)); + + for (Map.Entry e : jsonObject.entrySet()) { + clone.add(e.getKey(), e.getValue()); + } + Streams.write(clone, out); + } + }.nullSafe(); + } +} + diff --git a/services-geojson/src/main/java/com/google/gson/typeadapters/package-info.java b/services-geojson/src/main/java/com/google/gson/typeadapters/package-info.java new file mode 100644 index 000000000..3117c6cd8 --- /dev/null +++ b/services-geojson/src/main/java/com/google/gson/typeadapters/package-info.java @@ -0,0 +1,5 @@ +/** + * Contains Google gson type adapters. + */ + +package com.google.gson.typeadapters; diff --git a/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java new file mode 100644 index 000000000..6da74207a --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/BaseCoordinatesTypeAdapter.java @@ -0,0 +1,83 @@ +package com.mapbox.geojson; + +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 + * JSON to instance of {@code T}. + * + * @param Type of coordinates + * @since 4.6.0 + */ +abstract class BaseCoordinatesTypeAdapter extends TypeAdapter { + + + protected void writePoint(JsonWriter out, Point value) throws IOException { + writePointList(out, value.coordinates()); + } + + 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"); + } + + + protected void writePointList(JsonWriter out, List value) throws IOException { + + if (value == null) { + return; + } + + out.beginArray(); + + // Unshift coordinates + List unshiftedCoordinates = + CoordinateShifterManager.getCoordinateShifter().unshiftPoint(value); + + out.value(GeoJsonUtils.trim(unshiftedCoordinates.get(0))); + out.value(GeoJsonUtils.trim(unshiftedCoordinates.get(1))); + + // Includes altitude + if (value.size() > 2) { + out.value(unshiftedCoordinates.get(2)); + } + out.endArray(); + } + + protected List readPointList(JsonReader in) throws IOException { + + if (in.peek() == JsonToken.NULL) { + throw new NullPointerException(); + } + + List coordinates = new ArrayList(); + in.beginArray(); + while (in.hasNext()) { + coordinates.add(in.nextDouble()); + } + in.endArray(); + + if (coordinates.size() > 2) { + return CoordinateShifterManager.getCoordinateShifter() + .shiftLonLatAlt(coordinates.get(0), coordinates.get(1), coordinates.get(2)); + } + 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 new file mode 100644 index 000000000..b1c7b3c4d --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/BaseGeometryTypeAdapter.java @@ -0,0 +1,134 @@ +package com.mapbox.geojson; + +import com.google.gson.Gson; +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.gson.BoundingBoxTypeAdapter; + +import java.io.IOException; + +/** + * Base class for converting {@code Geometry} instances to JSON and + * JSON to instances of {@code Geometry}. + * + * @param Geometry + * @param Type of coordinates + * @since 4.6.0 + */ +abstract class BaseGeometryTypeAdapter extends TypeAdapter { + + private volatile TypeAdapter stringAdapter; + private volatile TypeAdapter boundingBoxAdapter; + private volatile TypeAdapter coordinatesAdapter; + + private final Gson gson; + + BaseGeometryTypeAdapter(Gson gson, TypeAdapter coordinatesAdapter) { + this.gson = gson; + this.coordinatesAdapter = coordinatesAdapter; + this.boundingBoxAdapter = new BoundingBoxTypeAdapter(); + } + + public void writeCoordinateContainer(JsonWriter jsonWriter, CoordinateContainer object) + throws IOException { + if (object == null) { + jsonWriter.nullValue(); + return; + } + jsonWriter.beginObject(); + jsonWriter.name("type"); + if (object.type() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter stringAdapter = this.stringAdapter; + if (stringAdapter == null) { + stringAdapter = gson.getAdapter(String.class); + this.stringAdapter = stringAdapter; + } + stringAdapter.write(jsonWriter, object.type()); + } + jsonWriter.name("bbox"); + if (object.bbox() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter boundingBoxAdapter = this.boundingBoxAdapter; + if (boundingBoxAdapter == null) { + boundingBoxAdapter = gson.getAdapter(BoundingBox.class); + this.boundingBoxAdapter = boundingBoxAdapter; + } + 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 { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); + return null; + } + + jsonReader.beginObject(); + String type = null; + BoundingBox bbox = null; + T coordinates = null; + + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); + continue; + } + switch (name) { + case "type": + TypeAdapter stringAdapter = this.stringAdapter; + if (stringAdapter == null) { + stringAdapter = gson.getAdapter(String.class); + this.stringAdapter = stringAdapter; + } + type = stringAdapter.read(jsonReader); + break; + + case "bbox": + TypeAdapter boundingBoxAdapter = this.boundingBoxAdapter; + if (boundingBoxAdapter == null) { + boundingBoxAdapter = gson.getAdapter(BoundingBox.class); + this.boundingBoxAdapter = boundingBoxAdapter; + } + bbox = boundingBoxAdapter.read(jsonReader); + break; + + case "coordinates": + TypeAdapter coordinatesAdapter = this.coordinatesAdapter; + if (coordinatesAdapter == null) { + throw new GeoJsonException("Coordinates type adapter is null"); + } + coordinates = coordinatesAdapter.read(jsonReader); + break; + + default: + jsonReader.skipValue(); + + } + } + jsonReader.endObject(); + + return createCoordinateContainer(type, bbox, coordinates); + } + + abstract CoordinateContainer createCoordinateContainer(String type, + BoundingBox bbox, + T coordinates); +} diff --git a/services-geojson/src/main/java/com/mapbox/geojson/BoundingBox.java b/services-geojson/src/main/java/com/mapbox/geojson/BoundingBox.java index 9dd5c5246..45c6a7f97 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/BoundingBox.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/BoundingBox.java @@ -5,12 +5,11 @@ import android.support.annotation.FloatRange; import android.support.annotation.NonNull; -import com.google.auto.value.AutoValue; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; import com.mapbox.geojson.constants.GeoJsonConstants; -import com.mapbox.geojson.gson.GeoJsonAdapterFactory; +import com.mapbox.geojson.gson.BoundingBoxTypeAdapter; import java.io.Serializable; @@ -29,8 +28,11 @@ * * @since 3.0.0 */ -@AutoValue -public abstract class BoundingBox implements Serializable { +public class BoundingBox implements Serializable { + + private final Point southwest; + + private final Point northeast; /** * Create a new instance of this class by passing in a formatted valid JSON String. @@ -42,7 +44,7 @@ public abstract class BoundingBox implements Serializable { */ public static BoundingBox fromJson(String json) { Gson gson = new GsonBuilder() - .registerTypeAdapterFactory(GeoJsonAdapterFactory.create()) + .registerTypeAdapter(BoundingBox.class, new BoundingBoxTypeAdapter()) .create(); return gson.fromJson(json, BoundingBox.class); } @@ -59,7 +61,7 @@ public static BoundingBox fromJson(String json) { * @since 3.0.0 */ public static BoundingBox fromPoints(@NonNull Point southwest, @NonNull Point northeast) { - return new AutoValue_BoundingBox(southwest, northeast); + return new BoundingBox(southwest, northeast); } /** @@ -130,7 +132,7 @@ public static BoundingBox fromLngLats( @FloatRange(from = MIN_LATITUDE, to = GeoJsonConstants.MAX_LATITUDE) double south, @FloatRange(from = MIN_LONGITUDE, to = GeoJsonConstants.MAX_LONGITUDE) double east, @FloatRange(from = MIN_LATITUDE, to = GeoJsonConstants.MAX_LATITUDE) double north) { - return new AutoValue_BoundingBox(Point.fromLngLat(west, south), Point.fromLngLat(east, north)); + return new BoundingBox(Point.fromLngLat(west, south), Point.fromLngLat(east, north)); } /** @@ -156,11 +158,22 @@ public static BoundingBox fromLngLats( @FloatRange(from = MIN_LONGITUDE, to = GeoJsonConstants.MAX_LONGITUDE) double east, @FloatRange(from = MIN_LATITUDE, to = GeoJsonConstants.MAX_LATITUDE) double north, double northEastAltitude) { - return new AutoValue_BoundingBox( + return new BoundingBox( Point.fromLngLat(west, south, southwestAltitude), Point.fromLngLat(east, north, northEastAltitude)); } + BoundingBox(Point southwest, Point northeast) { + if (southwest == null) { + throw new NullPointerException("Null southwest"); + } + this.southwest = southwest; + if (northeast == null) { + throw new NullPointerException("Null northeast"); + } + this.northeast = northeast; + } + /** * Provides the {@link Point} which represents the southwest corner of this bounding box when the * map is facing due north. @@ -169,7 +182,9 @@ public static BoundingBox fromLngLats( * @since 3.0.0 */ @NonNull - public abstract Point southwest(); + public Point southwest() { + return southwest; + } /** * Provides the {@link Point} which represents the northeast corner of this bounding box when the @@ -179,7 +194,9 @@ public static BoundingBox fromLngLats( * @since 3.0.0 */ @NonNull - public abstract Point northeast(); + public Point northeast() { + return northeast; + } /** * Convenience method for getting the bounding box most westerly point (longitude) as a double @@ -233,7 +250,7 @@ public final double north() { * @since 3.0.0 */ public static TypeAdapter typeAdapter(Gson gson) { - return new AutoValue_BoundingBox.GsonTypeAdapter(gson); + return new BoundingBoxTypeAdapter(); } /** @@ -245,9 +262,39 @@ public static TypeAdapter typeAdapter(Gson gson) { */ public final String toJson() { Gson gson = new GsonBuilder() - .setPrettyPrinting() - .registerTypeAdapterFactory(GeoJsonAdapterFactory.create()) + .registerTypeAdapter(BoundingBox.class, new BoundingBoxTypeAdapter()) .create(); return gson.toJson(this, BoundingBox.class); } + + @Override + public String toString() { + return "BoundingBox{" + + "southwest=" + southwest + ", " + + "northeast=" + northeast + + "}"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof BoundingBox) { + BoundingBox that = (BoundingBox) obj; + return (this.southwest.equals(that.southwest())) + && (this.northeast.equals(that.northeast())); + } + return false; + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode *= 1000003; + hashCode ^= southwest.hashCode(); + hashCode *= 1000003; + hashCode ^= northeast.hashCode(); + return hashCode; + } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/CoordinateContainer.java b/services-geojson/src/main/java/com/mapbox/geojson/CoordinateContainer.java index 4f040a193..43d80180a 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/CoordinateContainer.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/CoordinateContainer.java @@ -18,4 +18,5 @@ public interface CoordinateContainer extends Geometry { * @since 3.0.0 */ T coordinates(); + } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/Feature.java b/services-geojson/src/main/java/com/mapbox/geojson/Feature.java index fa49527ef..45a2c58a1 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Feature.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Feature.java @@ -2,18 +2,19 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.google.auto.value.AutoValue; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.TypeAdapter; -import com.mapbox.geojson.gson.BoundingBoxDeserializer; -import com.mapbox.geojson.gson.BoundingBoxSerializer; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.mapbox.geojson.gson.BoundingBoxTypeAdapter; import com.mapbox.geojson.gson.GeoJsonAdapterFactory; -import com.mapbox.geojson.gson.GeometryDeserializer; -import com.mapbox.geojson.gson.PointDeserializer; -import com.mapbox.geojson.gson.PointSerializer; + +import java.io.IOException; /** * This defines a GeoJson Feature object which represents a spatially bound thing. Every Feature @@ -45,11 +46,21 @@ * * @since 1.0.0 */ -@AutoValue -public abstract class Feature implements GeoJson { +public final class Feature implements GeoJson { private static final String TYPE = "Feature"; + private final String type; + + @JsonAdapter(BoundingBoxTypeAdapter.class) + private final BoundingBox bbox; + + private final String id; + + private final Geometry geometry; + + private final JsonObject properties; + /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are * creating a Feature object from scratch it is better to use one of the other provided static @@ -61,11 +72,11 @@ public abstract class Feature implements GeoJson { * @since 1.0.0 */ public static Feature fromJson(@NonNull String json) { + GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()); - gson.registerTypeAdapter(Geometry.class, new GeometryDeserializer()); + gson.registerTypeAdapterFactory(GeometryAdapterFactory.create()); + Feature feature = gson.create().fromJson(json, Feature.class); // Even thought properties are Nullable, @@ -74,7 +85,7 @@ public static Feature fromJson(@NonNull String json) { if (feature.properties() != null) { return feature; } - return new AutoValue_Feature(TYPE, feature.bbox(), + return new Feature(TYPE, feature.bbox(), feature.id(), feature.geometry(), new JsonObject()); } @@ -87,7 +98,7 @@ public static Feature fromJson(@NonNull String json) { * @since 1.0.0 */ public static Feature fromGeometry(@Nullable Geometry geometry) { - return new AutoValue_Feature(TYPE, null, null, geometry, new JsonObject()); + return new Feature(TYPE, null, null, geometry, new JsonObject()); } /** @@ -101,7 +112,7 @@ public static Feature fromGeometry(@Nullable Geometry geometry) { * @since 1.0.0 */ public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable BoundingBox bbox) { - return new AutoValue_Feature(TYPE, bbox, null, geometry, new JsonObject()); + return new Feature(TYPE, bbox, null, geometry, new JsonObject()); } /** @@ -115,7 +126,7 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable Boundi * @since 1.0.0 */ public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable JsonObject properties) { - return new AutoValue_Feature(TYPE, null, null, geometry, + return new Feature(TYPE, null, null, geometry, properties == null ? new JsonObject() : properties); } @@ -132,7 +143,7 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable JsonOb */ public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable JsonObject properties, @Nullable BoundingBox bbox) { - return new AutoValue_Feature(TYPE, bbox, null, geometry, + return new Feature(TYPE, bbox, null, geometry, properties == null ? new JsonObject() : properties); } @@ -148,7 +159,7 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable JsonOb */ public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable JsonObject properties, @Nullable String id) { - return new AutoValue_Feature(TYPE, null, id, geometry, + return new Feature(TYPE, null, id, geometry, properties == null ? new JsonObject() : properties); } @@ -165,10 +176,22 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @Nullable JsonOb */ public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObject properties, @Nullable String id, @Nullable BoundingBox bbox) { - return new AutoValue_Feature(TYPE, bbox, id, geometry, + return new Feature(TYPE, bbox, id, geometry, properties == null ? new JsonObject() : properties); } + Feature(String type, @Nullable BoundingBox bbox, @Nullable String id, + @Nullable Geometry geometry, @Nullable JsonObject properties) { + if (type == null) { + throw new NullPointerException("Null type"); + } + this.type = type; + this.bbox = bbox; + this.id = id; + this.geometry = geometry; + this.properties = properties; + } + /** * This describes the TYPE of GeoJson geometry this object is, thus this will always return * {@link Feature}. @@ -179,7 +202,9 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObj */ @NonNull @Override - public abstract String type(); + public String type() { + return type; + } /** * A Feature Collection might have a member named {@code bbox} to include information on the @@ -193,7 +218,9 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObj */ @Nullable @Override - public abstract BoundingBox bbox(); + public BoundingBox bbox() { + return bbox; + } /** * A feature may have a commonly used identifier which is either a unique String or number. @@ -203,7 +230,9 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObj * @since 1.0.0 */ @Nullable - public abstract String id(); + public String id() { + return id; + } /** * The geometry which makes up this feature. A Geometry object represents points, curves, and @@ -214,7 +243,9 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObj * @since 1.0.0 */ @Nullable - public abstract Geometry geometry(); + public Geometry geometry() { + return geometry; + } /** * This contains the JSON object which holds the feature properties. The value of the properties @@ -224,7 +255,9 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObj * @since 1.0.0 */ @Nullable - public abstract JsonObject properties(); + public JsonObject properties() { + return properties; + } /** * This takes the currently defined values found inside this instance and converts it to a GeoJson @@ -235,17 +268,20 @@ public static Feature fromGeometry(@Nullable Geometry geometry, @NonNull JsonObj */ @Override public String toJson() { - GsonBuilder gson = new GsonBuilder(); - gson.registerTypeAdapter(Point.class, new PointSerializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer()); + + Gson gson = new GsonBuilder() + .registerTypeAdapterFactory(GeoJsonAdapterFactory.create()) + .registerTypeAdapterFactory(GeometryAdapterFactory.create()) + .create(); + // Empty properties -> should not appear in json string Feature feature = this; if (properties().size() == 0) { - feature = new AutoValue_Feature(TYPE, bbox(), id(), geometry(), null); + feature = new Feature(TYPE, bbox(), id(), geometry(), null); } - return gson.create().toJson(feature); + return gson.toJson(feature); } /** @@ -256,7 +292,7 @@ public String toJson() { * @since 3.0.0 */ public static TypeAdapter typeAdapter(Gson gson) { - return new AutoValue_Feature.GsonTypeAdapter(gson); + return new Feature.GsonTypeAdapter(gson); } /** @@ -401,4 +437,204 @@ public boolean hasProperty(String key) { public boolean hasNonNullValueForProperty(String key) { return hasProperty(key) && !getProperty(key).isJsonNull(); } + + @Override + public String toString() { + return "Feature{" + + "type=" + type + ", " + + "bbox=" + bbox + ", " + + "id=" + id + ", " + + "geometry=" + geometry + ", " + + "properties=" + properties + + "}"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Feature) { + Feature that = (Feature) obj; + return (this.type.equals(that.type())) + && ((this.bbox == null) ? (that.bbox() == null) : this.bbox.equals(that.bbox())) + && ((this.id == null) ? (that.id() == null) : this.id.equals(that.id())) + && ((this.geometry == null) + ? (that.geometry() == null) : this.geometry.equals(that.geometry())) + && ((this.properties == null) + ? (that.properties() == null) : this.properties.equals(that.properties())); + } + return false; + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode *= 1000003; + hashCode ^= type.hashCode(); + hashCode *= 1000003; + hashCode ^= (bbox == null) ? 0 : bbox.hashCode(); + hashCode *= 1000003; + hashCode ^= (id == null) ? 0 : id.hashCode(); + hashCode *= 1000003; + hashCode ^= (geometry == null) ? 0 : geometry.hashCode(); + hashCode *= 1000003; + hashCode ^= (properties == null) ? 0 : properties.hashCode(); + return hashCode; + } + + /** + * TypeAdapter to serialize/deserialize Feature objects. + * + * @since 4.6.0 + */ + static final class GsonTypeAdapter extends TypeAdapter { + private volatile TypeAdapter stringTypeAdapter; + private volatile TypeAdapter boundingBoxTypeAdapter; + private volatile TypeAdapter geometryTypeAdapter; + private volatile TypeAdapter jsonObjectTypeAdapter; + private final Gson gson; + + GsonTypeAdapter(Gson gson) { + this.gson = gson; + } + + @Override + public void write(JsonWriter jsonWriter, Feature object) throws IOException { + if (object == null) { + jsonWriter.nullValue(); + return; + } + jsonWriter.beginObject(); + jsonWriter.name("type"); + if (object.type() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter stringTypeAdapter = this.stringTypeAdapter; + if (stringTypeAdapter == null) { + stringTypeAdapter = gson.getAdapter(String.class); + this.stringTypeAdapter = stringTypeAdapter; + } + stringTypeAdapter.write(jsonWriter, object.type()); + } + jsonWriter.name("bbox"); + if (object.bbox() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter boundingBoxTypeAdapter = this.boundingBoxTypeAdapter; + if (boundingBoxTypeAdapter == null) { + boundingBoxTypeAdapter = gson.getAdapter(BoundingBox.class); + this.boundingBoxTypeAdapter = boundingBoxTypeAdapter; + } + boundingBoxTypeAdapter.write(jsonWriter, object.bbox()); + } + jsonWriter.name("id"); + if (object.id() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter stringTypeAdapter = this.stringTypeAdapter; + if (stringTypeAdapter == null) { + stringTypeAdapter = gson.getAdapter(String.class); + this.stringTypeAdapter = stringTypeAdapter; + } + stringTypeAdapter.write(jsonWriter, object.id()); + } + jsonWriter.name("geometry"); + if (object.geometry() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter geometryTypeAdapter = this.geometryTypeAdapter; + if (geometryTypeAdapter == null) { + geometryTypeAdapter = gson.getAdapter(Geometry.class); + this.geometryTypeAdapter = geometryTypeAdapter; + } + geometryTypeAdapter.write(jsonWriter, object.geometry()); + } + jsonWriter.name("properties"); + if (object.properties() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter jsonObjectTypeAdapter = this.jsonObjectTypeAdapter; + if (jsonObjectTypeAdapter == null) { + jsonObjectTypeAdapter = gson.getAdapter(JsonObject.class); + this.jsonObjectTypeAdapter = jsonObjectTypeAdapter; + } + jsonObjectTypeAdapter.write(jsonWriter, object.properties()); + } + jsonWriter.endObject(); + } + + @Override + public Feature read(JsonReader jsonReader) throws IOException { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); + return null; + } + jsonReader.beginObject(); + String type = null; + BoundingBox bbox = null; + String id = null; + Geometry geometry = null; + JsonObject properties = null; + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); + continue; + } + switch (name) { + case "type": + TypeAdapter strTypeAdapter = this.stringTypeAdapter; + if (strTypeAdapter == null) { + strTypeAdapter = gson.getAdapter(String.class); + this.stringTypeAdapter = strTypeAdapter; + } + type = strTypeAdapter.read(jsonReader); + break; + + case "bbox": + TypeAdapter boundingBoxTypeAdapter = this.boundingBoxTypeAdapter; + if (boundingBoxTypeAdapter == null) { + boundingBoxTypeAdapter = gson.getAdapter(BoundingBox.class); + this.boundingBoxTypeAdapter = boundingBoxTypeAdapter; + } + bbox = boundingBoxTypeAdapter.read(jsonReader); + break; + + case "id": + strTypeAdapter = this.stringTypeAdapter; + if (strTypeAdapter == null) { + strTypeAdapter = gson.getAdapter(String.class); + this.stringTypeAdapter = strTypeAdapter; + } + id = strTypeAdapter.read(jsonReader); + break; + + case "geometry": + TypeAdapter geometryTypeAdapter = this.geometryTypeAdapter; + if (geometryTypeAdapter == null) { + geometryTypeAdapter = gson.getAdapter(Geometry.class); + this.geometryTypeAdapter = geometryTypeAdapter; + } + geometry = geometryTypeAdapter.read(jsonReader); + break; + + case "properties": + TypeAdapter jsonObjectTypeAdapter = this.jsonObjectTypeAdapter; + if (jsonObjectTypeAdapter == null) { + jsonObjectTypeAdapter = gson.getAdapter(JsonObject.class); + this.jsonObjectTypeAdapter = jsonObjectTypeAdapter; + } + properties = jsonObjectTypeAdapter.read(jsonReader); + break; + + default: + jsonReader.skipValue(); + + } + } + jsonReader.endObject(); + return new Feature(type, bbox, id, geometry, properties); + } + } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/FeatureCollection.java b/services-geojson/src/main/java/com/mapbox/geojson/FeatureCollection.java index 341dfacd8..a121deb52 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/FeatureCollection.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/FeatureCollection.java @@ -2,17 +2,18 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.google.auto.value.AutoValue; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; -import com.mapbox.geojson.gson.BoundingBoxDeserializer; -import com.mapbox.geojson.gson.BoundingBoxSerializer; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.mapbox.geojson.gson.BoundingBoxTypeAdapter; import com.mapbox.geojson.gson.GeoJsonAdapterFactory; -import com.mapbox.geojson.gson.GeometryDeserializer; -import com.mapbox.geojson.gson.PointDeserializer; -import com.mapbox.geojson.gson.PointSerializer; +import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -36,11 +37,17 @@ * * @since 1.0.0 */ -@AutoValue -public abstract class FeatureCollection implements GeoJson { +public final class FeatureCollection implements GeoJson { private static final String TYPE = "FeatureCollection"; + private final String type; + + @JsonAdapter(BoundingBoxTypeAdapter.class) + private final BoundingBox bbox; + + private final List features; + /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are * creating a FeatureCollection object from scratch it is better to use one of the other provided @@ -52,11 +59,10 @@ public abstract class FeatureCollection implements GeoJson { * @since 1.0.0 */ public static FeatureCollection fromJson(@NonNull String json) { + GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); - gson.registerTypeAdapter(Geometry.class, new GeometryDeserializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()); + gson.registerTypeAdapterFactory(GeometryAdapterFactory.create()); return gson.create().fromJson(json, FeatureCollection.class); } @@ -71,7 +77,7 @@ public static FeatureCollection fromJson(@NonNull String json) { * @since 1.0.0 */ public static FeatureCollection fromFeatures(@NonNull Feature[] features) { - return new AutoValue_FeatureCollection(TYPE, null, Arrays.asList(features)); + return new FeatureCollection(TYPE, null, Arrays.asList(features)); } /** @@ -84,7 +90,7 @@ public static FeatureCollection fromFeatures(@NonNull Feature[] features) { * @since 1.0.0 */ public static FeatureCollection fromFeatures(@NonNull List features) { - return new AutoValue_FeatureCollection(TYPE, null, features); + return new FeatureCollection(TYPE, null, features); } /** @@ -100,7 +106,7 @@ public static FeatureCollection fromFeatures(@NonNull List features) { */ public static FeatureCollection fromFeatures(@NonNull Feature[] features, @Nullable BoundingBox bbox) { - return new AutoValue_FeatureCollection(TYPE, bbox, Arrays.asList(features)); + return new FeatureCollection(TYPE, bbox, Arrays.asList(features)); } /** @@ -116,7 +122,7 @@ public static FeatureCollection fromFeatures(@NonNull Feature[] features, */ public static FeatureCollection fromFeatures(@NonNull List features, @Nullable BoundingBox bbox) { - return new AutoValue_FeatureCollection(TYPE, bbox, features); + return new FeatureCollection(TYPE, bbox, features); } /** @@ -129,7 +135,7 @@ public static FeatureCollection fromFeatures(@NonNull List features, */ public static FeatureCollection fromFeature(@NonNull Feature feature) { List featureList = Arrays.asList(feature); - return new AutoValue_FeatureCollection(TYPE, null, featureList); + return new FeatureCollection(TYPE, null, featureList); } /** @@ -144,7 +150,16 @@ public static FeatureCollection fromFeature(@NonNull Feature feature) { public static FeatureCollection fromFeature(@NonNull Feature feature, @Nullable BoundingBox bbox) { List featureList = Arrays.asList(feature); - return new AutoValue_FeatureCollection(TYPE, bbox, featureList); + return new FeatureCollection(TYPE, bbox, featureList); + } + + FeatureCollection(String type, @Nullable BoundingBox bbox, @Nullable List features) { + if (type == null) { + throw new NullPointerException("Null type"); + } + this.type = type; + this.bbox = bbox; + this.features = features; } /** @@ -157,7 +172,9 @@ public static FeatureCollection fromFeature(@NonNull Feature feature, */ @NonNull @Override - public abstract String type(); + public String type() { + return type; + } /** * A Feature Collection might have a member named {@code bbox} to include information on the @@ -171,7 +188,9 @@ public static FeatureCollection fromFeature(@NonNull Feature feature, */ @Nullable @Override - public abstract BoundingBox bbox(); + public BoundingBox bbox() { + return bbox; + } /** * This provides the list of feature making up this Feature Collection. Note that if the @@ -182,7 +201,9 @@ public static FeatureCollection fromFeature(@NonNull Feature feature, * @since 1.0.0 */ @Nullable - public abstract List features(); + public List features() { + return features; + } /** * This takes the currently defined values found inside this instance and converts it to a GeoJson @@ -193,9 +214,10 @@ public static FeatureCollection fromFeature(@NonNull Feature feature, */ @Override public String toJson() { + GsonBuilder gson = new GsonBuilder(); - gson.registerTypeAdapter(Point.class, new PointSerializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer()); + gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); + gson.registerTypeAdapterFactory(GeometryAdapterFactory.create()); return gson.create().toJson(this); } @@ -207,6 +229,158 @@ public String toJson() { * @since 3.0.0 */ public static TypeAdapter typeAdapter(Gson gson) { - return new AutoValue_FeatureCollection.GsonTypeAdapter(gson); + return new FeatureCollection.GsonTypeAdapter(gson); + } + + @Override + public String toString() { + return "FeatureCollection{" + + "type=" + type + ", " + + "bbox=" + bbox + ", " + + "features=" + features + + "}"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof FeatureCollection) { + FeatureCollection that = (FeatureCollection) obj; + return (this.type.equals(that.type())) + && ((this.bbox == null) ? (that.bbox() == null) : this.bbox.equals(that.bbox())) + && ((this.features == null) + ? (that.features() == null) : this.features.equals(that.features())); + } + return false; + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode *= 1000003; + hashCode ^= type.hashCode(); + hashCode *= 1000003; + hashCode ^= (bbox == null) ? 0 : bbox.hashCode(); + hashCode *= 1000003; + hashCode ^= (features == null) ? 0 : features.hashCode(); + return hashCode; + } + + /** + * TypeAdapter to serialize/deserialize FeatureCollection objects. + * + * @since 4.6.0 + */ + static final class GsonTypeAdapter extends TypeAdapter { + private volatile TypeAdapter stringAdapter; + private volatile TypeAdapter boundingBoxAdapter; + private volatile TypeAdapter> listFeatureAdapter; + private final Gson gson; + + GsonTypeAdapter(Gson gson) { + this.gson = gson; + } + + @Override + public void write(JsonWriter jsonWriter, FeatureCollection object) throws IOException { + if (object == null) { + jsonWriter.nullValue(); + return; + } + jsonWriter.beginObject(); + jsonWriter.name("type"); + if (object.type() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter stringAdapter = this.stringAdapter; + if (stringAdapter == null) { + stringAdapter = gson.getAdapter(String.class); + this.stringAdapter = stringAdapter; + } + stringAdapter.write(jsonWriter, object.type()); + } + jsonWriter.name("bbox"); + if (object.bbox() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter boundingBoxTypeAdapter = this.boundingBoxAdapter; + if (boundingBoxTypeAdapter == null) { + boundingBoxTypeAdapter = gson.getAdapter(BoundingBox.class); + this.boundingBoxAdapter = boundingBoxTypeAdapter; + } + boundingBoxTypeAdapter.write(jsonWriter, object.bbox()); + } + jsonWriter.name("features"); + if (object.features() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter> listFeatureAdapter = this.listFeatureAdapter; + if (listFeatureAdapter == null) { + TypeToken typeToken = TypeToken.getParameterized(List.class, Feature.class); + listFeatureAdapter = + (TypeAdapter>) gson.getAdapter(typeToken); + this.listFeatureAdapter = listFeatureAdapter; + } + listFeatureAdapter.write(jsonWriter, object.features()); + } + jsonWriter.endObject(); + } + + @Override + public FeatureCollection read(JsonReader jsonReader) throws IOException { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); + return null; + } + jsonReader.beginObject(); + String type = null; + BoundingBox bbox = null; + List features = null; + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); + continue; + } + switch (name) { + case "type": + TypeAdapter stringAdapter = this.stringAdapter; + if (stringAdapter == null) { + stringAdapter = gson.getAdapter(String.class); + this.stringAdapter = stringAdapter; + } + type = stringAdapter.read(jsonReader); + break; + + case "bbox": + TypeAdapter boundingBoxAdapter = this.boundingBoxAdapter; + if (boundingBoxAdapter == null) { + boundingBoxAdapter = gson.getAdapter(BoundingBox.class); + this.boundingBoxAdapter = boundingBoxAdapter; + } + bbox = boundingBoxAdapter.read(jsonReader); + break; + + case "features": + TypeAdapter> listFeatureAdapter = this.listFeatureAdapter; + if (listFeatureAdapter == null) { + TypeToken typeToken = TypeToken.getParameterized(List.class, Feature.class); + listFeatureAdapter = + (TypeAdapter>) gson.getAdapter(typeToken); + this.listFeatureAdapter = listFeatureAdapter; + } + features = listFeatureAdapter.read(jsonReader); + break; + + default: + jsonReader.skipValue(); + + } + } + jsonReader.endObject(); + return new FeatureCollection(type, bbox, features); + } } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/GeometryAdapterFactory.java b/services-geojson/src/main/java/com/mapbox/geojson/GeometryAdapterFactory.java new file mode 100644 index 000000000..5e6a283a1 --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/GeometryAdapterFactory.java @@ -0,0 +1,37 @@ +package com.mapbox.geojson; + +import com.google.gson.TypeAdapterFactory; + +import com.google.gson.typeadapters.RuntimeTypeAdapterFactory; + +/** + * A Geometry type adapter factory for convenience for serialization/deserialization. + * @since 4.6.0 + */ +public abstract class GeometryAdapterFactory implements TypeAdapterFactory { + + private static TypeAdapterFactory geometryTypeFactory; + + + /** + * Create a new instance of Geometry type adapter factory, this is passed into the Gson + * Builder. + * + * @return a new GSON TypeAdapterFactory + * @since 4.4.0 + */ + public static TypeAdapterFactory create() { + + if (geometryTypeFactory == null) { + geometryTypeFactory = RuntimeTypeAdapterFactory.of(Geometry.class, "type", true) + .registerSubtype(GeometryCollection.class, "GeometryCollection") + .registerSubtype(Point.class, "Point") + .registerSubtype(MultiPoint.class, "MultiPoint") + .registerSubtype(LineString.class, "LineString") + .registerSubtype(MultiLineString.class, "MultiLineString") + .registerSubtype(Polygon.class, "Polygon") + .registerSubtype(MultiPolygon.class, "MultiPolygon"); + } + return geometryTypeFactory; + } +} diff --git a/services-geojson/src/main/java/com/mapbox/geojson/GeometryCollection.java b/services-geojson/src/main/java/com/mapbox/geojson/GeometryCollection.java index 63cf7cc1d..bf8eb2ab7 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/GeometryCollection.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/GeometryCollection.java @@ -3,17 +3,16 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.google.auto.value.AutoValue; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; -import com.mapbox.geojson.gson.BoundingBoxDeserializer; -import com.mapbox.geojson.gson.BoundingBoxSerializer; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; import com.mapbox.geojson.gson.GeoJsonAdapterFactory; -import com.mapbox.geojson.gson.GeometryDeserializer; -import com.mapbox.geojson.gson.PointDeserializer; -import com.mapbox.geojson.gson.PointSerializer; +import java.io.IOException; import java.io.Serializable; import java.util.Arrays; import java.util.List; @@ -60,11 +59,16 @@ * * @since 1.0.0 */ -@AutoValue -public abstract class GeometryCollection implements Geometry, Serializable { +public final class GeometryCollection implements Geometry, Serializable { private static final String TYPE = "GeometryCollection"; + private final String type; + + private final BoundingBox bbox; + + private final List geometries; + /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are * creating a GeometryCollection object from scratch it is better to use one of the other provided @@ -76,11 +80,10 @@ public abstract class GeometryCollection implements Geometry, Serializable { * @since 1.0.0 */ public static GeometryCollection fromJson(String json) { + GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()); - gson.registerTypeAdapter(Geometry.class, new GeometryDeserializer()); + gson.registerTypeAdapterFactory(GeometryAdapterFactory.create()); return gson.create().fromJson(json, GeometryCollection.class); } @@ -93,7 +96,7 @@ public static GeometryCollection fromJson(String json) { * @since 1.0.0 */ public static GeometryCollection fromGeometries(@NonNull List geometries) { - return new AutoValue_GeometryCollection(TYPE, null, geometries); + return new GeometryCollection(TYPE, null, geometries); } /** @@ -107,7 +110,7 @@ public static GeometryCollection fromGeometries(@NonNull List geometri */ public static GeometryCollection fromGeometries(@NonNull List geometries, @Nullable BoundingBox bbox) { - return new AutoValue_GeometryCollection(TYPE, bbox, geometries); + return new GeometryCollection(TYPE, bbox, geometries); } /** @@ -120,7 +123,7 @@ public static GeometryCollection fromGeometries(@NonNull List geometri */ public static GeometryCollection fromGeometry(@NonNull Geometry geometry) { List geometries = Arrays.asList(geometry); - return new AutoValue_GeometryCollection(TYPE, null, geometries); + return new GeometryCollection(TYPE, null, geometries); } /** @@ -135,7 +138,29 @@ public static GeometryCollection fromGeometry(@NonNull Geometry geometry) { public static GeometryCollection fromGeometry(@NonNull Geometry geometry, @Nullable BoundingBox bbox) { List geometries = Arrays.asList(geometry); - return new AutoValue_GeometryCollection(TYPE, bbox, geometries); + return new GeometryCollection(TYPE, bbox, geometries); + } + + /** + * Create a new instance of this class by giving the collection a list of {@link Geometry} and + * bounding box. + * + * @param geometries a non-null list of geometry which makes up this collection + * @param bbox optionally include a bbox definition as a double array + * @return a new instance of this class defined by the values passed inside this static factory + * method + * @since 4.6.0 + */ + GeometryCollection(String type, @Nullable BoundingBox bbox, List geometries) { + if (type == null) { + throw new NullPointerException("Null type"); + } + this.type = type; + this.bbox = bbox; + if (geometries == null) { + throw new NullPointerException("Null geometries"); + } + this.geometries = geometries; } /** @@ -148,7 +173,9 @@ public static GeometryCollection fromGeometry(@NonNull Geometry geometry, */ @NonNull @Override - public abstract String type(); + public String type() { + return type; + } /** * A Feature Collection might have a member named {@code bbox} to include information on the @@ -162,7 +189,9 @@ public static GeometryCollection fromGeometry(@NonNull Geometry geometry, */ @Nullable @Override - public abstract BoundingBox bbox(); + public BoundingBox bbox() { + return bbox; + } /** * This provides the list of geometry making up this Geometry Collection. Note that if the @@ -173,7 +202,9 @@ public static GeometryCollection fromGeometry(@NonNull Geometry geometry, * @since 1.0.0 */ @NonNull - public abstract List geometries(); + public List geometries() { + return geometries; + } /** * This takes the currently defined values found inside this instance and converts it to a GeoJson @@ -185,8 +216,8 @@ public static GeometryCollection fromGeometry(@NonNull Geometry geometry, @Override public String toJson() { GsonBuilder gson = new GsonBuilder(); - gson.registerTypeAdapter(Point.class, new PointSerializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer()); + gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); + gson.registerTypeAdapterFactory(GeometryAdapterFactory.create()); return gson.create().toJson(this); } @@ -198,6 +229,157 @@ public String toJson() { * @since 3.0.0 */ public static TypeAdapter typeAdapter(Gson gson) { - return new AutoValue_GeometryCollection.GsonTypeAdapter(gson); + return new GeometryCollection.GsonTypeAdapter(gson); + } + + @Override + public String toString() { + return "GeometryCollection{" + + "type=" + type + ", " + + "bbox=" + bbox + ", " + + "geometries=" + geometries + + "}"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof GeometryCollection) { + GeometryCollection that = (GeometryCollection) obj; + return (this.type.equals(that.type())) + && ((this.bbox == null) ? (that.bbox() == null) : this.bbox.equals(that.bbox())) + && (this.geometries.equals(that.geometries())); + } + return false; + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode *= 1000003; + hashCode ^= type.hashCode(); + hashCode *= 1000003; + hashCode ^= (bbox == null) ? 0 : bbox.hashCode(); + hashCode *= 1000003; + hashCode ^= geometries.hashCode(); + return hashCode; + } + + /** + * TypeAdapter to serialize/deserialize GeometryCollection objects. + * + * @since 4.6.0 + */ + static final class GsonTypeAdapter extends TypeAdapter { + private volatile TypeAdapter stringTypeAdapter; + private volatile TypeAdapter boundingBoxTypeAdapter; + private volatile TypeAdapter> listGeometryAdapter; + private final Gson gson; + + GsonTypeAdapter(Gson gson) { + this.gson = gson; + } + + @Override + public void write(JsonWriter jsonWriter, GeometryCollection object) throws IOException { + if (object == null) { + jsonWriter.nullValue(); + return; + } + jsonWriter.beginObject(); + jsonWriter.name("type"); + if (object.type() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter stringTypeAdapter = this.stringTypeAdapter; + if (stringTypeAdapter == null) { + stringTypeAdapter = gson.getAdapter(String.class); + this.stringTypeAdapter = stringTypeAdapter; + } + stringTypeAdapter.write(jsonWriter, object.type()); + } + jsonWriter.name("bbox"); + if (object.bbox() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter boundingBoxTypeAdapter = this.boundingBoxTypeAdapter; + if (boundingBoxTypeAdapter == null) { + boundingBoxTypeAdapter = gson.getAdapter(BoundingBox.class); + this.boundingBoxTypeAdapter = boundingBoxTypeAdapter; + } + boundingBoxTypeAdapter.write(jsonWriter, object.bbox()); + } + jsonWriter.name("geometries"); + if (object.geometries() == null) { + jsonWriter.nullValue(); + } else { + TypeAdapter> listGeometryAdapter = this.listGeometryAdapter; + if (listGeometryAdapter == null) { + TypeToken typeToken = TypeToken.getParameterized(List.class, Geometry.class); + listGeometryAdapter = + (TypeAdapter>) gson.getAdapter(typeToken); + this.listGeometryAdapter = listGeometryAdapter; + } + listGeometryAdapter.write(jsonWriter, object.geometries()); + } + jsonWriter.endObject(); + } + + @Override + public GeometryCollection read(JsonReader jsonReader) throws IOException { + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); + return null; + } + jsonReader.beginObject(); + String type = null; + BoundingBox bbox = null; + List geometries = null; + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.nextNull(); + continue; + } + switch (name) { + case "type": + TypeAdapter stringTypeAdapter = this.stringTypeAdapter; + if (stringTypeAdapter == null) { + stringTypeAdapter = gson.getAdapter(String.class); + this.stringTypeAdapter = stringTypeAdapter; + } + type = stringTypeAdapter.read(jsonReader); + break; + + case "bbox": + TypeAdapter boundingBoxTypeAdapter = this.boundingBoxTypeAdapter; + if (boundingBoxTypeAdapter == null) { + boundingBoxTypeAdapter = gson.getAdapter(BoundingBox.class); + this.boundingBoxTypeAdapter = boundingBoxTypeAdapter; + } + bbox = boundingBoxTypeAdapter.read(jsonReader); + break; + + case "geometries": + TypeAdapter> listGeometryAdapter = this.listGeometryAdapter; + if (listGeometryAdapter == null) { + TypeToken typeToken = TypeToken.getParameterized(List.class, Geometry.class); + listGeometryAdapter = + (TypeAdapter>) gson.getAdapter(typeToken); + this.listGeometryAdapter = listGeometryAdapter; + } + geometries = listGeometryAdapter.read(jsonReader); + break; + + default: + jsonReader.skipValue(); + + } + } + jsonReader.endObject(); + return new GeometryCollection(type == null ? "GeometryCollection" : type, bbox, geometries); + } } } 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 d0280378d..f657610bb 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/LineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/LineString.java @@ -2,17 +2,15 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.google.auto.value.AutoValue; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; -import com.mapbox.geojson.gson.BoundingBoxDeserializer; -import com.mapbox.geojson.gson.BoundingBoxSerializer; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; import com.mapbox.geojson.gson.GeoJsonAdapterFactory; -import com.mapbox.geojson.gson.PointDeserializer; -import com.mapbox.geojson.gson.PointSerializer; import com.mapbox.geojson.utils.PolylineUtils; +import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -50,11 +48,16 @@ * * @since 1.0.0 */ -@AutoValue -public abstract class LineString implements CoordinateContainer>, Serializable { +public final class LineString implements CoordinateContainer>, Serializable { private static final String TYPE = "LineString"; + private final String type; + + private final BoundingBox bbox; + + private final List coordinates; + /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are * creating a LineString object from scratch it is better to use one of the other provided static @@ -70,8 +73,6 @@ public abstract class LineString implements CoordinateContainer>, Se public static LineString fromJson(String json) { GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()); return gson.create().fromJson(json, LineString.class); } @@ -85,7 +86,7 @@ public static LineString fromJson(String json) { * @since 3.0.0 */ public static LineString fromLngLats(@NonNull MultiPoint multiPoint) { - return new AutoValue_LineString(TYPE, null, multiPoint.coordinates()); + return new LineString(TYPE, null, multiPoint.coordinates()); } /** @@ -103,7 +104,7 @@ public static LineString fromLngLats(@NonNull MultiPoint multiPoint) { * @since 3.0.0 */ public static LineString fromLngLats(@NonNull List points) { - return new AutoValue_LineString(TYPE, null, points); + return new LineString(TYPE, null, points); } /** @@ -122,7 +123,7 @@ public static LineString fromLngLats(@NonNull List points) { * @since 3.0.0 */ public static LineString fromLngLats(@NonNull List points, @Nullable BoundingBox bbox) { - return new AutoValue_LineString(TYPE, bbox, points); + return new LineString(TYPE, bbox, points); } /** @@ -136,7 +137,19 @@ public static LineString fromLngLats(@NonNull List points, @Nullable Boun * @since 3.0.0 */ public static LineString fromLngLats(@NonNull MultiPoint multiPoint, @Nullable BoundingBox bbox) { - return new AutoValue_LineString(TYPE, bbox, multiPoint.coordinates()); + return new LineString(TYPE, bbox, multiPoint.coordinates()); + } + + LineString(String type, @Nullable BoundingBox bbox, List coordinates) { + if (type == null) { + throw new NullPointerException("Null type"); + } + this.type = type; + this.bbox = bbox; + if (coordinates == null) { + throw new NullPointerException("Null coordinates"); + } + this.coordinates = coordinates; } static LineString fromLngLats(double[][] coordinates) { @@ -175,7 +188,9 @@ public static LineString fromPolyline(@NonNull String polyline, int precision) { */ @NonNull @Override - public abstract String type(); + public String type() { + return type; + } /** * A Feature Collection might have a member named {@code bbox} to include information on the @@ -189,7 +204,9 @@ public static LineString fromPolyline(@NonNull String polyline, int precision) { */ @Nullable @Override - public abstract BoundingBox bbox(); + public BoundingBox bbox() { + return bbox; + } /** * Provides the list of {@link Point}s that make up the LineString geometry. @@ -199,7 +216,9 @@ public static LineString fromPolyline(@NonNull String polyline, int precision) { */ @NonNull @Override - public abstract List coordinates(); + public List coordinates() { + return coordinates; + } /** * This takes the currently defined values found inside this instance and converts it to a GeoJson @@ -211,8 +230,7 @@ public static LineString fromPolyline(@NonNull String polyline, int precision) { @Override public String toJson() { GsonBuilder gson = new GsonBuilder(); - gson.registerTypeAdapter(Point.class, new PointSerializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer()); + gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); return gson.create().toJson(this); } @@ -237,6 +255,70 @@ public String toPolyline(int precision) { * @since 3.0.0 */ public static TypeAdapter typeAdapter(Gson gson) { - return new AutoValue_LineString.GsonTypeAdapter(gson); + return new LineString.GsonTypeAdapter(gson); + } + + @Override + public String toString() { + return "LineString{" + + "type=" + type + ", " + + "bbox=" + bbox + ", " + + "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; + } + + @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; + } + + /** + * TypeAdapter for LineString geometry. + * + * @since 4.6.0 + */ + static final class GsonTypeAdapter extends BaseGeometryTypeAdapter> { + + GsonTypeAdapter(Gson gson) { + super(gson, new ListOfPointCoordinatesTypeAdapter()); + } + + @Override + public void write(JsonWriter jsonWriter, LineString object) throws IOException { + writeCoordinateContainer(jsonWriter, object); + } + + @Override + public LineString read(JsonReader jsonReader) throws IOException { + return (LineString) readCoordinateContainer(jsonReader); + } + + @Override + CoordinateContainer> createCoordinateContainer(String type, + BoundingBox bbox, + List coordinates) { + return new LineString(type == null ? "LineString" : type, bbox, coordinates); + } } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/ListOfDoublesCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/ListOfDoublesCoordinatesTypeAdapter.java new file mode 100644 index 000000000..a5480aee4 --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/ListOfDoublesCoordinatesTypeAdapter.java @@ -0,0 +1,25 @@ +package com.mapbox.geojson; + +import com.google.gson.stream.JsonReader; +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. + * + * @since 4.6.0 + */ +class ListOfDoublesCoordinatesTypeAdapter extends BaseCoordinatesTypeAdapter> { + + @Override + public void write(JsonWriter out, List value) throws IOException { + writePointList(out, value); + } + + @Override + public List read(JsonReader in) throws IOException { + return readPointList(in); + } +} diff --git a/services-geojson/src/main/java/com/mapbox/geojson/ListOfListOfPointCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/ListOfListOfPointCoordinatesTypeAdapter.java new file mode 100644 index 000000000..e41f04026 --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/ListOfListOfPointCoordinatesTypeAdapter.java @@ -0,0 +1,74 @@ +package com.mapbox.geojson; + +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 ist <List<Point>> + * into/from three dimentional double array. + * + * @since 4.6.0 + */ +class ListOfListOfPointCoordinatesTypeAdapter + extends BaseCoordinatesTypeAdapter>> { + + @Override + public void write(JsonWriter out, List> points) throws IOException { + + if (points == null) { + out.nullValue(); + return; + } + + out.beginArray(); + + for (List listOfPoints : points) { + + out.beginArray(); + + for (Point point : listOfPoints) { + writePoint(out, point); + } + out.endArray(); + } + + out.endArray(); + } + + @Override + public List> read(JsonReader in) throws IOException { + + if (in.peek() == JsonToken.NULL) { + throw new NullPointerException(); + } + + if (in.peek() == JsonToken.BEGIN_ARRAY) { + + in.beginArray(); + List> points = new ArrayList<>(); + + while (in.peek() == JsonToken.BEGIN_ARRAY) { + + in.beginArray(); + List listOfPoints = new ArrayList<>(); + + while (in.peek() == JsonToken.BEGIN_ARRAY) { + listOfPoints.add(readPoint(in)); + } + in.endArray(); + points.add(listOfPoints); + } + in.endArray(); + + return points; + } + + throw new GeoJsonException("coordinates should be array of array of array of double"); + } +} diff --git a/services-geojson/src/main/java/com/mapbox/geojson/ListOfPointCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/ListOfPointCoordinatesTypeAdapter.java new file mode 100644 index 000000000..1ede3b278 --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/ListOfPointCoordinatesTypeAdapter.java @@ -0,0 +1,57 @@ +package com.mapbox.geojson; + +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 + */ +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"); + } +} diff --git a/services-geojson/src/main/java/com/mapbox/geojson/ListofListofListOfPointCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/ListofListofListOfPointCoordinatesTypeAdapter.java new file mode 100644 index 000000000..e71694914 --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/ListofListofListOfPointCoordinatesTypeAdapter.java @@ -0,0 +1,86 @@ +package com.mapbox.geojson; + +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<List<List<Point>>> into/from + * four dimentional double array. + * + * @since 4.6.0 + */ +class ListofListofListOfPointCoordinatesTypeAdapter + extends BaseCoordinatesTypeAdapter>>> { + + @Override + public void write(JsonWriter out, List>> points) throws IOException { + + if (points == null) { + out.nullValue(); + return; + } + + out.beginArray(); + + for (List> listOfListOfPoints : points) { + + out.beginArray(); + + for (List listOfPoints : listOfListOfPoints) { + + out.beginArray(); + + for (Point point : listOfPoints) { + writePoint(out, point); + } + out.endArray(); + } + + out.endArray(); + } + out.endArray(); + } + + @Override + public List>> read(JsonReader in) throws IOException { + + if (in.peek() == JsonToken.NULL) { + throw new NullPointerException(); + } + + if (in.peek() == JsonToken.BEGIN_ARRAY) { + + in.beginArray(); + List>> listOfListOflistOfPoints = new ArrayList<>(); + while (in.peek() == JsonToken.BEGIN_ARRAY) { + + in.beginArray(); + List> listOfListOfPoints = new ArrayList<>(); + + while (in.peek() == JsonToken.BEGIN_ARRAY) { + + in.beginArray(); + List listOfPoints = new ArrayList<>(); + while (in.peek() == JsonToken.BEGIN_ARRAY) { + listOfPoints.add(readPoint(in)); + } + in.endArray(); + + listOfListOfPoints.add(listOfPoints); + } + in.endArray(); + listOfListOflistOfPoints.add(listOfListOfPoints); + } + in.endArray(); + return listOfListOflistOfPoints; + } + + throw new GeoJsonException("coordinates should be array of array of array of double"); + } +} 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 0505f88fe..286eaff69 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiLineString.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiLineString.java @@ -2,15 +2,14 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.google.auto.value.AutoValue; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; -import com.mapbox.geojson.gson.BoundingBoxSerializer; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; import com.mapbox.geojson.gson.GeoJsonAdapterFactory; -import com.mapbox.geojson.gson.PointDeserializer; -import com.mapbox.geojson.gson.PointSerializer; +import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; @@ -50,12 +49,17 @@ * * @since 1.0.0 */ -@AutoValue -public abstract class MultiLineString +public final class MultiLineString implements CoordinateContainer>>, Serializable { private static final String TYPE = "MultiLineString"; + private final String type; + + private final BoundingBox bbox; + + private final List> coordinates; + /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are * creating a MultiLineString object from scratch it is better to use one of the other provided @@ -69,7 +73,6 @@ public abstract class MultiLineString public static MultiLineString fromJson(@NonNull String json) { GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); return gson.create().fromJson(json, MultiLineString.class); } @@ -88,7 +91,7 @@ public static MultiLineString fromLineStrings(@NonNull List lineStri for (LineString lineString : lineStrings) { coordinates.add(lineString.coordinates()); } - return new AutoValue_MultiLineString(TYPE, null, coordinates); + return new MultiLineString(TYPE, null, coordinates); } /** @@ -109,7 +112,7 @@ public static MultiLineString fromLineStrings(@NonNull List lineStri for (LineString lineString : lineStrings) { coordinates.add(lineString.coordinates()); } - return new AutoValue_MultiLineString(TYPE, bbox, coordinates); + return new MultiLineString(TYPE, bbox, coordinates); } /** @@ -123,7 +126,7 @@ public static MultiLineString fromLineStrings(@NonNull List lineStri */ public static MultiLineString fromLineString(@NonNull LineString lineString) { List> coordinates = Arrays.asList(lineString.coordinates()); - return new AutoValue_MultiLineString(TYPE, null, coordinates); + return new MultiLineString(TYPE, null, coordinates); } /** @@ -139,7 +142,7 @@ public static MultiLineString fromLineString(@NonNull LineString lineString) { public static MultiLineString fromLineString(@NonNull LineString lineString, @Nullable BoundingBox bbox) { List> coordinates = Arrays.asList(lineString.coordinates()); - return new AutoValue_MultiLineString(TYPE, bbox, coordinates); + return new MultiLineString(TYPE, bbox, coordinates); } /** @@ -154,7 +157,7 @@ public static MultiLineString fromLineString(@NonNull LineString lineString, * @since 3.0.0 */ public static MultiLineString fromLngLats(@NonNull List> points) { - return new AutoValue_MultiLineString(TYPE, null, points); + return new MultiLineString(TYPE, null, points); } /** @@ -171,7 +174,7 @@ public static MultiLineString fromLngLats(@NonNull List> points) { */ public static MultiLineString fromLngLats(@NonNull List> points, @Nullable BoundingBox bbox) { - return new AutoValue_MultiLineString(TYPE, bbox, points); + return new MultiLineString(TYPE, bbox, points); } static MultiLineString fromLngLats(double[][][] coordinates) { @@ -184,7 +187,19 @@ static MultiLineString fromLngLats(double[][][] coordinates) { multiLine.add(lineString); } - return new AutoValue_MultiLineString(TYPE, null, multiLine); + return new MultiLineString(TYPE, null, multiLine); + } + + MultiLineString(String type, @Nullable BoundingBox bbox, List> coordinates) { + if (type == null) { + throw new NullPointerException("Null type"); + } + this.type = type; + this.bbox = bbox; + if (coordinates == null) { + throw new NullPointerException("Null coordinates"); + } + this.coordinates = coordinates; } /** @@ -197,7 +212,9 @@ static MultiLineString fromLngLats(double[][][] coordinates) { */ @NonNull @Override - public abstract String type(); + public String type() { + return type; + } /** * A Feature Collection might have a member named {@code bbox} to include information on the @@ -211,7 +228,9 @@ static MultiLineString fromLngLats(double[][][] coordinates) { */ @Nullable @Override - public abstract BoundingBox bbox(); + public BoundingBox bbox() { + return bbox; + } /** * Provides the list of list of {@link Point}s that make up the MultiLineString geometry. @@ -221,7 +240,9 @@ static MultiLineString fromLngLats(double[][][] coordinates) { */ @NonNull @Override - public abstract List> coordinates(); + public List> coordinates() { + return coordinates; + } /** * Returns a list of LineStrings which are currently making up this MultiLineString. @@ -248,8 +269,7 @@ public List lineStrings() { @Override public String toJson() { GsonBuilder gson = new GsonBuilder(); - gson.registerTypeAdapter(Point.class, new PointSerializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer()); + gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); return gson.create().toJson(this); } @@ -261,6 +281,71 @@ public String toJson() { * @since 3.0.0 */ public static TypeAdapter typeAdapter(Gson gson) { - return new AutoValue_MultiLineString.GsonTypeAdapter(gson); + return new MultiLineString.GsonTypeAdapter(gson); + } + + @Override + public String toString() { + return "MultiLineString{" + + "type=" + type + ", " + + "bbox=" + bbox + ", " + + "coordinates=" + coordinates + + "}"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof MultiLineString) { + MultiLineString that = (MultiLineString) 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; + } + + @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; + } + + /** + * TypeAdapter for MultiLineString geometry. + * + * @since 4.6.0 + */ + static final class GsonTypeAdapter + extends BaseGeometryTypeAdapter>> { + + GsonTypeAdapter(Gson gson) { + super(gson, new ListOfListOfPointCoordinatesTypeAdapter()); + } + + @Override + public void write(JsonWriter jsonWriter, MultiLineString object) throws IOException { + writeCoordinateContainer(jsonWriter, object); + } + + @Override + public MultiLineString read(JsonReader jsonReader) throws IOException { + return (MultiLineString) readCoordinateContainer(jsonReader); + } + + @Override + CoordinateContainer>> createCoordinateContainer(String type, + BoundingBox bbox, + List> coords) { + return new MultiLineString(type == null ? "MultiLineString" : type, bbox, coords); + } } } 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 dd145f808..d166eb9fd 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiPoint.java @@ -2,15 +2,14 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.google.auto.value.AutoValue; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; import com.mapbox.geojson.gson.GeoJsonAdapterFactory; -import com.mapbox.geojson.gson.PointDeserializer; -import com.mapbox.geojson.gson.BoundingBoxSerializer; -import com.mapbox.geojson.gson.PointSerializer; +import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -35,11 +34,16 @@ * * @since 1.0.0 */ -@AutoValue -public abstract class MultiPoint implements CoordinateContainer>, Serializable { +public final class MultiPoint implements CoordinateContainer>, Serializable { private static final String TYPE = "MultiPoint"; + private final String type; + + private final BoundingBox bbox; + + private final List coordinates; + /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are * creating a MultiPoint object from scratch it is better to use one of the other provided static @@ -54,7 +58,6 @@ public abstract class MultiPoint implements CoordinateContainer>, Se public static MultiPoint fromJson(@NonNull String json) { GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); return gson.create().fromJson(json, MultiPoint.class); } @@ -70,7 +73,7 @@ public static MultiPoint fromJson(@NonNull String json) { * @since 3.0.0 */ public static MultiPoint fromLngLats(@NonNull List points) { - return new AutoValue_MultiPoint(TYPE, null, points); + return new MultiPoint(TYPE, null, points); } /** @@ -86,7 +89,7 @@ public static MultiPoint fromLngLats(@NonNull List points) { * @since 3.0.0 */ public static MultiPoint fromLngLats(@NonNull List points, @Nullable BoundingBox bbox) { - return new AutoValue_MultiPoint(TYPE, bbox, points); + return new MultiPoint(TYPE, bbox, points); } static MultiPoint fromLngLats(@NonNull double[][] coordinates) { @@ -95,7 +98,19 @@ static MultiPoint fromLngLats(@NonNull double[][] coordinates) { converted.add(Point.fromLngLat(coordinates[i])); } - return new AutoValue_MultiPoint(TYPE, null, converted); + return new MultiPoint(TYPE, null, converted); + } + + MultiPoint(String type, @Nullable BoundingBox bbox, List coordinates) { + if (type == null) { + throw new NullPointerException("Null type"); + } + this.type = type; + this.bbox = bbox; + if (coordinates == null) { + throw new NullPointerException("Null coordinates"); + } + this.coordinates = coordinates; } /** @@ -108,7 +123,9 @@ static MultiPoint fromLngLats(@NonNull double[][] coordinates) { */ @NonNull @Override - public abstract String type(); + public String type() { + return type; + } /** * A Feature Collection might have a member named {@code bbox} to include information on the @@ -122,7 +139,9 @@ static MultiPoint fromLngLats(@NonNull double[][] coordinates) { */ @Nullable @Override - public abstract BoundingBox bbox(); + public BoundingBox bbox() { + return bbox; + } /** * provides the list of {@link Point}s that make up the MultiPoint geometry. @@ -132,7 +151,9 @@ static MultiPoint fromLngLats(@NonNull double[][] coordinates) { */ @NonNull @Override - public abstract List coordinates(); + public List coordinates() { + return coordinates; + } /** * This takes the currently defined values found inside this instance and converts it to a GeoJson @@ -144,8 +165,7 @@ static MultiPoint fromLngLats(@NonNull double[][] coordinates) { @Override public String toJson() { GsonBuilder gson = new GsonBuilder(); - gson.registerTypeAdapter(Point.class, new PointSerializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer()); + gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); return gson.create().toJson(this); } @@ -157,6 +177,70 @@ public String toJson() { * @since 3.0.0 */ public static TypeAdapter typeAdapter(Gson gson) { - return new AutoValue_MultiPoint.GsonTypeAdapter(gson); + return new MultiPoint.GsonTypeAdapter(gson); + } + + @Override + public String toString() { + return "MultiPoint{" + + "type=" + type + ", " + + "bbox=" + bbox + ", " + + "coordinates=" + 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; + } + + @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; + } + + /** + * TypeAdapter for MultiPoint geometry. + * + * @since 4.6.0 + */ + static final class GsonTypeAdapter extends BaseGeometryTypeAdapter> { + + GsonTypeAdapter(Gson gson) { + super(gson, new ListOfPointCoordinatesTypeAdapter()); + } + + @Override + public void write(JsonWriter jsonWriter, MultiPoint object) throws IOException { + writeCoordinateContainer(jsonWriter, object); + } + + @Override + public MultiPoint read(JsonReader jsonReader) throws IOException { + return (MultiPoint) readCoordinateContainer(jsonReader); + } + + @Override + CoordinateContainer> createCoordinateContainer(String type, + BoundingBox bbox, + List coordinates) { + return new MultiPoint(type == null ? "MultiPoint" : type, bbox, coordinates); + } } } 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 2eeb703c3..1e8701c70 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/MultiPolygon.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/MultiPolygon.java @@ -2,15 +2,14 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.google.auto.value.AutoValue; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; -import com.mapbox.geojson.gson.BoundingBoxSerializer; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; import com.mapbox.geojson.gson.GeoJsonAdapterFactory; -import com.mapbox.geojson.gson.PointDeserializer; -import com.mapbox.geojson.gson.PointSerializer; +import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; @@ -68,12 +67,17 @@ * * @since 1.0.0 */ -@AutoValue -public abstract class MultiPolygon +public final class MultiPolygon implements CoordinateContainer>>>, Serializable { private static final String TYPE = "MultiPolygon"; + private final String type; + + private final BoundingBox bbox; + + private final List>> coordinates; + /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are * creating a MultiPolygon object from scratch it is better to use one of the other provided @@ -87,7 +91,6 @@ public abstract class MultiPolygon public static MultiPolygon fromJson(String json) { GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); return gson.create().fromJson(json, MultiPolygon.class); } @@ -106,7 +109,7 @@ public static MultiPolygon fromPolygons(@NonNull List polygons) { for (Polygon polygon : polygons) { coordinates.add(polygon.coordinates()); } - return new AutoValue_MultiPolygon(TYPE, null, coordinates); + return new MultiPolygon(TYPE, null, coordinates); } /** @@ -127,7 +130,7 @@ public static MultiPolygon fromPolygons(@NonNull List polygons, for (Polygon polygon : polygons) { coordinates.add(polygon.coordinates()); } - return new AutoValue_MultiPolygon(TYPE, bbox, coordinates); + return new MultiPolygon(TYPE, bbox, coordinates); } /** @@ -142,7 +145,7 @@ public static MultiPolygon fromPolygons(@NonNull List polygons, */ public static MultiPolygon fromPolygon(@NonNull Polygon polygon) { List>> coordinates = Arrays.asList(polygon.coordinates()); - return new AutoValue_MultiPolygon(TYPE, null, coordinates); + return new MultiPolygon(TYPE, null, coordinates); } /** @@ -158,7 +161,7 @@ public static MultiPolygon fromPolygon(@NonNull Polygon polygon) { */ public static MultiPolygon fromPolygon(@NonNull Polygon polygon, @Nullable BoundingBox bbox) { List>> coordinates = Arrays.asList(polygon.coordinates()); - return new AutoValue_MultiPolygon(TYPE, bbox, coordinates); + return new MultiPolygon(TYPE, bbox, coordinates); } /** @@ -171,7 +174,7 @@ public static MultiPolygon fromPolygon(@NonNull Polygon polygon, @Nullable Bound * @since 3.0.0 */ public static MultiPolygon fromLngLats(@NonNull List>> points) { - return new AutoValue_MultiPolygon(TYPE, null, points); + return new MultiPolygon(TYPE, null, points); } /** @@ -186,7 +189,7 @@ public static MultiPolygon fromLngLats(@NonNull List>> points) */ public static MultiPolygon fromLngLats(@NonNull List>> points, @Nullable BoundingBox bbox) { - return new AutoValue_MultiPolygon(TYPE, bbox, points); + return new MultiPolygon(TYPE, bbox, points); } static MultiPolygon fromLngLats(@NonNull double[][][][] coordinates) { @@ -203,7 +206,19 @@ static MultiPolygon fromLngLats(@NonNull double[][][][] coordinates) { converted.add(innerOneList); } - return new AutoValue_MultiPolygon(TYPE, null, converted); + return new MultiPolygon(TYPE, null, converted); + } + + MultiPolygon(String type, @Nullable BoundingBox bbox, List>> coordinates) { + if (type == null) { + throw new NullPointerException("Null type"); + } + this.type = type; + this.bbox = bbox; + if (coordinates == null) { + throw new NullPointerException("Null coordinates"); + } + this.coordinates = coordinates; } /** @@ -231,7 +246,9 @@ public List polygons() { */ @NonNull @Override - public abstract String type(); + public String type() { + return type; + } /** * A Feature Collection might have a member named {@code bbox} to include information on the @@ -245,7 +262,9 @@ public List polygons() { */ @Nullable @Override - public abstract BoundingBox bbox(); + public BoundingBox bbox() { + return bbox; + } /** * Provides the list of list of list of {@link Point}s that make up the MultiPolygon geometry. @@ -255,7 +274,9 @@ public List polygons() { */ @NonNull @Override - public abstract List>> coordinates(); + public List>> coordinates() { + return coordinates; + } /** * This takes the currently defined values found inside this instance and converts it to a GeoJson @@ -267,8 +288,7 @@ public List polygons() { @Override public String toJson() { GsonBuilder gson = new GsonBuilder(); - gson.registerTypeAdapter(Point.class, new PointSerializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer()); + gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); return gson.create().toJson(this); } @@ -280,6 +300,70 @@ public String toJson() { * @since 3.0.0 */ public static TypeAdapter typeAdapter(Gson gson) { - return new AutoValue_MultiPolygon.GsonTypeAdapter(gson); + return new MultiPolygon.GsonTypeAdapter(gson); + } + + @Override + public String toString() { + return "Polygon{" + + "type=" + type + ", " + + "bbox=" + bbox + ", " + + "coordinates=" + coordinates + + "}"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof MultiPolygon) { + MultiPolygon that = (MultiPolygon) 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; + } + + @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; + } + + /** + * TypeAdapter for MultiPolygon geometry. + * + * @since 4.6.0 + */ + static final class GsonTypeAdapter + extends BaseGeometryTypeAdapter>>> { + + GsonTypeAdapter(Gson gson) { + super(gson, new ListofListofListOfPointCoordinatesTypeAdapter()); + } + + @Override + public void write(JsonWriter jsonWriter, MultiPolygon object) throws IOException { + writeCoordinateContainer(jsonWriter, object); + } + + @Override + public MultiPolygon read(JsonReader jsonReader) throws IOException { + return (MultiPolygon) readCoordinateContainer(jsonReader); + } + + @Override + CoordinateContainer>>> + createCoordinateContainer(String type, BoundingBox bbox, List>> coords) { + return new MultiPolygon(type == null ? "MultiPolygon" : type, bbox, coords); + } } } 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 e9cb1990b..0e0837a17 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Point.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Point.java @@ -8,17 +8,15 @@ import android.support.annotation.FloatRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.google.auto.value.AutoValue; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; -import com.google.gson.reflect.TypeToken; -import com.mapbox.geojson.gson.BoundingBoxDeserializer; -import com.mapbox.geojson.gson.BoundingBoxSerializer; -import com.mapbox.geojson.gson.CoordinateTypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; import com.mapbox.geojson.gson.GeoJsonAdapterFactory; import com.mapbox.geojson.shifter.CoordinateShifterManager; +import java.io.IOException; import java.io.Serializable; import java.util.List; @@ -53,11 +51,19 @@ * * @since 1.0.0 */ -@AutoValue -public abstract class Point implements CoordinateContainer>, Serializable { +public final class Point implements CoordinateContainer>, Serializable { private static final String TYPE = "Point"; + @NonNull + private final String type; + + @Nullable + private final BoundingBox bbox; + + @NonNull + private final List coordinates; + /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are * creating a Point object from scratch it is better to use one of the other provided static @@ -74,9 +80,6 @@ public abstract class Point implements CoordinateContainer>, Serial public static Point fromJson(@NonNull String json) { GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); - gson.registerTypeAdapter(new TypeToken>(){}.getType(), - new CoordinateTypeAdapter()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()); return gson.create().fromJson(json, Point.class); } @@ -99,7 +102,7 @@ public static Point fromLngLat( List coordinates = CoordinateShifterManager.getCoordinateShifter().shiftLonLat(longitude, latitude); - return new AutoValue_Point(TYPE, null, coordinates); + return new Point(TYPE, null, coordinates); } /** @@ -124,7 +127,7 @@ public static Point fromLngLat( List coordinates = CoordinateShifterManager.getCoordinateShifter().shiftLonLat(longitude, latitude); - return new AutoValue_Point(TYPE, bbox, coordinates); + return new Point(TYPE, bbox, coordinates); } /** @@ -151,7 +154,7 @@ public static Point fromLngLat( List coordinates = CoordinateShifterManager.getCoordinateShifter().shiftLonLatAlt(longitude, latitude, altitude); - return new AutoValue_Point(TYPE, null, coordinates); + return new Point(TYPE, null, coordinates); } /** @@ -178,7 +181,7 @@ public static Point fromLngLat( List coordinates = CoordinateShifterManager.getCoordinateShifter().shiftLonLatAlt(longitude, latitude, altitude); - return new AutoValue_Point(TYPE, bbox, coordinates); + return new Point(TYPE, bbox, coordinates); } static Point fromLngLat(@NonNull double[] coords) { @@ -191,6 +194,18 @@ static Point fromLngLat(@NonNull double[] coords) { return null; } + Point(String type, @Nullable BoundingBox bbox, List coordinates) { + if (type == null) { + throw new NullPointerException("Null type"); + } + this.type = type; + this.bbox = bbox; + if (coordinates == null || coordinates.size() == 0) { + throw new NullPointerException("Null coordinates"); + } + this.coordinates = coordinates; + } + /** * This returns a double value ranging from -180 to 180 representing the x or easting position of * this point. ideally, this value would be restricted to 6 decimal places to correctly follow the @@ -255,7 +270,9 @@ public boolean hasAltitude() { */ @NonNull @Override - public abstract String type(); + public String type() { + return type; + } /** * A Feature Collection might have a member named {@code bbox} to include information on the @@ -269,7 +286,9 @@ public boolean hasAltitude() { */ @Nullable @Override - public abstract BoundingBox bbox(); + public BoundingBox bbox() { + return bbox; + } /** * Provide a single double array containing the longitude, latitude, and optionally an @@ -281,7 +300,9 @@ public boolean hasAltitude() { */ @NonNull @Override - public abstract List coordinates(); + public List coordinates() { + return coordinates; + } /** * This takes the currently defined values found inside this instance and converts it to a GeoJson @@ -293,9 +314,7 @@ public boolean hasAltitude() { @Override public String toJson() { GsonBuilder gson = new GsonBuilder(); - gson.registerTypeAdapter(new TypeToken>(){}.getType(), - new CoordinateTypeAdapter()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer()); + gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); return gson.create().toJson(this); } @@ -307,6 +326,72 @@ public String toJson() { * @since 3.0.0 */ public static TypeAdapter typeAdapter(Gson gson) { - return new AutoValue_Point.GsonTypeAdapter(gson); + return new Point.GsonTypeAdapter(gson); + } + + @Override + public String toString() { + return "Point{" + + "type=" + type + ", " + + "bbox=" + bbox + ", " + + "coordinates=" + coordinates + + "}"; + } + + @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; + } + + @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; + } + + /** + * TypeAdapter for Point geometry. + * + * @since 4.6.0 + */ + static final class GsonTypeAdapter extends BaseGeometryTypeAdapter> { + + GsonTypeAdapter(Gson gson) { + super(gson, new ListOfDoublesCoordinatesTypeAdapter()); + } + + @Override + @SuppressWarnings("unchecked") + public void write(JsonWriter jsonWriter, Point object) throws IOException { + writeCoordinateContainer(jsonWriter, object); + } + + @Override + @SuppressWarnings("unchecked") + public Point read(JsonReader jsonReader) throws IOException { + return (Point)readCoordinateContainer(jsonReader); + } + + @Override + CoordinateContainer> createCoordinateContainer(String type, + BoundingBox bbox, + List coordinates) { + return new Point(type == null ? "Point" : type, bbox, coordinates); + } } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/PointAsCoordinatesTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/PointAsCoordinatesTypeAdapter.java new file mode 100644 index 000000000..1b5d539bf --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/PointAsCoordinatesTypeAdapter.java @@ -0,0 +1,25 @@ +package com.mapbox.geojson; + +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; + +/** + * TypeAdapter to serialize Point as coordinates, i.e array of doubles and + * to deserialize into Point out of array of doubles. + * + * @since 4.6.0 + */ +public class PointAsCoordinatesTypeAdapter extends BaseCoordinatesTypeAdapter { + + @Override + public void write(JsonWriter out, Point value) throws IOException { + writePoint(out, value); + } + + @Override + public Point read(JsonReader in) throws IOException { + return readPoint(in); + } +} 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 b0150d424..9545abe66 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/Polygon.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/Polygon.java @@ -4,18 +4,15 @@ import android.support.annotation.Nullable; import android.support.annotation.Size; -import com.google.auto.value.AutoValue; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; -import com.mapbox.geojson.gson.GeoJsonAdapterFactory; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; import com.mapbox.geojson.exception.GeoJsonException; -import com.mapbox.geojson.gson.BoundingBoxDeserializer; -import com.mapbox.geojson.gson.GeometryDeserializer; -import com.mapbox.geojson.gson.PointDeserializer; -import com.mapbox.geojson.gson.BoundingBoxSerializer; -import com.mapbox.geojson.gson.PointSerializer; +import com.mapbox.geojson.gson.GeoJsonAdapterFactory; +import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -58,11 +55,16 @@ * * @since 1.0.0 */ -@AutoValue -public abstract class Polygon implements CoordinateContainer>>, Serializable { +public final class Polygon implements CoordinateContainer>>, Serializable { private static final String TYPE = "Polygon"; + private final String type; + + private final BoundingBox bbox; + + private final List> coordinates; + /** * Create a new instance of this class by passing in a formatted valid JSON String. If you are * creating a Polygon object from scratch it is better to use one of the other provided static @@ -78,9 +80,6 @@ public abstract class Polygon implements CoordinateContainer>>, public static Polygon fromJson(@NonNull String json) { GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); - gson.registerTypeAdapter(Geometry.class, new GeometryDeserializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()); return gson.create().fromJson(json, Polygon.class); } @@ -95,7 +94,7 @@ public static Polygon fromJson(@NonNull String json) { * @since 3.0.0 */ public static Polygon fromLngLats(@NonNull List> coordinates) { - return new AutoValue_Polygon(TYPE, null, coordinates); + return new Polygon(TYPE, null, coordinates); } /** @@ -111,7 +110,7 @@ public static Polygon fromLngLats(@NonNull List> coordinates) { */ public static Polygon fromLngLats(@NonNull List> coordinates, @Nullable BoundingBox bbox) { - return new AutoValue_Polygon(TYPE, bbox, coordinates); + return new Polygon(TYPE, bbox, coordinates); } /** @@ -132,7 +131,7 @@ static Polygon fromLngLats(@NonNull double[][][] coordinates) { } converted.add(innerList); } - return new AutoValue_Polygon(TYPE, null, converted); + return new Polygon(TYPE, null, converted); } /** @@ -154,13 +153,13 @@ public static Polygon fromOuterInner(@NonNull LineString outer, @Nullable LineSt coordinates.add(outer.coordinates()); // If inner rings are set to null, return early. if (inner == null) { - return new AutoValue_Polygon(TYPE, null, coordinates); + return new Polygon(TYPE, null, coordinates); } for (LineString lineString : inner) { isLinearRing(lineString); coordinates.add(lineString.coordinates()); } - return new AutoValue_Polygon(TYPE, null, coordinates); + return new Polygon(TYPE, null, coordinates); } /** @@ -184,13 +183,13 @@ public static Polygon fromOuterInner(@NonNull LineString outer, @Nullable Boundi coordinates.add(outer.coordinates()); // If inner rings are set to null, return early. if (inner == null) { - return new AutoValue_Polygon(TYPE, bbox, coordinates); + return new Polygon(TYPE, bbox, coordinates); } for (LineString lineString : inner) { isLinearRing(lineString); coordinates.add(lineString.coordinates()); } - return new AutoValue_Polygon(TYPE, bbox, coordinates); + return new Polygon(TYPE, bbox, coordinates); } /** @@ -215,13 +214,13 @@ public static Polygon fromOuterInner(@NonNull LineString outer, coordinates.add(outer.coordinates()); // If inner rings are set to null, return early. if (inner == null || inner.isEmpty()) { - return new AutoValue_Polygon(TYPE, null, coordinates); + return new Polygon(TYPE, null, coordinates); } for (LineString lineString : inner) { isLinearRing(lineString); coordinates.add(lineString.coordinates()); } - return new AutoValue_Polygon(TYPE, null, coordinates); + return new Polygon(TYPE, null, coordinates); } /** @@ -247,13 +246,25 @@ public static Polygon fromOuterInner(@NonNull LineString outer, @Nullable Boundi coordinates.add(outer.coordinates()); // If inner rings are set to null, return early. if (inner == null) { - return new AutoValue_Polygon(TYPE, bbox, coordinates); + return new Polygon(TYPE, bbox, coordinates); } for (LineString lineString : inner) { isLinearRing(lineString); coordinates.add(lineString.coordinates()); } - return new AutoValue_Polygon(TYPE, bbox, coordinates); + return new Polygon(TYPE, bbox, coordinates); + } + + Polygon(String type, @Nullable BoundingBox bbox, List> coordinates) { + if (type == null) { + throw new NullPointerException("Null type"); + } + this.type = type; + this.bbox = bbox; + if (coordinates == null) { + throw new NullPointerException("Null coordinates"); + } + this.coordinates = coordinates; } /** @@ -299,7 +310,9 @@ public List inner() { */ @NonNull @Override - public abstract String type(); + public String type() { + return type; + } /** * A Feature Collection might have a member named {@code bbox} to include information on the @@ -313,7 +326,9 @@ public List inner() { */ @Nullable @Override - public abstract BoundingBox bbox(); + public BoundingBox bbox() { + return bbox; + } /** * Provides the list of {@link Point}s that make up the Polygon geometry. The first list holds the @@ -325,7 +340,9 @@ public List inner() { */ @NonNull @Override - public abstract List> coordinates(); + public List> coordinates() { + return coordinates; + } /** * This takes the currently defined values found inside this instance and converts it to a GeoJson @@ -337,8 +354,7 @@ public List inner() { @Override public String toJson() { GsonBuilder gson = new GsonBuilder(); - gson.registerTypeAdapter(Point.class, new PointSerializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxSerializer()); + gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); return gson.create().toJson(this); } @@ -350,7 +366,7 @@ public String toJson() { * @since 3.0.0 */ public static TypeAdapter typeAdapter(Gson gson) { - return new AutoValue_Polygon.GsonTypeAdapter(gson); + return new Polygon.GsonTypeAdapter(gson); } /** @@ -374,4 +390,68 @@ private static boolean isLinearRing(LineString lineString) { } return true; } + + @Override + public String toString() { + return "Polygon{" + + "type=" + type + ", " + + "bbox=" + bbox + ", " + + "coordinates=" + coordinates + + "}"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Polygon) { + Polygon that = (Polygon) 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; + } + + @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; + } + + /** + * TypeAdapter for Polygon geometry. + * + * @since 4.6.0 + */ + static final class GsonTypeAdapter extends BaseGeometryTypeAdapter>> { + + GsonTypeAdapter(Gson gson) { + super(gson, new ListOfListOfPointCoordinatesTypeAdapter()); + } + + @Override + public void write(JsonWriter jsonWriter, Polygon object) throws IOException { + writeCoordinateContainer(jsonWriter, object); + } + + @Override + public Polygon read(JsonReader jsonReader) throws IOException { + return (Polygon) readCoordinateContainer(jsonReader); + } + + @Override + CoordinateContainer>> createCoordinateContainer(String type, + BoundingBox bbox, + List> coords) { + return new Polygon(type == null ? "Polygon" : type, bbox, coords); + } + } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/gson/BoundingBoxDeserializer.java b/services-geojson/src/main/java/com/mapbox/geojson/gson/BoundingBoxDeserializer.java index 40cd0ca62..c5a3c4b19 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/gson/BoundingBoxDeserializer.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/gson/BoundingBoxDeserializer.java @@ -15,7 +15,10 @@ * {@link BoundingBox} object for easier consumption in developers java applications. * * @since 3.0.0 + * @deprecated this class is deprecated, {@link com.mapbox.geojson.gson.BoundingBoxTypeAdapter} + * should be used to serialize/deserialize coordinates as Points. */ +@Deprecated public class BoundingBoxDeserializer implements JsonDeserializer { /** diff --git a/services-geojson/src/main/java/com/mapbox/geojson/gson/BoundingBoxSerializer.java b/services-geojson/src/main/java/com/mapbox/geojson/gson/BoundingBoxSerializer.java index f347d358d..8de3b875a 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/gson/BoundingBoxSerializer.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/gson/BoundingBoxSerializer.java @@ -18,7 +18,10 @@ * element which can be read by GSON and added to the final JSON output. * * @since 3.0.0 + * @deprecated this class is deprecated, {@link com.mapbox.geojson.gson.BoundingBoxTypeAdapter} + * should be used to serialize/deserialize coordinates as Points. */ +@Deprecated public class BoundingBoxSerializer implements JsonSerializer { /** diff --git a/services-geojson/src/main/java/com/mapbox/geojson/gson/BoundingBoxTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/gson/BoundingBoxTypeAdapter.java new file mode 100644 index 000000000..9939c87db --- /dev/null +++ b/services-geojson/src/main/java/com/mapbox/geojson/gson/BoundingBoxTypeAdapter.java @@ -0,0 +1,90 @@ +package com.mapbox.geojson.gson; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.mapbox.geojson.BoundingBox; +import com.mapbox.geojson.Point; +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; + +/** + * Adapter to read and write coordinates for BoundingBox class. + * + * @since 4.6.0 + */ +public class BoundingBoxTypeAdapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, BoundingBox value) throws IOException { + + if (value == null) { + out.nullValue(); + return; + } + + out.beginArray(); + + // Southwest + Point point = value.southwest(); + List unshiftedCoordinates = + CoordinateShifterManager.getCoordinateShifter().unshiftPoint(point); + + out.value(GeoJsonUtils.trim(unshiftedCoordinates.get(0))); + out.value(GeoJsonUtils.trim(unshiftedCoordinates.get(1))); + if (point.hasAltitude()) { + out.value(unshiftedCoordinates.get(2)); + } + + // Northeast + point = value.northeast(); + unshiftedCoordinates = + CoordinateShifterManager.getCoordinateShifter().unshiftPoint(point); + out.value(GeoJsonUtils.trim(unshiftedCoordinates.get(0))); + out.value(GeoJsonUtils.trim(unshiftedCoordinates.get(1))); + if (point.hasAltitude()) { + out.value(unshiftedCoordinates.get(2)); + } + + out.endArray(); + } + + @Override + public BoundingBox read(JsonReader in) throws IOException { + + List rawCoordinates = new ArrayList(); + in.beginArray(); + while (in.hasNext()) { + rawCoordinates.add(in.nextDouble()); + } + in.endArray(); + + if (rawCoordinates.size() == 6) { + return BoundingBox.fromLngLats( + rawCoordinates.get(0), + rawCoordinates.get(1), + rawCoordinates.get(2), + rawCoordinates.get(3), + rawCoordinates.get(4), + rawCoordinates.get(5)); + } + if (rawCoordinates.size() == 4) { + return BoundingBox.fromLngLats( + rawCoordinates.get(0), + rawCoordinates.get(1), + rawCoordinates.get(2), + rawCoordinates.get(3)); + } else { + throw new GeoJsonException("The value of the bbox member MUST be an array of length 2*n where" + + " n is the number of dimensions represented in the contained geometries," + + "with all axes of the most southwesterly point followed " + + " by all axes of the more northeasterly point. The " + + "axes order of a bbox follows the axes order of geometries."); + } + } +} diff --git a/services-geojson/src/main/java/com/mapbox/geojson/gson/CoordinateTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/gson/CoordinateTypeAdapter.java index 6e1bc276a..052ad07e9 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/gson/CoordinateTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/gson/CoordinateTypeAdapter.java @@ -14,7 +14,10 @@ * Adapter to read and write coordinates for Point class. * * @since 4.1.0 + * @deprecated this class is deprecated, {@link com.mapbox.geojson.PointAsCoordinatesTypeAdapter} + * should be used to serialize/deserialize coordinates as Points. */ +@Deprecated public class CoordinateTypeAdapter extends TypeAdapter> { @Override public void write(JsonWriter out, List value) throws IOException { diff --git a/services-geojson/src/main/java/com/mapbox/geojson/gson/GeoJsonAdapterFactory.java b/services-geojson/src/main/java/com/mapbox/geojson/gson/GeoJsonAdapterFactory.java index c76868ca7..6ce8a0e46 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/gson/GeoJsonAdapterFactory.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/gson/GeoJsonAdapterFactory.java @@ -1,15 +1,26 @@ package com.mapbox.geojson.gson; +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; -import com.ryanharter.auto.value.gson.GsonTypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.mapbox.geojson.BoundingBox; +import com.mapbox.geojson.Feature; +import com.mapbox.geojson.FeatureCollection; +import com.mapbox.geojson.GeometryCollection; +import com.mapbox.geojson.LineString; +import com.mapbox.geojson.MultiLineString; +import com.mapbox.geojson.MultiPoint; +import com.mapbox.geojson.MultiPolygon; +import com.mapbox.geojson.Point; +import com.mapbox.geojson.Polygon; /** - * A GeoJson type adapter factory for convenience when using AutoValue and handling - * serialization/deserialization. The majority of this class gets generated during compilation time. + * A GeoJson type adapter factory for convenience for + * serialization/deserialization. * * @since 3.0.0 */ -@GsonTypeAdapterFactory public abstract class GeoJsonAdapterFactory implements TypeAdapterFactory { /** @@ -20,6 +31,42 @@ public abstract class GeoJsonAdapterFactory implements TypeAdapterFactory { * @since 3.0.0 */ public static TypeAdapterFactory create() { - return new AutoValueGson_GeoJsonAdapterFactory(); + return new GeoJsonAdapterFactoryIml(); + } + + /** + * GeoJsonAdapterFactory implementation. + * + * @since 3.0.0 + */ + public static final class GeoJsonAdapterFactoryIml extends GeoJsonAdapterFactory { + @Override + @SuppressWarnings("unchecked") + public TypeAdapter create(Gson gson, TypeToken type) { + Class rawType = type.getRawType(); + if (BoundingBox.class.isAssignableFrom(rawType)) { + return (TypeAdapter) BoundingBox.typeAdapter(gson); + } else if (Feature.class.isAssignableFrom(rawType)) { + return (TypeAdapter) Feature.typeAdapter(gson); + } else if (FeatureCollection.class.isAssignableFrom(rawType)) { + return (TypeAdapter) FeatureCollection.typeAdapter(gson); + } else if (GeometryCollection.class.isAssignableFrom(rawType)) { + return (TypeAdapter) GeometryCollection.typeAdapter(gson); + } else if (LineString.class.isAssignableFrom(rawType)) { + return (TypeAdapter) LineString.typeAdapter(gson); + } else if (MultiLineString.class.isAssignableFrom(rawType)) { + return (TypeAdapter) MultiLineString.typeAdapter(gson); + } else if (MultiPoint.class.isAssignableFrom(rawType)) { + return (TypeAdapter) MultiPoint.typeAdapter(gson); + } else if (MultiPolygon.class.isAssignableFrom(rawType)) { + return (TypeAdapter) MultiPolygon.typeAdapter(gson); + } else if (Polygon.class.isAssignableFrom(rawType)) { + return (TypeAdapter) Polygon.typeAdapter(gson); + } else if (Point.class.isAssignableFrom(rawType)) { + return (TypeAdapter) Point.typeAdapter(gson); + } + return null; + } } } + diff --git a/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryDeserializer.java b/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryDeserializer.java index d5ad6bee7..7c4acdcbc 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryDeserializer.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryDeserializer.java @@ -13,7 +13,10 @@ * that Gson shows when trying to deserialize a list of {@link Geometry}. * * @since 1.0.0 + * @deprecated this class is deprecated, {@link com.mapbox.geojson.GeometryAdapterFactory} + * should be used to serialize/deserialize Geometries. */ +@Deprecated public class GeometryDeserializer implements JsonDeserializer { /** @@ -51,7 +54,7 @@ public Geometry deserialize(JsonElement json, Type typeOfT, JsonDeserializationC try { // Use the current context to deserialize it - Type classType = Class.forName("com.mapbox.geojson.AutoValue_" + geometryType); + Type classType = Class.forName("com.mapbox.geojson." + geometryType); return context.deserialize(json, classType); } catch (ClassNotFoundException classNotFoundException) { // Unknown geometry diff --git a/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryGeoJson.java b/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryGeoJson.java index 8705ec945..aa039c713 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryGeoJson.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryGeoJson.java @@ -3,9 +3,8 @@ import android.support.annotation.NonNull; import com.google.gson.GsonBuilder; -import com.mapbox.geojson.BoundingBox; import com.mapbox.geojson.Geometry; -import com.mapbox.geojson.Point; +import com.mapbox.geojson.GeometryAdapterFactory; /** * This is a utility class that helps create a Geometry instance from a JSON string. @@ -22,11 +21,11 @@ public class GeometryGeoJson { * @since 4.0.0 */ public static Geometry fromJson(@NonNull String json) { + GsonBuilder gson = new GsonBuilder(); gson.registerTypeAdapterFactory(GeoJsonAdapterFactory.create()); - gson.registerTypeAdapter(Point.class, new PointDeserializer()); - gson.registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()); - gson.registerTypeAdapter(Geometry.class, new GeometryDeserializer()); + gson.registerTypeAdapterFactory(GeometryAdapterFactory.create()); + return gson.create().fromJson(json, Geometry.class); } } diff --git a/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryTypeAdapter.java b/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryTypeAdapter.java index c3ae2704e..43e781ca1 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryTypeAdapter.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/gson/GeometryTypeAdapter.java @@ -13,7 +13,10 @@ * instances of {@code Geometry}. * * @since 3.0.0 + * @deprecated this class is deprecated, {@link com.mapbox.geojson.GeometryAdapterFactory} + * should be used to serialize/deserialize Geometries. */ +@Deprecated public class GeometryTypeAdapter extends TypeAdapter { diff --git a/services-geojson/src/main/java/com/mapbox/geojson/gson/PointDeserializer.java b/services-geojson/src/main/java/com/mapbox/geojson/gson/PointDeserializer.java index bb2cbfc62..8fd88ed88 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/gson/PointDeserializer.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/gson/PointDeserializer.java @@ -14,7 +14,10 @@ * "Expected BEGIN_OBJECT but was BEGIN_ARRAY". * * @since 1.0.0 + * @deprecated this class is deprecated, {@link com.mapbox.geojson.PointAsCoordinatesTypeAdapter} + * should be used to serialize/deserialize coordinates as Points. */ +@Deprecated public class PointDeserializer implements JsonDeserializer { /** diff --git a/services-geojson/src/main/java/com/mapbox/geojson/gson/PointSerializer.java b/services-geojson/src/main/java/com/mapbox/geojson/gson/PointSerializer.java index d763a7932..b19ae06b1 100644 --- a/services-geojson/src/main/java/com/mapbox/geojson/gson/PointSerializer.java +++ b/services-geojson/src/main/java/com/mapbox/geojson/gson/PointSerializer.java @@ -17,7 +17,10 @@ * double value as per JSON specification. * * @since 1.0.0 + * @deprecated this class is deprecated, {@link com.mapbox.geojson.PointAsCoordinatesTypeAdapter} + * should be used to serialize/deserialize coordinates as Points. */ +@Deprecated public class PointSerializer implements JsonSerializer { /** diff --git a/services-geojson/src/test/java/com/mapbox/geojson/FeatureTest.java b/services-geojson/src/test/java/com/mapbox/geojson/FeatureTest.java index 1af04568a..4d7eb655d 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/FeatureTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/FeatureTest.java @@ -18,7 +18,6 @@ public class FeatureTest extends TestUtils { - private static final String SAMPLE_FEATURE = "sample-feature.json"; private static final String SAMPLE_FEATURE_POINT = "sample-feature-point-all.json"; @Test @@ -48,7 +47,9 @@ public void bbox_doesNotSerializeWhenNotPresent() throws Exception { points.add(Point.fromLngLat(2.0, 3.0)); LineString lineString = LineString.fromLngLats(points); Feature feature = Feature.fromGeometry(lineString); - compareJson(feature.toJson(), + + String featureJsonString = feature.toJson(); + compareJson(featureJsonString, "{\"type\":\"Feature\",\"geometry\":{\"type\":" + "\"LineString\",\"coordinates\":[[1,2],[2,3]]}}"); } @@ -85,7 +86,7 @@ public void bbox_doesSerializeWhenPresent() throws Exception { } @Test - public void test_point_feature_fromJson() throws IOException { + public void point_feature_fromJson() throws IOException { final String json = "{ \"type\": \"Feature\"," + "\"geometry\": { \"type\": \"Point\", \"coordinates\": [ 125.6, 10.1] }," + "\"properties\": {\"name\": \"Dinagat Islands\" }}"; @@ -98,7 +99,7 @@ public void test_point_feature_fromJson() throws IOException { } @Test - public void test_linestring_feature_fromJson() throws IOException { + public void linestring_feature_fromJson() throws IOException { final String json = "{ \"type\": \"Feature\"," + "\"geometry\": { \"type\": \"LineString\", "+ " \"coordinates\": [[ 102.0, 20],[103.0, 3.0],[104.0, 4.0], [105.0, 5.0]]}," + @@ -116,7 +117,7 @@ public void test_linestring_feature_fromJson() throws IOException { } @Test - public void test_point_feature_toJson() throws IOException { + public void point_feature_toJson() throws IOException { JsonObject properties = new JsonObject(); properties.addProperty("name", "Dinagat Islands"); Feature geo = Feature.fromGeometry(Point.fromLngLat(125.6, 10.1), @@ -130,6 +131,27 @@ public void test_point_feature_toJson() throws IOException { compareJson(expectedJson, geoJsonString); } + @Test + public void linestring_feature_toJson() throws IOException { + JsonObject properties = new JsonObject(); + properties.addProperty("name", "Dinagat Islands"); + + List points = new ArrayList<>(); + points.add(Point.fromLngLat(1.0, 1.0)); + points.add(Point.fromLngLat(2.0, 2.0)); + points.add(Point.fromLngLat(3.0, 3.0)); + LineString lineString = LineString.fromLngLats(points); + + Feature geo = Feature.fromGeometry(lineString, properties); + String geoJsonString = geo.toJson(); + + String expectedJson = "{ \"type\": \"Feature\"," + + "\"geometry\": { \"type\": \"LineString\", \"coordinates\": [[1,1],[2,2],[3,3]]}," + + "\"properties\": {\"name\": \"Dinagat Islands\" }}"; + + compareJson(expectedJson, geoJsonString); + } + @Test public void testNullProperties() { List coordinates = new ArrayList<>(); @@ -163,19 +185,34 @@ public void testNonNullProperties() { @Test public void testNullPropertiesJson() { - String jsonString = "{\"type\":\"Feature\",\"bbox\":[1.0,2.0,3.0,4.0],\"geometry\":" + final String jsonString = + "{\"type\":\"Feature\"," + + " \"bbox\":[1.0,2.0,3.0,4.0]," + + " \"geometry\":" + "{\"type\":\"LineString\",\"coordinates\":[[1.0,2.0],[2.0,3.0]]}}"; + Feature feature = Feature.fromJson(jsonString); // Json( null Properties) -> Feature (empty Properties) -> Json(null Properties) String fromFeatureJsonString = feature.toJson(); - assertEquals(fromFeatureJsonString, jsonString); + compareJson(fromFeatureJsonString, jsonString); } @Test - public void test_fromJson_toJson() throws IOException { - final String jsonString = loadJsonFixture(SAMPLE_FEATURE_POINT); + public void pointFeature_fromJson_toJson() throws IOException { + final String jsonString = + "{\"id\" : \"id0\"," + + " \"bbox\": [-120.0, -60.0, 120.0, 60.0]," + + " \"geometry\": {" + + " \"bbox\": [-110.0, -50.0, 110.0, 50.0]," + + " \"coordinates\": [ 100.0, 0.0], " + + " \"type\": \"Point\"}," + + "\"type\": \"Feature\"," + + "\"properties\": {\"prop0\": \"value0\", \"prop1\": \"value1\"}" + + "}"; + + Feature featureFromJson = Feature.fromJson(jsonString); String jsonStringFromFeature = featureFromJson.toJson(); diff --git a/services-geojson/src/test/java/com/mapbox/geojson/GeoJsonTest.java b/services-geojson/src/test/java/com/mapbox/geojson/GeoJsonTest.java index 4aae71859..c8dd39fbd 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/GeoJsonTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/GeoJsonTest.java @@ -7,6 +7,8 @@ import java.util.ArrayList; import java.util.List; +import static org.junit.Assert.assertEquals; + public class GeoJsonTest extends TestUtils { private static final String GEOJSON_FIXTURE = "sample-geojson-result.json"; @@ -37,6 +39,28 @@ public void testSevenDigitRounding() throws IOException { features.add(Feature.fromGeometry(negRound)); FeatureCollection featureCollection = FeatureCollection.fromFeatures(features); - compareJson(loadJsonFixture(GEOJSON_FIXTURE), featureCollection.toJson()); + + List featureCollectionRounded = + FeatureCollection.fromJson(featureCollection.toJson()).features(); + Point roundDown2 = (Point)featureCollectionRounded.get(0).geometry(); + Point noRound2 = (Point)featureCollectionRounded.get(1).geometry(); + Point matchRound2 = (Point)featureCollectionRounded.get(2).geometry(); + Point roundLat2 = (Point)featureCollectionRounded.get(3).geometry(); + Point roundLon2 = (Point)featureCollectionRounded.get(4).geometry(); + Point largeRound2 = (Point)featureCollectionRounded.get(5).geometry(); + Point negRound2 = (Point)featureCollectionRounded.get(6).geometry(); + + assertEquals(1.1234568, roundDown2.longitude(), DELTA); + assertEquals(1.1234568, roundDown2.latitude(), DELTA); + assertEquals(noRound, noRound2); + assertEquals(matchRound, matchRound2); + assertEquals(roundLat.longitude(), roundLat2.longitude(), DELTA); + assertEquals(1.1234568, roundLat2.latitude(), DELTA); + assertEquals(1.1234568, roundLon2.longitude(), DELTA); + assertEquals(roundLon.latitude(), roundLon2.latitude(), DELTA); + assertEquals(105.1234568, largeRound2.longitude(), DELTA); + assertEquals(largeRound.latitude(), largeRound2.latitude(), DELTA); + assertEquals(-105.1234568, negRound2.longitude(), DELTA); + assertEquals(negRound.latitude(), negRound2.latitude(), DELTA); } } diff --git a/services-geojson/src/test/java/com/mapbox/geojson/GeometryCollectionTest.java b/services-geojson/src/test/java/com/mapbox/geojson/GeometryCollectionTest.java index 42955c5be..2221a8e3a 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/GeometryCollectionTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/GeometryCollectionTest.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class GeometryCollectionTest extends TestUtils { @@ -124,7 +125,16 @@ public void testSerializable() throws Exception { @Test public void fromJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_GEOMETRYCOLLECTION); + final String json = + " { \"type\": \"GeometryCollection\"," + + " \"bbox\": [120, 40, -120, -40]," + + " \"geometries\": [" + + " { \"type\": \"Point\"," + + " \"bbox\": [110, 30, -110, -30]," + + " \"coordinates\": [100, 0]}," + + " { \"type\": \"LineString\"," + + " \"bbox\": [110, 30, -110, -30]," + + " \"coordinates\": [[101, 0], [102, 1]]}]}"; GeometryCollection geo = GeometryCollection.fromJson(json); assertEquals(geo.type(), "GeometryCollection"); assertEquals(geo.geometries().get(0).type(), "Point"); @@ -133,8 +143,31 @@ public void fromJson() throws IOException { @Test public void toJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_GEOMETRYCOLLECTION); - GeometryCollection geo = GeometryCollection.fromJson(json); - compareJson(json, geo.toJson()); + final String jsonOriginal = + " { \"type\": \"GeometryCollection\"," + + " \"bbox\": [-120, -40, 120, 40]," + + " \"geometries\": [" + + " { \"type\": \"Point\"," + + " \"bbox\": [-110, -30, 110, 30]," + + " \"coordinates\": [100, 0]}," + + " { \"type\": \"LineString\"," + + " \"bbox\": [-110, -30, 110, 30]," + + " \"coordinates\": [[101, 0], [102, 1]]}]}"; + + List geometries = new ArrayList<>(2); + geometries.add(Point.fromLngLat(100, 0, + BoundingBox.fromLngLats(-110, -30,110, 30))); + geometries.add(LineString.fromLngLats( + Arrays.asList(Point.fromLngLat(101, 0), + Point.fromLngLat(102, 1)), + BoundingBox.fromLngLats(-110, -30,110, 30))); + + GeometryCollection geometryCollection = + GeometryCollection.fromGeometries(geometries, + BoundingBox.fromLngLats(-120, -40,120, 40)); + + String jsonString = geometryCollection.toJson(); + compareJson(jsonOriginal, jsonString); + } } diff --git a/services-geojson/src/test/java/com/mapbox/geojson/GeometryTest.java b/services-geojson/src/test/java/com/mapbox/geojson/GeometryTest.java index f4e8bfc12..3171f3a51 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/GeometryTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/GeometryTest.java @@ -6,8 +6,10 @@ import org.junit.Test; import java.io.IOException; +import java.util.Arrays; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; public class GeometryTest extends TestUtils { @@ -15,8 +17,77 @@ public class GeometryTest extends TestUtils { @Test public void fromJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_GEOMETRY_COLLECTION); + final String json = + " { \"type\": \"GeometryCollection\"," + + " \"bbox\": [120, 40, -120, -40]," + + " \"geometries\": [" + + " { \"type\": \"Point\"," + + " \"bbox\": [110, 30, -110, -30]," + + " \"coordinates\": [100, 0]}," + + " { \"type\": \"LineString\"," + + " \"bbox\": [110, 30, -110, -30]," + + " \"coordinates\": [[101, 0], [102, 1]]}]}"; Geometry geo = GeometryGeoJson.fromJson(json); assertEquals(geo.type(), "GeometryCollection"); } + + @Test + public void pointFromJson() throws IOException { + Geometry geometry = GeometryGeoJson.fromJson("{\"coordinates\": [2,3]," + + "\"type\":\"Point\",\"bbox\":[1.0,2.0,3.0,4.0]}"); + + assertNotNull(geometry); + assertNotNull(geometry.bbox()); + assertEquals(1.0, geometry.bbox().southwest().longitude(), DELTA); + assertEquals(2.0, geometry.bbox().southwest().latitude(), DELTA); + assertEquals(3.0, geometry.bbox().northeast().longitude(), DELTA); + assertEquals(4.0, geometry.bbox().northeast().latitude(), DELTA); + assertNotNull(((Point)geometry).coordinates()); + assertEquals(2, ((Point)geometry).longitude(), DELTA); + assertEquals(3, ((Point)geometry).latitude(), DELTA); + } + + @Test + public void pointToJson() throws IOException { + Geometry geometry = Point.fromLngLat(2, 3, + BoundingBox.fromLngLats(1, 2, 3, 4)); + String pointStr = geometry.toJson(); + compareJson("{\"coordinates\": [2,3]," + + "\"type\":\"Point\",\"bbox\":[1.0,2.0,3.0,4.0]}", + pointStr); + } + + @Test + public void lineStringFromJson() throws Exception { + Geometry lineString = GeometryGeoJson.fromJson("{\"coordinates\":[[1,2],[2,3],[3,4]]," + + "\"type\":\"LineString\",\"bbox\":[1.0,2.0,3.0,4.0]}"); + + assertNotNull(lineString); + assertNotNull(lineString.bbox()); + assertEquals(1.0, lineString.bbox().southwest().longitude(), DELTA); + 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)lineString).coordinates()); + assertEquals(1, ((LineString)lineString).coordinates().get(0).longitude(), DELTA); + assertEquals(2, ((LineString)lineString).coordinates().get(0).latitude(), DELTA); + assertEquals(2, ((LineString)lineString).coordinates().get(1).longitude(), DELTA); + assertEquals(3, ((LineString)lineString).coordinates().get(1).latitude(), DELTA); + assertEquals(3, ((LineString)lineString).coordinates().get(2).longitude(), DELTA); + assertEquals(4, ((LineString)lineString).coordinates().get(2).latitude(), DELTA); + } + + @Test + public void lineStringToJson() throws Exception { + + Geometry geometry = LineString.fromLngLats( + Arrays.asList(Point.fromLngLat(1, 2), + Point.fromLngLat(2, 3), + Point.fromLngLat(3, 4)), + BoundingBox.fromLngLats(1, 2, 3, 4)); + String geometryJsonStr = geometry.toJson(); + String expectedJsonString = "{\"coordinates\":[[1,2],[2,3],[3,4]]," + + "\"type\":\"LineString\",\"bbox\":[1.0,2.0,3.0,4.0]}"; + compareJson(expectedJsonString, geometryJsonStr); + } } 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 bd86428de..ad09c8618 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/LineStringTest.java @@ -93,6 +93,26 @@ public void bbox_doesSerializeWhenPresent() throws Exception { lineStringJson); } + @Test + public void bbox_doesDeserializeWhenPresent() throws Exception { + LineString lineString = LineString.fromJson("{\"coordinates\":[[1,2],[2,3],[3,4]]," + + "\"type\":\"LineString\",\"bbox\":[1.0,2.0,3.0,4.0]}"); + + assertNotNull(lineString); + assertNotNull(lineString.bbox()); + assertEquals(1.0, lineString.bbox().southwest().longitude(), DELTA); + 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); + } + @Test public void testSerializable() throws Exception { List points = new ArrayList<>(); @@ -107,7 +127,8 @@ public void testSerializable() throws Exception { @Test public void fromJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_LINESTRING_FIXTURE); + final String json = "{\"type\": \"LineString\"," + + " \"coordinates\": [[ 100, 0], [101, 1]]} "; LineString geo = LineString.fromJson(json); assertEquals(geo.type(), "LineString"); assertEquals(geo.coordinates().get(0).longitude(), 100.0, 0.0); @@ -117,9 +138,11 @@ public void fromJson() throws IOException { @Test public void toJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_LINESTRING_FIXTURE); + final String json = "{\"type\": \"LineString\"," + + " \"coordinates\": [[ 100, 0], [101, 1]]} "; LineString geo = LineString.fromJson(json); - compareJson(json, geo.toJson()); + String geoJsonString = geo.toJson(); + compareJson(geoJsonString, json); } @Test diff --git a/services-geojson/src/test/java/com/mapbox/geojson/MultiLineStringTest.java b/services-geojson/src/test/java/com/mapbox/geojson/MultiLineStringTest.java index fb4e4ec7a..0c20169fa 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/MultiLineStringTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/MultiLineStringTest.java @@ -125,7 +125,9 @@ public void testSerializable() throws Exception { @Test public void fromJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_MULTILINESTRING); + final String json = "{\"type\": \"MultiLineString\", " + + "\"coordinates\": [[[100, 0],[101, 1]],[[102, 2],[103, 3]]] }"; + MultiLineString geo = MultiLineString.fromJson(json); assertEquals("MultiLineString", geo.type()); assertEquals(geo.coordinates().get(0).get(0).longitude(), 100.0, DELTA); @@ -135,7 +137,8 @@ public void fromJson() throws IOException { @Test public void toJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_MULTILINESTRING); + final String json = "{\"type\": \"MultiLineString\", " + + "\"coordinates\": [[[100, 0],[101, 1]],[[102, 2],[103, 3]]] }"; MultiLineString geo = MultiLineString.fromJson(json); compareJson(json, geo.toJson()); } 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 e6f0b7782..f7dc684c9 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/MultiPointTest.java @@ -94,7 +94,8 @@ public void testSerializable() throws Exception { @Test public void fromJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_MULTIPOINT); + final String json = "{ \"type\": \"MultiPoint\"," + + "\"coordinates\": [ [100, 0], [101, 1] ] } "; MultiPoint geo = MultiPoint.fromJson(json); assertEquals(geo.type(), "MultiPoint"); assertEquals(geo.coordinates().get(0).longitude(), 100.0, DELTA); @@ -107,7 +108,8 @@ public void fromJson() throws IOException { @Test public void toJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_MULTIPOINT); + final String json = "{ \"type\": \"MultiPoint\"," + + "\"coordinates\": [ [100, 0], [101, 1] ] } "; MultiPoint geo = MultiPoint.fromJson(json); compareJson(json, geo.toJson()); } diff --git a/services-geojson/src/test/java/com/mapbox/geojson/MultiPolygonTest.java b/services-geojson/src/test/java/com/mapbox/geojson/MultiPolygonTest.java index 57dca1e2c..56661b7d8 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/MultiPolygonTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/MultiPolygonTest.java @@ -142,7 +142,10 @@ public void testSerializable() throws Exception { @Test public void fromJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_MULTIPOLYGON); + final String json = "{\"type\":\"MultiPolygon\",\"coordinates\": " + + " [[[[102, 2], [103, 2], [103, 3], [102, 3], [102, 2]]]," + + " [[[100, 0], [101, 0], [101, 1], [100, 1], [100, 0]]," + + " [[100.2, 0.2], [100.2, 0.8], [100.8, 0.8], [100.8, 0.2], [100.2, 0.2]]]]}"; MultiPolygon geo = MultiPolygon.fromJson(json); assertEquals(geo.type(), "MultiPolygon"); assertEquals(geo.coordinates().get(0).get(0).get(0).longitude(), 102.0, DELTA); @@ -152,7 +155,11 @@ public void fromJson() throws IOException { @Test public void toJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_MULTIPOLYGON); + final String json = "{\"type\":\"MultiPolygon\",\"coordinates\": " + + " [[[[102, 2], [103, 2], [103, 3], [102, 3], [102, 2]]]," + + " [[[100, 0], [101, 0], [101, 1], [100, 1], [100, 0]]," + + " [[100.2, 0.2], [100.2, 0.8], [100.8, 0.8], [100.8, 0.2], [100.2, 0.2]]]]}"; + MultiPolygon geo = MultiPolygon.fromJson(json); compareJson(json, geo.toJson()); } 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 12aff4315..f6745693a 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/PointTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/PointTest.java @@ -13,7 +13,9 @@ import org.junit.rules.ExpectedException; import java.io.IOException; +import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class PointTest extends TestUtils { @@ -97,15 +99,27 @@ public void bbox_returnsCorrectBbox() throws Exception { @Test public void bbox_doesSerializeWhenPresent() throws Exception { - List points = new ArrayList<>(); - points.add(Point.fromLngLat(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); - LineString lineString = LineString.fromLngLats(points, bbox); - compareJson(lineString.toJson(), - "{\"coordinates\":[[1,1],[2,2],[3,3]]," - + "\"type\":\"LineString\",\"bbox\":[1.0,2.0,3.0,4.0]}"); + Point point = Point.fromLngLat(2.0, 2.0, bbox); + compareJson(point.toJson(), + "{\"coordinates\": [2,2]," + + "\"type\":\"Point\",\"bbox\":[1.0,2.0,3.0,4.0]}"); + } + + @Test + public void bbox_doesDeserializeWhenPresent() throws Exception { + Point point = Point.fromJson("{\"coordinates\": [2,3]," + + "\"type\":\"Point\",\"bbox\":[1.0,2.0,3.0,4.0]}"); + + assertNotNull(point); + assertNotNull(point.bbox()); + assertEquals(1.0, point.bbox().southwest().longitude(), DELTA); + assertEquals(2.0, point.bbox().southwest().latitude(), DELTA); + assertEquals(3.0, point.bbox().northeast().longitude(), DELTA); + assertEquals(4.0, point.bbox().northeast().latitude(), DELTA); + assertNotNull(point.coordinates()); + assertEquals(2, point.longitude(), DELTA); + assertEquals(3, point.latitude(), DELTA); } @Test @@ -122,7 +136,8 @@ public void testSerializable() throws Exception { @Test public void fromJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_POINT); + final String json = + "{ \"type\": \"Point\", \"coordinates\": [ 100, 0] }"; Point geo = Point.fromJson(json); assertEquals(geo.type(), "Point"); assertEquals(geo.longitude(), 100.0, DELTA); @@ -136,7 +151,8 @@ public void fromJson() throws IOException { @Test public void toJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_POINT); + final String json = + "{ \"type\": \"Point\", \"coordinates\": [ 100, 0] }"; Point geo = Point.fromJson(json); compareJson(json, geo.toJson()); } diff --git a/services-geojson/src/test/java/com/mapbox/geojson/PolygonTest.java b/services-geojson/src/test/java/com/mapbox/geojson/PolygonTest.java index c8d018f8c..f99e97fa1 100644 --- a/services-geojson/src/test/java/com/mapbox/geojson/PolygonTest.java +++ b/services-geojson/src/test/java/com/mapbox/geojson/PolygonTest.java @@ -230,7 +230,8 @@ public void testSerializable() throws Exception { @Test public void fromJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_POLYGON); + final String json = "{\"type\": \"Polygon\", " + + "\"coordinates\": [[[100, 0], [101, 0], [101, 1], [100, 1],[100, 0]]]}"; Polygon geo = Polygon.fromJson(json); assertEquals("Polygon", geo.type()); assertEquals(100.0, geo.coordinates().get(0).get(0).longitude(), DELTA); @@ -240,7 +241,9 @@ public void fromJson() throws IOException { @Test public void fromJsonHoles() throws IOException { - final String json = loadJsonFixture(SAMPLE_POLYGON_HOLES); + final String json = "{\"type\": \"Polygon\", " + + "\"coordinates\": [[[100, 0], [101, 0], [101, 1], [100, 1],[100, 0]], " + + " [[100.8, 0.8],[100.8, 0.2],[100.2, 0.2],[100.2, 0.8],[100.8, 0.8]]]}"; Polygon geo = Polygon.fromJson(json); assertEquals("Polygon", geo.type()); assertEquals(100.0, geo.coordinates().get(0).get(0).longitude(), DELTA); @@ -253,14 +256,17 @@ public void fromJsonHoles() throws IOException { @Test public void toJson() throws IOException { - final String json = loadJsonFixture(SAMPLE_POLYGON); + final String json = "{\"type\": \"Polygon\", " + + "\"coordinates\": [[[100, 0], [101, 0], [101, 1], [100, 1],[100, 0]]]}"; Polygon geo = Polygon.fromJson(json); compareJson(json, geo.toJson()); } @Test public void toJsonHoles() throws IOException { - final String json = loadJsonFixture(SAMPLE_POLYGON_HOLES); + final String json = "{\"type\": \"Polygon\", " + + "\"coordinates\": [[[100, 0], [101, 0], [101, 1], [100, 1],[100, 0]], " + + " [[100.8, 0.8],[100.8, 0.2],[100.2, 0.2],[100.2, 0.8],[100.8, 0.8]]]}"; Polygon geo = Polygon.fromJson(json); compareJson(json, geo.toJson()); } diff --git a/services-tilequery/src/main/java/com/mapbox/api/tilequery/MapboxTilequery.java b/services-tilequery/src/main/java/com/mapbox/api/tilequery/MapboxTilequery.java index 8687f1a6c..fe666b9d7 100644 --- a/services-tilequery/src/main/java/com/mapbox/api/tilequery/MapboxTilequery.java +++ b/services-tilequery/src/main/java/com/mapbox/api/tilequery/MapboxTilequery.java @@ -10,14 +10,10 @@ import com.mapbox.core.exceptions.ServicesException; import com.mapbox.core.utils.MapboxUtils; import com.mapbox.core.utils.TextUtils; -import com.mapbox.geojson.BoundingBox; import com.mapbox.geojson.FeatureCollection; -import com.mapbox.geojson.Geometry; +import com.mapbox.geojson.GeometryAdapterFactory; import com.mapbox.geojson.Point; -import com.mapbox.geojson.gson.BoundingBoxDeserializer; import com.mapbox.geojson.gson.GeoJsonAdapterFactory; -import com.mapbox.geojson.gson.GeometryDeserializer; -import com.mapbox.geojson.gson.PointDeserializer; import java.io.IOException; import java.util.List; @@ -47,11 +43,10 @@ protected MapboxTilequery() { @Override protected GsonBuilder getGsonBuilder() { + return new GsonBuilder() .registerTypeAdapterFactory(GeoJsonAdapterFactory.create()) - .registerTypeAdapter(Point.class, new PointDeserializer()) - .registerTypeAdapter(BoundingBox.class, new BoundingBoxDeserializer()) - .registerTypeAdapter(Geometry.class, new GeometryDeserializer()); + .registerTypeAdapterFactory(GeometryAdapterFactory.create()); } @Override