From 8b7dc8398a17d09c5e20857b4e1e5f82ad28e769 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Fri, 29 Sep 2023 18:19:17 +0200 Subject: [PATCH] Work on GeoJSon ans JTS support --- .../src/org/argeo/app/geo/GeoJSon.java | 102 +++++++++++++++--- .../src/org/argeo/app/geo/JTS.java | 8 +- .../argeo/app/geo/http/WfsHttpHandler.java | 4 +- 3 files changed, 95 insertions(+), 19 deletions(-) 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 index b590814..5d5b99f 100644 --- a/org.argeo.app.geo/src/org/argeo/app/geo/GeoJSon.java +++ b/org.argeo.app.geo/src/org/argeo/app/geo/GeoJSon.java @@ -8,6 +8,8 @@ import org.locationtech.jts.geom.LinearRing; 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; /** @@ -16,31 +18,34 @@ import jakarta.json.stream.JsonGenerator; * @see https://datatracker.ietf.org/doc/html/rfc7946 */ public class GeoJSon { - public static void writeBBox(JsonGenerator generator, Geometry geometry) { - generator.writeStartArray("bbox"); - Envelope envelope = geometry.getEnvelopeInternal(); - generator.write(envelope.getMinX()); - generator.write(envelope.getMinY()); - generator.write(envelope.getMaxX()); - generator.write(envelope.getMaxY()); - generator.writeEnd(); - } + public final static String POINT_TYPE = "Point"; + public final static String LINE_STRING_TYPE = "LineString"; + public final static String POLYGON_TYPE = "Polygon"; + + public final static String TYPE = "type"; + public final static String GEOMETRY = "geometry"; + 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 generator, Geometry geometry) { - generator.writeStartObject("geometry"); if (geometry instanceof Point point) { - generator.write("type", "Point"); - generator.writeStartArray("coordinates"); + generator.write(TYPE, POINT_TYPE); + generator.writeStartArray(COORDINATES); writeCoordinate(generator, point.getCoordinate()); generator.writeEnd();// coordinates array } else if (geometry instanceof LineString lineString) { - generator.write("type", "LineString"); - generator.writeStartArray("coordinates"); + generator.write(TYPE, LINE_STRING_TYPE); + generator.writeStartArray(COORDINATES); writeCoordinates(generator, lineString.getCoordinates()); generator.writeEnd();// coordinates array } else if (geometry instanceof Polygon polygon) { - generator.write("type", "Polygon"); - generator.writeStartArray("coordinates"); + generator.write(TYPE, POLYGON_TYPE); + generator.writeStartArray(COORDINATES); LinearRing exteriorRing = polygon.getExteriorRing(); generator.writeStartArray(); writeCoordinates(generator, exteriorRing.getCoordinates()); @@ -54,9 +59,9 @@ public class GeoJSon { } generator.writeEnd();// coordinates array } - generator.writeEnd();// geometry object } + /** Writes a sequence of coordinates [[lat,lon],[lat,lon]] */ public static void writeCoordinates(JsonGenerator generator, Coordinate[] coordinates) { for (Coordinate coordinate : coordinates) { generator.writeStartArray(); @@ -65,6 +70,7 @@ public class GeoJSon { } } + /** Writes a pair of coordinates [lat,lon]. */ public static void writeCoordinate(JsonGenerator generator, Coordinate coordinate) { generator.write(coordinate.getX()); generator.write(coordinate.getY()); @@ -74,6 +80,68 @@ public class GeoJSon { } } + /** + * Writes the {@link Envelope} of a {@link Geometry} as a bbox GeoJSON object. + */ + public static void writeBBox(JsonGenerator generator, Geometry geometry) { + generator.writeStartArray(BBOX); + Envelope envelope = geometry.getEnvelopeInternal(); + generator.write(envelope.getMinX()); + generator.write(envelope.getMinY()); + generator.write(envelope.getMaxX()); + generator.write(envelope.getMaxY()); + generator.writeEnd(); + } + + /* + * READ + */ + /** Reads a geometry from the geometry object of a GEoJSON feature. */ + @SuppressWarnings("unchecked") + public static T readGeometry(JsonObject geom, Class 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() { } diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/JTS.java b/org.argeo.app.geo/src/org/argeo/app/geo/JTS.java index 2623712..ba7ecf9 100644 --- a/org.argeo.app.geo/src/org/argeo/app/geo/JTS.java +++ b/org.argeo.app.geo/src/org/argeo/app/geo/JTS.java @@ -2,6 +2,7 @@ package org.argeo.app.geo; import org.argeo.api.cms.CmsLog; import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.PrecisionModel; /** * Factories initialisation and workarounds for the JTS library. The idea is to @@ -12,12 +13,17 @@ import org.locationtech.jts.geom.GeometryFactory; public class JTS { private final static CmsLog log = CmsLog.getLog(JTS.class); + public final static int WGS84_SRID = 4326; + + /** A geometry factory with no SRID specified */ public final static GeometryFactory GEOMETRY_FACTORY; + /** A geometry factory with SRID 4326 (WGS84 in the EPSG database) */ + public final static GeometryFactory GEOMETRY_FACTORY_WGS84; static { try { - // GEOMETRY_FACTORY = JTSFactoryFinder.getGeometryFactory(); GEOMETRY_FACTORY = new GeometryFactory(); + GEOMETRY_FACTORY_WGS84 = new GeometryFactory(new PrecisionModel(), WGS84_SRID); } catch (RuntimeException e) { log.error("Basic JTS initialisation failed, geographical utilities are probably not available", e); throw e; diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java b/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java index ae2940c..4ab3654 100644 --- a/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java +++ b/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java @@ -202,9 +202,11 @@ public class WfsHttpHandler implements HttpHandler { if (featureId != null) generator.write("id", featureId); GeoJSon.writeBBox(generator, defaultGeometry); + generator.writeStartObject(GeoJSon.GEOMETRY); GeoJSon.writeGeometry(generator, defaultGeometry); + generator.writeEnd();// geometry object - generator.writeStartObject("properties"); + generator.writeStartObject(GeoJSon.PROPERTIES); AcrJsonUtils.writeTimeProperties(generator, c); if (featureAdapter != null) featureAdapter.writeProperties(generator, c, typeName); -- 2.30.2