Improve and clean up geo utilities.
authorMathieu Baudier <mbaudier@argeo.org>
Fri, 8 Sep 2023 07:01:34 +0000 (09:01 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Fri, 8 Sep 2023 07:01:34 +0000 (09:01 +0200)
org.argeo.app.geo/OSGI-INF/geoJsonHttpHandler.xml [deleted file]
org.argeo.app.geo/OSGI-INF/wfsHttpHandler.xml [new file with mode: 0644]
org.argeo.app.geo/bnd.bnd
org.argeo.app.geo/src/org/argeo/app/geo/GeoTools.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/GpxUtils.java
org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java

diff --git a/org.argeo.app.geo/OSGI-INF/geoJsonHttpHandler.xml b/org.argeo.app.geo/OSGI-INF/geoJsonHttpHandler.xml
deleted file mode 100644 (file)
index 352595b..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true">
-   <implementation class="org.argeo.app.geo.http.WfsHttpHandler"/>
-   <service>
-      <provide interface="com.sun.net.httpserver.HttpHandler"/>
-   </service>
-   <property name="context.path" type="String" value="/api/geojson/" />
-   <property name="context.public" type="String" value="true" />
-   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ProvidedRepository" policy="static"/>
-</scr:component>
diff --git a/org.argeo.app.geo/OSGI-INF/wfsHttpHandler.xml b/org.argeo.app.geo/OSGI-INF/wfsHttpHandler.xml
new file mode 100644 (file)
index 0000000..d5646f2
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true">
+   <implementation class="org.argeo.app.geo.http.WfsHttpHandler"/>
+   <service>
+      <provide interface="com.sun.net.httpserver.HttpHandler"/>
+   </service>
+   <property name="context.path" type="String" value="/api/wfs/" />
+   <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ProvidedRepository" policy="static"/>
+</scr:component>
index 3a5153ec15a83e7ea818d59a4cac94adee55ee67..37b6d314fb9e012c4ed38e8594b6c759a2dc985a 100644 (file)
@@ -5,4 +5,4 @@ org.geotools.xml.transform,\
 *
 
 Service-Component:\
-OSGI-INF/geoJsonHttpHandler.xml,\
+OSGI-INF/wfsHttpHandler.xml,\
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/GeoTools.java b/org.argeo.app.geo/src/org/argeo/app/geo/GeoTools.java
new file mode 100644 (file)
index 0000000..166c686
--- /dev/null
@@ -0,0 +1,34 @@
+package org.argeo.app.geo;
+
+import org.argeo.api.cms.CmsLog;
+import org.geotools.factory.CommonFactoryFinder;
+import org.geotools.geometry.jts.JTSFactoryFinder;
+import org.geotools.styling.StyleFactory;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.opengis.filter.FilterFactory2;
+
+/**
+ * Factories initialisation and workarounds for the GeoTools library. The idea
+ * is to code defensively around factory initialisation, API changes, and issues
+ * related to running in an OSGi environment. Rather see {@link GeoUtils} for
+ * functional static utilities.
+ */
+public class GeoTools {
+       private final static CmsLog log = CmsLog.getLog(GeoTools.class);
+
+       public final static GeometryFactory GEOMETRY_FACTORY;
+       public final static StyleFactory STYLE_FACTORY;
+       public final static FilterFactory2 FILTER_FACTORY;
+
+       static {
+               try {
+                       GEOMETRY_FACTORY = JTSFactoryFinder.getGeometryFactory();
+                       STYLE_FACTORY = CommonFactoryFinder.getStyleFactory();
+                       FILTER_FACTORY = CommonFactoryFinder.getFilterFactory2();
+               } catch (RuntimeException e) {
+                       log.error("Basic GeoTools initialisation failed, geographical utilities are probably not available", e);
+                       throw e;
+               }
+       }
+
+}
index 645b08bda8d9ed13b05c4488e1c3ad326e3f660f..15f6b89675e5a03cf16bc03882271baa36ce6f5b 100644 (file)
@@ -18,9 +18,10 @@ import javax.xml.parsers.SAXParserFactory;
 import org.geotools.data.DataUtilities;
 import org.geotools.feature.SchemaException;
 import org.geotools.feature.simple.SimpleFeatureBuilder;
-import org.geotools.geometry.jts.JTSFactoryFinder;
 import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
 import org.locationtech.jts.geom.Polygon;
 import org.opengis.feature.simple.SimpleFeature;
 import org.opengis.feature.simple.SimpleFeatureType;
@@ -30,14 +31,30 @@ import org.xml.sax.helpers.DefaultHandler;
 
 /** Utilities around the GPX format. */
 public class GpxUtils {
+       /** GPX as a LineString in WGS84 (feature type with only the_geom). */
+       public static final SimpleFeatureType LINESTRING_FEATURE_TYPE;
+       /** GPX as a Polygon in WGS84 (feature type with only the_geom). */
+       public static final SimpleFeatureType POLYGON_FEATURE_TYPE;
 
-       public static SimpleFeature parseGpxToPolygon(InputStream in) throws IOException {
+       static {
                try {
-                       final SimpleFeatureType TYPE = DataUtilities.createType("Area", "the_geom:Polygon:srid=4326");
-                       SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
+                       LINESTRING_FEATURE_TYPE = DataUtilities.createType("Area", "the_geom:LineString:srid=4326");
+                       POLYGON_FEATURE_TYPE = DataUtilities.createType("Area", "the_geom:Polygon:srid=4326");
+               } catch (SchemaException e) {
+                       throw new RuntimeException("Cannot create GPX Feature type", e);
+               }
+       }
 
-                       GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
-                       List<Coordinate> coordinates = new ArrayList<>();
+       /**
+        * Converts a GPX track to either a {@link Geometry} with WGS84 coordinates
+        * ({@link LineString} or {@link Polygon}) or a {@link SimpleFeature} (with
+        * {@link #LINESTRING_FEATURE_TYPE}).
+        */
+       @SuppressWarnings("unchecked")
+       public static <T> T parseGpxTrackTo(InputStream in, Class<T> clss) throws IOException {
+               GeometryFactory geometryFactory = GeoTools.GEOMETRY_FACTORY;
+               List<Coordinate> coordinates = new ArrayList<>();
+               try {
                        SAXParserFactory factory = SAXParserFactory.newInstance();
                        SAXParser saxParser = factory.newSAXParser();
 
@@ -49,24 +66,51 @@ public class GpxUtils {
                                        if ("trkpt".equals(qName)) {
                                                Double latitude = Double.parseDouble(attributes.getValue("lat"));
                                                Double longitude = Double.parseDouble(attributes.getValue("lon"));
+                                               // TODO elevation in 3D context
                                                Coordinate coordinate = new Coordinate(longitude, latitude);
                                                coordinates.add(coordinate);
                                        }
                                }
 
                        });
+               } catch (ParserConfigurationException | SAXException e) {
+                       throw new RuntimeException("Cannot convert GPX", e);
+               }
+
+               if (LineString.class.isAssignableFrom(clss)) {
+                       LineString lineString = geometryFactory
+                                       .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
+                       return (T) lineString;
+               } else if (Polygon.class.isAssignableFrom(clss)) {
                        // close the line string
                        coordinates.add(coordinates.get(0));
-
                        Polygon polygon = geometryFactory.createPolygon(coordinates.toArray(new Coordinate[coordinates.size()]));
-                       featureBuilder.add(polygon);
+                       return (T) polygon;
+               }
+               // TODO MultiPoint? MultiLine? etc.
+               else if (SimpleFeature.class.isAssignableFrom(clss)) {
+                       SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(LINESTRING_FEATURE_TYPE);
+                       LineString lineString = geometryFactory
+                                       .createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
+                       featureBuilder.add(lineString);
                        SimpleFeature area = featureBuilder.buildFeature(null);
-                       return area;
-               } catch (ParserConfigurationException | SAXException | SchemaException e) {
-                       throw new RuntimeException("Cannot convert GPX", e);
+                       return (T) area;
+               } else {
+                       throw new IllegalArgumentException("Unsupported format " + clss);
                }
        }
 
+       /** @deprecated Use {@link #parseGpxTrackTo(InputStream, Class)} instead. */
+       @Deprecated
+       public static SimpleFeature parseGpxToPolygon(InputStream in) throws IOException {
+               SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(POLYGON_FEATURE_TYPE);
+               Polygon polygon = parseGpxTrackTo(in, Polygon.class);
+               featureBuilder.add(polygon);
+               SimpleFeature area = featureBuilder.buildFeature(null);
+               return area;
+       }
+
+       /** Write ODK GepShape as a GPX file. */
        public static void writeGeoShapeAsGpx(String geoShape, OutputStream out) throws IOException {
                Objects.requireNonNull(geoShape);
                Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
index bccf98ecd287c077374aae3bbe3c20d2626e7f23..b10a10f4b186844500034fc65b97948c6696a648 100644 (file)
@@ -19,6 +19,7 @@ import org.argeo.app.api.EntityName;
 import org.argeo.app.api.EntityType;
 import org.argeo.app.api.WGS84PosName;
 import org.argeo.app.geo.CqlUtils;
+import org.argeo.app.geo.GeoTools;
 import org.argeo.app.geo.GpxUtils;
 import org.argeo.cms.http.HttpHeader;
 import org.argeo.cms.http.server.HttpServerUtils;
@@ -35,6 +36,7 @@ import org.geotools.wfs.GML.Version;
 import org.locationtech.jts.geom.Coordinate;
 import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.Polygon;
 import org.opengis.feature.GeometryAttribute;
 import org.opengis.feature.simple.SimpleFeature;
 import org.opengis.feature.simple.SimpleFeatureType;
@@ -124,21 +126,19 @@ public class WfsHttpHandler implements HttpHandler {
 //                     SimpleFeatureType featureType = DataUtilities.simple(parsed);
 
                        SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(featureType);
-                       GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
 
                        DefaultFeatureCollection featureCollection = new DefaultFeatureCollection();
 
                        res.forEach((c) -> {
 //                             boolean gpx = false;
                                Geometry the_geom = null;
-                               Geometry the_area = null;
+                               Polygon the_area = null;
 //                             if (gpx) {
                                Content area = c.getContent("gpx/area.gpx").orElse(null);
                                if (area != null) {
 
                                        try (InputStream in = area.open(InputStream.class)) {
-                                               SimpleFeature feature = GpxUtils.parseGpxToPolygon(in);
-                                               the_area = (Geometry) feature.getDefaultGeometry();
+                                               the_area = GpxUtils.parseGpxTrackTo(in, Polygon.class);
                                        } catch (IOException e) {
                                                throw new UncheckedIOException("Cannot parse " + c, e);
                                        }
@@ -149,14 +149,14 @@ public class WfsHttpHandler implements HttpHandler {
                                        double longitude = c.get(WGS84PosName.lng, Double.class).get();
 
                                        Coordinate coordinate = new Coordinate(longitude, latitude);
-                                       the_geom = geometryFactory.createPoint(coordinate);
+                                       the_geom = GeoTools.GEOMETRY_FACTORY.createPoint(coordinate);
                                }
 
 //                             }
                                if (the_geom != null)
                                        featureBuilder.set(new NameImpl(namespace, "geopoint"), the_geom);
                                if (the_area != null)
-                                       featureBuilder.set(new NameImpl(namespace, "area"), the_geom);
+                                       featureBuilder.set(new NameImpl(namespace, "area"), the_area);
 
                                List<AttributeDescriptor> attrDescs = featureType.getAttributeDescriptors();
                                for (AttributeDescriptor attrDesc : attrDescs) {