From 1eaec4a7690f29973a712a9000c010a37f725ea1 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Wed, 4 Oct 2023 09:05:20 +0200 Subject: [PATCH] Introduce GeoEntityUtils --- .../src/org/argeo/app/api/EntityName.java | 6 + .../src/org/argeo/app/api/EntityType.java | 4 +- .../src/org/argeo/app/api/entity.cnd | 9 ++ .../org/argeo/app/geo/acr/GeoEntityUtils.java | 125 ++++++++++++++++++ 4 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 org.argeo.app.geo/src/org/argeo/app/geo/acr/GeoEntityUtils.java diff --git a/org.argeo.app.api/src/org/argeo/app/api/EntityName.java b/org.argeo.app.api/src/org/argeo/app/api/EntityName.java index 308f459..f890632 100644 --- a/org.argeo.app.api/src/org/argeo/app/api/EntityName.java +++ b/org.argeo.app.api/src/org/argeo/app/api/EntityName.java @@ -2,8 +2,14 @@ package org.argeo.app.api; import org.argeo.api.acr.QNamed; +/** Names used in the entity namespace http://www.argeo.org/ns/entity. */ public enum EntityName implements QNamed { type, // + // geography + minLat, minLon, maxLat, maxLon, + // geo entities + place, + // ; @Override diff --git a/org.argeo.app.api/src/org/argeo/app/api/EntityType.java b/org.argeo.app.api/src/org/argeo/app/api/EntityType.java index 1e30821..8a258eb 100644 --- a/org.argeo.app.api/src/org/argeo/app/api/EntityType.java +++ b/org.argeo.app.api/src/org/argeo/app/api/EntityType.java @@ -2,7 +2,7 @@ package org.argeo.app.api; import org.argeo.api.acr.QNamed; -/** Types related to entities. */ +/** Types used in the entity namespace http://www.argeo.org/ns/entity. */ public enum EntityType implements QNamed { // entity entity, local, relatedTo, @@ -15,7 +15,7 @@ public enum EntityType implements QNamed { // graphics box, // geography - geopoint, bearing, + geopoint, bearing, geobounded, // ldap person, user; diff --git a/org.argeo.app.api/src/org/argeo/app/api/entity.cnd b/org.argeo.app.api/src/org/argeo/app/api/entity.cnd index 396b6f2..2ca640b 100644 --- a/org.argeo.app.api/src/org/argeo/app/api/entity.cnd +++ b/org.argeo.app.api/src/org/argeo/app/api/entity.cnd @@ -115,3 +115,12 @@ mixin [entity:bearing] mixin - svg:direction (DOUBLE) + +[entity:geobounded] +mixin +- entity:minLat (DOUBLE) m +- entity:minLon (DOUBLE) m +- entity:maxLat (DOUBLE) m +- entity:maxLon (DOUBLE) m +- entity:minAlt (DOUBLE) +- entity:maxAlt (DOUBLE) diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/acr/GeoEntityUtils.java b/org.argeo.app.geo/src/org/argeo/app/geo/acr/GeoEntityUtils.java new file mode 100644 index 0000000..6019ee9 --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/geo/acr/GeoEntityUtils.java @@ -0,0 +1,125 @@ +package org.argeo.app.geo.acr; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentName; +import org.argeo.api.acr.DName; +import org.argeo.api.acr.QNamed; +import org.argeo.app.api.EntityName; +import org.argeo.app.api.EntityType; +import org.argeo.app.api.WGS84PosName; +import org.argeo.app.geo.GeoJson; +import org.argeo.app.geo.JTS; +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.Point; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; +import jakarta.json.stream.JsonGenerator; + +/** Utilities around entity types related to geography. */ +public class GeoEntityUtils { + public static final String PLACE_GEOM_JSON = "place.geom.json"; + public static final String _GEOM_JSON = ".geom.json"; + + public static void putGeometry(Content c, QNamed name, Geometry geometry) { + putGeometry(c, name.qName(), geometry); + } + + public static void putGeometry(Content c, QName name, Geometry geometry) { + QName jsonFileName = new ContentName(name.getNamespaceURI(), name.getLocalPart() + _GEOM_JSON); + Content geom = c.soleChild(jsonFileName).orElseGet( + () -> c.add(jsonFileName, Collections.singletonMap(DName.getcontenttype.qName(), "application/json"))); + try (OutputStream out = geom.open(OutputStream.class)) { + JsonGenerator g = Json.createGenerator(out); + g.writeStartObject(); + GeoJson.writeGeometry(g, geometry); + g.writeEnd(); + g.close(); + } catch (IOException e) { + throw new UncheckedIOException("Cannot add geometry " + name + " to " + c, e); + } + updateBoundingBox(c); + } + + public static T getGeometry(Content c, QNamed name, Class clss) { + return getGeometry(c, name.qName(), clss); + } + + public static T getGeometry(Content c, QName name, Class clss) { + QName jsonFileName = new ContentName(name.getNamespaceURI(), name.getLocalPart() + _GEOM_JSON); + Content geom = c.soleChild(jsonFileName).orElse(null); + if (geom == null) + return null; + try (Reader in = new InputStreamReader(geom.open(InputStream.class), StandardCharsets.UTF_8)) { + JsonReader jsonReader = Json.createReader(in); + JsonObject jsonObject = jsonReader.readObject(); + T readGeom = GeoJson.readGeometry(jsonObject, clss); + return readGeom; + } catch (IOException e) { + throw new UncheckedIOException("Cannot parse " + c, e); + } + } + + public static Point toPoint(Content c) { + if (c.hasContentClass(EntityType.geopoint)) { + Double lat = c.get(WGS84PosName.lat, Double.class).orElseThrow(); + Double lon = c.get(WGS84PosName.lon, Double.class).orElseThrow(); + Double alt = c.get(WGS84PosName.alt, Double.class).orElse(null); + return JTS.GEOMETRY_FACTORY_WGS84 + .createPoint(alt != null ? new Coordinate(lat, lon, alt) : new Coordinate(lat, lon)); + } + return null; + } + + public static GeometryCollection getGeometries(Content entity) { + List geoms = new ArrayList<>(); + Point geoPoint = toPoint(entity); + if (geoPoint != null) + geoms.add(geoPoint); + + Geometry place = getGeometry(entity, EntityName.place.qName(), Geometry.class); + if (place != null) + geoms.add(place); + + if (geoms.isEmpty()) + return null; + GeometryCollection geometryCollection = JTS.GEOMETRY_FACTORY_WGS84 + .createGeometryCollection(geoms.toArray(new Geometry[geoms.size()])); + return geometryCollection; + } + + public static void updateBoundingBox(Content entity) { + GeometryCollection geometryCollection = getGeometries(entity); + if (geometryCollection == null) + return; + entity.addContentClasses(EntityType.geobounded.qName()); + + Envelope bbox = geometryCollection.getEnvelopeInternal(); + entity.put(EntityName.minLat, bbox.getMinX()); + entity.put(EntityName.minLon, bbox.getMinY()); + entity.put(EntityName.maxLat, bbox.getMaxX()); + entity.put(EntityName.maxLon, bbox.getMaxY()); + } + + /** singleton */ + private GeoEntityUtils() { + } + +} -- 2.30.2