Work on GPX polygons clean up
[gpl/argeo-suite.git] / org.argeo.app.geo / src / org / argeo / app / geo / GeoJson.java
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GeoJson.java b/org.argeo.app.geo/src/org/argeo/app/geo/GeoJson.java
new file mode 100644 (file)
index 0000000..c1ba1d3
--- /dev/null
@@ -0,0 +1,170 @@
+package org.argeo.app.geo;
+
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+
+import jakarta.json.JsonArray;
+import jakarta.json.JsonObject;
+import jakarta.json.stream.JsonGenerator;
+
+/**
+ * GeoJSon format.
+ * 
+ * @see https://datatracker.ietf.org/doc/html/rfc7946
+ */
+public class GeoJson {
+       public final static String POINT_TYPE = "Point";
+       public final static String MULTI_POINT_TYPE = "MultiPoint";
+       public final static String LINE_STRING_TYPE = "LineString";
+       public final static String POLYGON_TYPE = "Polygon";
+       public final static String GEOMETRY_COLLECTION_TYPE = "GeometryCollection";
+
+       public final static String TYPE = "type";
+       public final static String GEOMETRY = "geometry";
+       public final static String GEOMETRIES = "geometries";
+       public final static String COORDINATES = "coordinates";
+       public final static String BBOX = "bbox";
+       public final static String PROPERTIES = "properties";
+
+       /*
+        * WRITE
+        */
+       /** Writes a {@link Geometry} as GeoJSON. */
+       public static void writeGeometry(JsonGenerator g, Geometry geometry) {
+               if (geometry instanceof Point point) {
+                       g.write(TYPE, POINT_TYPE);
+                       g.writeStartArray(COORDINATES);
+                       writeCoordinate(g, point.getCoordinate());
+                       g.writeEnd();// coordinates array
+               } else if (geometry instanceof MultiPoint multiPoint) {
+                       g.write(TYPE, MULTI_POINT_TYPE);
+                       g.writeStartArray(COORDINATES);
+                       writeCoordinates(g, multiPoint.getCoordinates());
+                       g.writeEnd();// coordinates array
+               } else if (geometry instanceof LineString lineString) {
+                       g.write(TYPE, LINE_STRING_TYPE);
+                       g.writeStartArray(COORDINATES);
+                       writeCoordinates(g, lineString.getCoordinates());
+                       g.writeEnd();// coordinates array
+               } else if (geometry instanceof Polygon polygon) {
+                       g.write(TYPE, POLYGON_TYPE);
+                       g.writeStartArray(COORDINATES);
+                       LinearRing exteriorRing = polygon.getExteriorRing();
+                       g.writeStartArray();
+                       writeCoordinates(g, exteriorRing.getCoordinates());
+                       g.writeEnd();
+                       for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
+                               LinearRing interiorRing = polygon.getInteriorRingN(i);
+                               // TODO verify that holes are clockwise
+                               g.writeStartArray();
+                               writeCoordinates(g, interiorRing.getCoordinates());
+                               g.writeEnd();
+                       }
+                       g.writeEnd();// coordinates array
+               } else if (geometry instanceof GeometryCollection geometryCollection) {// must be last
+                       g.write(TYPE, GEOMETRY_COLLECTION_TYPE);
+                       g.writeStartArray(GEOMETRIES);
+                       for (int i = 0; i < geometryCollection.getNumGeometries(); i++) {
+                               g.writeStartObject();
+                               writeGeometry(g, geometryCollection.getGeometryN(i));
+                               g.writeEnd();// geometry object
+                       }
+                       g.writeEnd();// geometries array
+               } else {
+                       throw new IllegalArgumentException(geometry.getClass() + " is not supported.");
+               }
+       }
+
+       /** Writes a sequence of coordinates [[lat,lon],[lat,lon]] */
+       public static void writeCoordinates(JsonGenerator g, Coordinate[] coordinates) {
+               for (Coordinate coordinate : coordinates) {
+                       g.writeStartArray();
+                       writeCoordinate(g, coordinate);
+                       g.writeEnd();
+               }
+       }
+
+       /** Writes a pair of coordinates [lat,lon]. */
+       public static void writeCoordinate(JsonGenerator g, Coordinate coordinate) {
+               g.write(coordinate.getX());
+               g.write(coordinate.getY());
+               double z = coordinate.getZ();
+               if (!Double.isNaN(z)) {
+                       g.write(z);
+               }
+       }
+
+       /**
+        * Writes the {@link Envelope} of a {@link Geometry} as a bbox GeoJSON object.
+        */
+       public static void writeBBox(JsonGenerator g, Geometry geometry) {
+               g.writeStartArray(BBOX);
+               Envelope envelope = geometry.getEnvelopeInternal();
+               g.write(envelope.getMinX());
+               g.write(envelope.getMinY());
+               g.write(envelope.getMaxX());
+               g.write(envelope.getMaxY());
+               g.writeEnd();
+       }
+
+       /*
+        * READ
+        */
+       /** Reads a geometry from the geometry object of a GEoJSON feature. */
+       @SuppressWarnings("unchecked")
+       public static <T extends Geometry> T readGeometry(JsonObject geom, Class<T> clss) {
+               String type = geom.getString(TYPE);
+               JsonArray coordinates = geom.getJsonArray(COORDINATES);
+               Geometry res = switch (type) {
+               case POINT_TYPE: {
+                       Coordinate coord = readCoordinate(coordinates);
+                       yield JTS.GEOMETRY_FACTORY_WGS84.createPoint(coord);
+               }
+               case LINE_STRING_TYPE: {
+                       Coordinate[] coords = readCoordinates(coordinates);
+                       yield JTS.GEOMETRY_FACTORY_WGS84.createLineString(coords);
+               }
+               case POLYGON_TYPE: {
+                       assert coordinates.size() > 0;
+                       LinearRing exterior = JTS.GEOMETRY_FACTORY_WGS84
+                                       .createLinearRing(readCoordinates(coordinates.getJsonArray(0)));
+                       LinearRing[] holes = new LinearRing[coordinates.size() - 1];
+                       for (int i = 0; i < coordinates.size() - 1; i++) {
+                               holes[i] = JTS.GEOMETRY_FACTORY_WGS84
+                                               .createLinearRing(readCoordinates(coordinates.getJsonArray(i + 1)));
+                       }
+                       yield JTS.GEOMETRY_FACTORY_WGS84.createPolygon(exterior, holes);
+               }
+               default:
+                       throw new IllegalArgumentException("Unexpected value: " + type);
+               };
+//             res.normalize();
+               return (T) res;
+       }
+
+       /** Reads a coordinate sequence [[lat,lon],[lat,lon]]. */
+       public static Coordinate readCoordinate(JsonArray arr) {
+               assert arr.size() >= 2;
+               return new Coordinate(arr.getJsonNumber(0).doubleValue(), arr.getJsonNumber(1).doubleValue());
+       }
+
+       /** Reads a coordinate pair [lat,lon]. */
+       public static Coordinate[] readCoordinates(JsonArray arr) {
+               Coordinate[] coords = new Coordinate[arr.size()];
+               for (int i = 0; i < arr.size(); i++)
+                       coords[i] = readCoordinate(arr.getJsonArray(i));
+               return coords;
+       }
+
+       /** singleton */
+       private GeoJson() {
+       }
+
+}