GeoGson tooling
[gpl/argeo-suite.git] / org.argeo.app.geo / src / org / argeo / app / geo / http / WfsHttpHandler.java
index b10a10f4b186844500034fc65b97948c6696a648..d60d51d788249af5d334f4b632d7095da2e606a3 100644 (file)
@@ -1,26 +1,33 @@
 package org.argeo.app.geo.http;
 
+import java.io.BufferedOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.UncheckedIOException;
 import java.net.URL;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.Stream;
 
 import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentSession;
+import org.argeo.api.acr.DName;
 import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.QNamed;
 import org.argeo.api.acr.ldap.LdapAttr;
 import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.cms.CmsLog;
 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.GeoJSon;
 import org.argeo.app.geo.GpxUtils;
+import org.argeo.app.geo.JTS;
 import org.argeo.cms.http.HttpHeader;
 import org.argeo.cms.http.server.HttpServerUtils;
 import org.geotools.data.DataUtilities;
@@ -36,6 +43,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.Point;
 import org.locationtech.jts.geom.Polygon;
 import org.opengis.feature.GeometryAttribute;
 import org.opengis.feature.simple.SimpleFeature;
@@ -46,8 +54,12 @@ import org.opengis.feature.type.Name;
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpHandler;
 
+import jakarta.json.Json;
+import jakarta.json.stream.JsonGenerator;
+
 /** A partially implemented WFS 2.0 server. */
 public class WfsHttpHandler implements HttpHandler {
+       private final static CmsLog log = CmsLog.getLog(WfsHttpHandler.class);
        private ProvidedRepository contentRepository;
 
        // HTTP parameters
@@ -107,88 +119,139 @@ public class WfsHttpHandler implements HttpHandler {
 
                exchange.sendResponseHeaders(200, 0);
 
-               if ("GML3".equals(outputFormat)) {
-                       String entityType = "apafField";
-                       URL schemaLocation = new URL("http://localhost:7070/pkg/eu.netiket.on.apaf/apaf.xsd");
-                       String namespace = "http://apaf.on.netiket.eu/ns/apaf";
-
-                       GML gml = new GML(Version.WFS1_1);
-                       gml.setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84);
-                       gml.setNamespace("local", namespace);
+               final int BUFFER_SIZE = 100 * 1024;
+               try (BufferedOutputStream out = new BufferedOutputStream(exchange.getResponseBody(), BUFFER_SIZE)) {
+                       if ("GML3".equals(outputFormat)) {
+                               encodeCollectionAsGML(res, out);
+                       } else if ("application/json".equals(outputFormat)) {
+                               encodeCollectionAsGeoJSon(res, out);
+                       }
+               }
+       }
 
-                       SimpleFeatureType featureType = gml.decodeSimpleFeatureType(schemaLocation,
-                                       new NameImpl(namespace, entityType + "Feature"));
+       protected void encodeCollectionAsGeoJSon(Stream<Content> features, OutputStream out) throws IOException {
+               long begin = System.currentTimeMillis();
+               AtomicLong count = new AtomicLong(0);
+               JsonGenerator generator = Json.createGenerator(out);
+               generator.writeStartObject();
+               generator.write("type", "FeatureCollection");
+               generator.writeStartArray("features");
+               features.forEach((c) -> {
+                       Geometry defaultGeometry = getDefaultGeometry(c);
+                       if (defaultGeometry == null)
+                               return;
+                       generator.writeStartObject();
+                       generator.write("type", "Feature");
+                       String featureId = getFeatureId(c);
+                       if (featureId != null)
+                               generator.write("id", featureId);
+                       GeoJSon.writeBBox(generator, defaultGeometry);
+                       GeoJSon.writeGeometry(generator, defaultGeometry);
+
+                       generator.writeStartObject("properties");
+                       writeTimeProperties(generator, c);
+                       writeProperties(generator, c);
+                       generator.writeEnd();// properties object
+
+                       generator.writeEnd();// feature object
+
+                       if (count.incrementAndGet() % 10 == 0)
+                               try {
+                                       out.flush();
+                               } catch (IOException e) {
+                                       throw new UncheckedIOException(e);
+                               }
+               });
+               generator.writeEnd();// features array
+               generator.writeEnd().close();
 
-//                     CoordinateReferenceSystem crs=DefaultGeographicCRS.WGS84;
-//                     QName featureName = new QName(namespace,"apafFieldFeature");
-//                     GMLConfiguration configuration = new GMLConfiguration();
-//                     FeatureType parsed = GTXML.parseFeatureType(configuration, featureName, crs);
-//                     SimpleFeatureType featureType = DataUtilities.simple(parsed);
+               log.debug("GeoJSon encoding took " + (System.currentTimeMillis() - begin) + " ms.");
+       }
 
-                       SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(featureType);
+       protected Geometry getDefaultGeometry(Content content) {
+               if (content.hasContentClass(EntityType.geopoint)) {
+                       double latitude = content.get(WGS84PosName.lat, Double.class).get();
+                       double longitude = content.get(WGS84PosName.lng, Double.class).get();
 
-                       DefaultFeatureCollection featureCollection = new DefaultFeatureCollection();
+                       Coordinate coordinate = new Coordinate(longitude, latitude);
+                       Point the_geom = JTS.GEOMETRY_FACTORY.createPoint(coordinate);
+                       return the_geom;
+               }
+               return null;
+       }
 
-                       res.forEach((c) -> {
-//                             boolean gpx = false;
-                               Geometry the_geom = null;
-                               Polygon the_area = null;
-//                             if (gpx) {
-                               Content area = c.getContent("gpx/area.gpx").orElse(null);
-                               if (area != null) {
+       protected String getFeatureId(Content content) {
+               String uuid = content.attr(LdapAttr.entryUUID);
+               return uuid;
+       }
 
-                                       try (InputStream in = area.open(InputStream.class)) {
-                                               the_area = GpxUtils.parseGpxTrackTo(in, Polygon.class);
-                                       } catch (IOException e) {
-                                               throw new UncheckedIOException("Cannot parse " + c, e);
-                                       }
-                               }
-//                             } else {
-                               if (c.hasContentClass(EntityType.geopoint)) {
-                                       double latitude = c.get(WGS84PosName.lat, Double.class).get();
-                                       double longitude = c.get(WGS84PosName.lng, Double.class).get();
+       private final QName JCR_CREATED = NamespaceUtils.parsePrefixedName("jcr:created");
+
+       private final QName JCR_LAST_MODIFIED = NamespaceUtils.parsePrefixedName("jcr:lastModified");
+
+       protected void writeTimeProperties(JsonGenerator g, Content content) {
+               String creationDate = content.attr(DName.creationdate);
+               if (creationDate == null)
+                       creationDate = content.attr(JCR_CREATED);
+               if (creationDate != null)
+                       g.write(DName.creationdate.get(), creationDate);
+               String lastModified = content.attr(DName.getlastmodified);
+               if (lastModified == null)
+                       lastModified = content.attr(JCR_LAST_MODIFIED);
+               if (lastModified != null)
+                       g.write(DName.getlastmodified.get(), lastModified);
+       }
 
-                                       Coordinate coordinate = new Coordinate(longitude, latitude);
-                                       the_geom = GeoTools.GEOMETRY_FACTORY.createPoint(coordinate);
-                               }
+       protected void writeProperties(JsonGenerator generator, Content content) {
+               String path = content.getPath();
+               generator.write("path", path);
+               if (content.hasContentClass(EntityType.local)) {
+                       String type = content.attr(EntityName.type);
+                       generator.write("type", type);
+               } else {
+                       List<QName> contentClasses = content.getContentClasses();
+                       if (!contentClasses.isEmpty()) {
+                               generator.write("type", NamespaceUtils.toPrefixedName(contentClasses.get(0)));
+                       }
+               }
 
-//                             }
-                               if (the_geom != null)
-                                       featureBuilder.set(new NameImpl(namespace, "geopoint"), the_geom);
-                               if (the_area != null)
-                                       featureBuilder.set(new NameImpl(namespace, "area"), the_area);
-
-                               List<AttributeDescriptor> attrDescs = featureType.getAttributeDescriptors();
-                               for (AttributeDescriptor attrDesc : attrDescs) {
-                                       if (attrDesc instanceof GeometryAttribute)
-                                               continue;
-                                       Name name = attrDesc.getName();
-                                       QName qName = new QName(name.getNamespaceURI(), name.getLocalPart());
-                                       String value = c.attr(qName);
-                                       if (value == null) {
-                                               value = c.attr(name.getLocalPart());
-                                       }
-                                       if (value != null) {
-                                               featureBuilder.set(name, value);
-                                       }
-                               }
+       }
 
-                               String uuid = c.attr(LdapAttr.entryUUID);
+       protected void writeAttr(JsonGenerator g, Content content, String attr) {
+               writeAttr(g, content, NamespaceUtils.parsePrefixedName(attr));
+       }
 
-                               SimpleFeature feature = featureBuilder.buildFeature(uuid);
-                               featureCollection.add(feature);
+       protected void writeAttr(JsonGenerator g, Content content, QNamed attr) {
+               writeAttr(g, content, attr.qName());
+       }
 
-                       });
-                       gml.encode(exchange.getResponseBody(), featureCollection);
-                       exchange.getResponseBody().close();
+       protected void writeAttr(JsonGenerator g, Content content, QName attr) {
+               // String value = content.attr(attr);
+               Object value = content.get(attr);
+               if (value != null) {
+                       // TODO specify NamespaceContext
+                       String key = NamespaceUtils.toPrefixedName(attr);
+                       if (value instanceof Double v)
+                               g.write(key, v);
+                       else if (value instanceof Long v)
+                               g.write(key, v);
+                       else if (value instanceof Integer v)
+                               g.write(key, v);
+                       else if (value instanceof Boolean v)
+                               g.write(key, v);
+                       else
+                               g.write(key, value.toString());
+               }
+       }
 
-               } else if ("application/json".equals(outputFormat)) {
+       protected void encodeCollectionAsGeoJSonOld(Stream<Content> features, OutputStream out) throws IOException {
 
-                       // BODY PROCESSING
-                       GeoJSONWriter geoJSONWriter = new GeoJSONWriter(exchange.getResponseBody());
+               // BODY PROCESSING
+               try (GeoJSONWriter geoJSONWriter = new GeoJSONWriter(out)) {
                        geoJSONWriter.setPrettyPrinting(true);
+                       geoJSONWriter.setEncodeFeatureBounds(true);
 
-                       boolean gpx = false;
+                       boolean gpx = true;
                        SimpleFeatureType TYPE;
                        try {
                                if (gpx)
@@ -203,7 +266,7 @@ public class WfsHttpHandler implements HttpHandler {
                        SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
                        GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
 
-                       res.forEach((c) -> {
+                       features.forEach((c) -> {
                                Geometry the_geom;
                                if (gpx) {// experimental
                                        Content area = c.getContent("gpx/area.gpx").orElse(null);
@@ -250,8 +313,83 @@ public class WfsHttpHandler implements HttpHandler {
                                        throw new UncheckedIOException(e);
                                }
                        });
-                       geoJSONWriter.close();
                }
+       }
+
+       protected void encodeCollectionAsGML(Stream<Content> features, OutputStream out) throws IOException {
+               String entityType = "entity";
+               URL schemaLocation = getClass().getResource("/org/argeo/app/api/entity.xsd");
+               String namespace = "http://www.argeo.org/ns/entity";
+
+               GML gml = new GML(Version.WFS1_1);
+               gml.setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84);
+               gml.setNamespace("local", namespace);
+
+               SimpleFeatureType featureType = gml.decodeSimpleFeatureType(schemaLocation,
+                               new NameImpl(namespace, entityType + "Feature"));
+
+//             CoordinateReferenceSystem crs=DefaultGeographicCRS.WGS84;
+//             QName featureName = new QName(namespace,"apafFieldFeature");
+//             GMLConfiguration configuration = new GMLConfiguration();
+//             FeatureType parsed = GTXML.parseFeatureType(configuration, featureName, crs);
+//             SimpleFeatureType featureType = DataUtilities.simple(parsed);
+
+               SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(featureType);
+
+               DefaultFeatureCollection featureCollection = new DefaultFeatureCollection();
+
+               features.forEach((c) -> {
+//                     boolean gpx = false;
+                       Geometry the_geom = 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)) {
+                                       the_area = GpxUtils.parseGpxTrackTo(in, Polygon.class);
+                               } catch (IOException e) {
+                                       throw new UncheckedIOException("Cannot parse " + c, e);
+                               }
+                       }
+//                     } else {
+                       if (c.hasContentClass(EntityType.geopoint)) {
+                               double latitude = c.get(WGS84PosName.lat, Double.class).get();
+                               double longitude = c.get(WGS84PosName.lng, Double.class).get();
+
+                               Coordinate coordinate = new Coordinate(longitude, latitude);
+                               the_geom = JTS.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_area);
+
+                       List<AttributeDescriptor> attrDescs = featureType.getAttributeDescriptors();
+                       for (AttributeDescriptor attrDesc : attrDescs) {
+                               if (attrDesc instanceof GeometryAttribute)
+                                       continue;
+                               Name name = attrDesc.getName();
+                               QName qName = new QName(name.getNamespaceURI(), name.getLocalPart());
+                               String value = c.attr(qName);
+                               if (value == null) {
+                                       value = c.attr(name.getLocalPart());
+                               }
+                               if (value != null) {
+                                       featureBuilder.set(name, value);
+                               }
+                       }
+
+                       String uuid = c.attr(LdapAttr.entryUUID);
+
+                       SimpleFeature feature = featureBuilder.buildFeature(uuid);
+                       featureCollection.add(feature);
+
+               });
+               gml.encode(out, featureCollection);
+               out.close();
 
        }