--- /dev/null
+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.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+
+import jakarta.json.stream.JsonGenerator;
+
+/**
+ * GeoJSon format.
+ *
+ * @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 static void writeGeometry(JsonGenerator generator, Geometry geometry) {
+ generator.writeStartObject("geometry");
+ if (geometry instanceof Point point) {
+ generator.write("type", "Point");
+ generator.writeStartArray("coordinates");
+ writeCoordinate(generator, point.getCoordinate());
+ generator.writeEnd();// coordinates array
+ } else if (geometry instanceof LineString lineString) {
+ generator.write("type", "LineString");
+ generator.writeStartArray("coordinates");
+ writeCoordinates(generator, lineString.getCoordinates());
+ generator.writeEnd();// coordinates array
+ } else if (geometry instanceof Polygon polygon) {
+ generator.write("type", "Polygon");
+ generator.writeStartArray("coordinates");
+ LinearRing exteriorRing = polygon.getExteriorRing();
+ generator.writeStartArray();
+ writeCoordinates(generator, exteriorRing.getCoordinates());
+ generator.writeEnd();
+ for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
+ LinearRing interiorRing = polygon.getInteriorRingN(i);
+ // TODO verify that holes are clockwise
+ generator.writeStartArray();
+ writeCoordinates(generator, interiorRing.getCoordinates());
+ generator.writeEnd();
+ }
+ generator.writeEnd();// coordinates array
+ }
+ generator.writeEnd();// geometry object
+ }
+
+ public static void writeCoordinates(JsonGenerator generator, Coordinate[] coordinates) {
+ for (Coordinate coordinate : coordinates) {
+ generator.writeStartArray();
+ writeCoordinate(generator, coordinate);
+ generator.writeEnd();
+ }
+ }
+
+ public static void writeCoordinate(JsonGenerator generator, Coordinate coordinate) {
+ generator.write(coordinate.getX());
+ generator.write(coordinate.getY());
+ double z = coordinate.getZ();
+ if (!Double.isNaN(z)) {
+ generator.write(z);
+ }
+ }
+
+ /** singleton */
+ private GeoJSon() {
+ }
+
+}
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;
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;
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
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)
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);
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();
}