From: Mathieu Baudier Date: Thu, 31 Aug 2023 12:20:47 +0000 (+0200) Subject: GeoJSON HTTP service X-Git-Tag: v2.3.16~47 X-Git-Url: https://git.argeo.org/?p=gpl%2Fargeo-suite.git;a=commitdiff_plain;h=2c1a7dd0af92287d3239a21dcec90de7313842a9 GeoJSON HTTP service --- diff --git a/Makefile b/Makefile index 0925417..c41b17e 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ clean: ## WEB web: - cd org.argeo.app.geo.js && npm run build-prod + cd org.argeo.app.geo.js && npm run build npm-install: cd org.argeo.app.geo.js && npm install diff --git a/org.argeo.app.geo.js/package-lock.json b/org.argeo.app.geo.js/package-lock.json index 775cf04..68f7843 100644 --- a/org.argeo.app.geo.js/package-lock.json +++ b/org.argeo.app.geo.js/package-lock.json @@ -24,9 +24,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.13.tgz", - "integrity": "sha512-3l6+4YOvc9wx7VlCSw4yQfcBo01ECA8TicQfbnCPuCEpRQrf+gTUyGdxNw+pyTUyywp6JRD1w0YQs9TpBXYlkw==", + "version": "7.22.14", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.14.tgz", + "integrity": "sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -639,9 +639,9 @@ "dev": true }, "node_modules/@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g==", "dev": true }, "node_modules/@types/markdown-it": { @@ -1299,9 +1299,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001524", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz", - "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", + "version": "1.0.30001525", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz", + "integrity": "sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q==", "dev": true, "funding": [ { @@ -2019,9 +2019,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.505", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.505.tgz", - "integrity": "sha512-0A50eL5BCCKdxig2SsCXhpuztnB9PfUgRMojj5tMvt8O54lbwz3t6wNgnpiTRosw5QjlJB7ixhVyeg8daLQwSQ==", + "version": "1.4.506", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.506.tgz", + "integrity": "sha512-xxGct4GPAKSRlrLBtJxJFYy74W11zX6PO9GyHgl/U+2s3Dp0ZEwAklDfNHXOWcvH7zWMpsmgbR0ggEuaYAVvHA==", "dev": true }, "node_modules/emoji-regex": { @@ -2418,9 +2418,9 @@ } }, "node_modules/glob": { - "version": "10.3.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.3.tgz", - "integrity": "sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==", + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.4.tgz", + "integrity": "sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", @@ -6918,9 +6918,9 @@ }, "dependencies": { "@babel/parser": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.13.tgz", - "integrity": "sha512-3l6+4YOvc9wx7VlCSw4yQfcBo01ECA8TicQfbnCPuCEpRQrf+gTUyGdxNw+pyTUyywp6JRD1w0YQs9TpBXYlkw==", + "version": "7.22.14", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.14.tgz", + "integrity": "sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ==", "dev": true }, "@colors/colors": { @@ -7402,9 +7402,9 @@ "dev": true }, "@types/linkify-it": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", - "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g==", "dev": true }, "@types/markdown-it": { @@ -7934,9 +7934,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001524", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz", - "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", + "version": "1.0.30001525", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz", + "integrity": "sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q==", "dev": true }, "catharsis": { @@ -8427,9 +8427,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.505", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.505.tgz", - "integrity": "sha512-0A50eL5BCCKdxig2SsCXhpuztnB9PfUgRMojj5tMvt8O54lbwz3t6wNgnpiTRosw5QjlJB7ixhVyeg8daLQwSQ==", + "version": "1.4.506", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.506.tgz", + "integrity": "sha512-xxGct4GPAKSRlrLBtJxJFYy74W11zX6PO9GyHgl/U+2s3Dp0ZEwAklDfNHXOWcvH7zWMpsmgbR0ggEuaYAVvHA==", "dev": true }, "emoji-regex": { @@ -8733,9 +8733,9 @@ "dev": true }, "glob": { - "version": "10.3.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.3.tgz", - "integrity": "sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==", + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.4.tgz", + "integrity": "sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==", "dev": true, "requires": { "foreground-child": "^3.1.0", diff --git a/org.argeo.app.geo/.project b/org.argeo.app.geo/.project index 674edda..a41594e 100644 --- a/org.argeo.app.geo/.project +++ b/org.argeo.app.geo/.project @@ -20,6 +20,11 @@ + + org.eclipse.pde.ds.core.builder + + + org.eclipse.pde.PluginNature diff --git a/org.argeo.app.geo/OSGI-INF/geoJsonHttpHandler.xml b/org.argeo.app.geo/OSGI-INF/geoJsonHttpHandler.xml new file mode 100644 index 0000000..4fbec8f --- /dev/null +++ b/org.argeo.app.geo/OSGI-INF/geoJsonHttpHandler.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/org.argeo.app.geo/bnd.bnd b/org.argeo.app.geo/bnd.bnd index 6657284..2076ba1 100644 --- a/org.argeo.app.geo/bnd.bnd +++ b/org.argeo.app.geo/bnd.bnd @@ -2,3 +2,6 @@ Import-Package:\ tech.units.indriya.unit,\ com.fasterxml.jackson.core,\ * + +Service-Component:\ +OSGI-INF/geoJsonHttpHandler.xml,\ diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/CqlUtils.java b/org.argeo.app.geo/src/org/argeo/app/geo/CqlUtils.java new file mode 100644 index 0000000..8d6c138 --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/geo/CqlUtils.java @@ -0,0 +1,60 @@ +package org.argeo.app.geo; + +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.search.AndFilter; +import org.argeo.api.acr.search.BasicSearch; +import org.argeo.api.acr.search.ContentFilter; +import org.geotools.filter.text.cql2.CQL; +import org.geotools.filter.text.cql2.CQLException; +import org.opengis.filter.And; +import org.opengis.filter.Filter; +import org.opengis.filter.PropertyIsEqualTo; +import org.opengis.filter.expression.Literal; +import org.opengis.filter.expression.PropertyName; + +public class CqlUtils { + + public final static String CQL_FILTER = "cql_filter"; + + public static void filter(BasicSearch search, String cql) { + try { + filter(search, CQL.toFilter(cql)); + } catch (CQLException e) { + throw new IllegalArgumentException("Cannot parse CQL: " + cql, e); + } + } + + public static void filter(BasicSearch search, Filter filter) { + search.where((where) -> { + if (filter instanceof And and) { + processAnd(where, and); + } else if (filter instanceof PropertyIsEqualTo propertyIsEqualTo) { + processIsEqualTo(where, propertyIsEqualTo); + } else { + throw new IllegalArgumentException("Unsupported filter " + filter.getClass()); + } + }); + } + + private static void processAnd(AndFilter contentFilter, And filter) { + for (Filter child : filter.getChildren()) { + if (child instanceof PropertyIsEqualTo propertyIsEqualTo) { + processIsEqualTo(contentFilter, propertyIsEqualTo); + } + } + + } + + private static void processIsEqualTo(ContentFilter contentFilter, PropertyIsEqualTo propertyIsEqualTo) { + // TODO properly deal with types etc. + // see GeoTools org.geotools.filter.text.commons.ExpressionToText + PropertyName propertyName = (PropertyName) propertyIsEqualTo.getExpression1(); + Literal value = (Literal) propertyIsEqualTo.getExpression2(); + // String escaped = literal.toString().replaceAll("'", "''"); + contentFilter.eq(NamespaceUtils.parsePrefixedName(propertyName.toString()), value.toString()); + } + + /** singleton */ + private CqlUtils() { + } +} diff --git a/org.argeo.app.geo/src/org/argeo/app/internal/geo/http/GeoJsonHttpHandler.java b/org.argeo.app.geo/src/org/argeo/app/internal/geo/http/GeoJsonHttpHandler.java new file mode 100644 index 0000000..8840a4e --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/internal/geo/http/GeoJsonHttpHandler.java @@ -0,0 +1,118 @@ +package org.argeo.app.internal.geo.http; + +import static org.argeo.app.geo.CqlUtils.CQL_FILTER; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentSession; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.ldap.LdapAttr; +import org.argeo.api.acr.spi.ProvidedRepository; +import org.argeo.app.api.EntityType; +import org.argeo.app.api.WGS84PosName; +import org.argeo.app.geo.CqlUtils; +import org.argeo.cms.http.HttpHeader; +import org.argeo.cms.http.server.HttpServerUtils; +import org.geotools.data.DataUtilities; +import org.geotools.data.geojson.GeoJSONWriter; +import org.geotools.feature.SchemaException; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.filter.text.cql2.CQL; +import org.geotools.geometry.jts.JTSFactoryFinder; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.Point; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.simple.SimpleFeatureType; +import org.opengis.filter.Filter; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +public class GeoJsonHttpHandler implements HttpHandler { + private ProvidedRepository contentRepository; + + @Override + public void handle(HttpExchange exchange) throws IOException { + String path = HttpServerUtils.subPath(exchange); + ContentSession session = HttpServerUtils.getContentSession(contentRepository, exchange); + // Content content = session.get(path); + + exchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), "application/json; charset=utf-8"); + + Map> parameters = HttpServerUtils.parseParameters(exchange); + String cql = parameters.containsKey(CQL_FILTER) ? parameters.get(CQL_FILTER).get(0) : null; + + if (cql != null) { + Stream res = session.search((search) -> { + CqlUtils.filter(search.from(path), cql); + search.getWhere().isContentClass(EntityType.local); + }); + + GeoJSONWriter geoJSONWriter = new GeoJSONWriter(exchange.getResponseBody()); + geoJSONWriter.setPrettyPrinting(true); + +// Writer writer = new OutputStreamWriter(exchange.getResponseBody()); +// writer.write(""" +// { +// "type": "FeatureCollection", +// "features": [ +// """); + + SimpleFeatureType TYPE; + try { + TYPE = DataUtilities.createType("Content", "the_geom:Point:srid=4326,path:String,name:String"); + } catch (SchemaException e) { + throw new RuntimeException(e); + } + SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE); + GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory(); + + res.forEach((c) -> { + if (!c.hasContentClass(EntityType.geopoint)) + return; + + double latitude = c.get(WGS84PosName.lat, Double.class).get(); + double longitude = c.get(WGS84PosName.lng, Double.class).get(); + + Coordinate coordinate = new Coordinate(longitude, latitude); + Point point = geometryFactory.createPoint(coordinate); + + featureBuilder.add(point); + String pth = c.getPath(); + featureBuilder.add(pth); + featureBuilder.add(NamespaceUtils.toPrefixedName(c.getName())); + + String uuid = c.attr(LdapAttr.entryUUID); + + SimpleFeature feature = featureBuilder.buildFeature(uuid); +// String json = GeoJSONWriter.toGeoJSON(feature); + try { + geoJSONWriter.write(feature); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); +// writer.write(""" +// ] +// } +// """); + geoJSONWriter.close(); + } + + } + + public void setContentRepository(ProvidedRepository contentRepository) { + this.contentRepository = contentRepository; + } + + public static void main(String[] args) throws Exception { + Filter filter = CQL.toFilter("entity:type='apafField' AND jcr:isCheckedOut=false"); + System.out.println(CQL.toCQL(filter)); + } +} diff --git a/org.argeo.app.geo/src/org/argeo/app/internal/geo/http/GmlHttpHandler.java b/org.argeo.app.geo/src/org/argeo/app/internal/geo/http/GmlHttpHandler.java new file mode 100644 index 0000000..7213cd2 --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/internal/geo/http/GmlHttpHandler.java @@ -0,0 +1,31 @@ +package org.argeo.app.internal.geo.http; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.ContentSession; +import org.argeo.api.acr.spi.ProvidedRepository; +import org.argeo.app.geo.CqlUtils; +import org.argeo.cms.http.server.HttpServerUtils; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +public class GmlHttpHandler implements HttpHandler { + private ProvidedRepository contentRepository; + + @Override + public void handle(HttpExchange exchange) throws IOException { + String path = HttpServerUtils.subPath(exchange); + ContentSession session = HttpServerUtils.getContentSession(contentRepository, exchange); +// Content content = session.get(path); + } + + public void setContentRepository(ProvidedRepository contentRepository) { + this.contentRepository = contentRepository; + } + +}