bounding box support
authorMathieu Baudier <mbaudier@argeo.org>
Wed, 4 Oct 2023 10:43:52 +0000 (12:43 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Wed, 4 Oct 2023 10:43:52 +0000 (12:43 +0200)
js/src/geo/OpenLayersMapPart.js
js/src/geo/export-package.js
org.argeo.app.core/src/org/argeo/app/ux/js/JsClient.java
org.argeo.app.core/src/org/argeo/app/ux/js/JsReference.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/acr/GeoEntityUtils.java
org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java
org.argeo.app.geo/src/org/argeo/app/geo/ux/OpenLayersMapPart.java
org.argeo.app.geo/src/org/argeo/app/ol/VectorSource.java
org.argeo.app.geo/src/org/argeo/app/ol/View.java

index a75956680ee1e165ebf0151015d973725df8ef62..71b9a17b0574dd1539e1eb00c4cc5fa7823598c6 100644 (file)
@@ -6,7 +6,7 @@ import Map from 'ol/Map.js';
 import View from 'ol/View.js';
 import OSM from 'ol/source/OSM.js';
 import TileLayer from 'ol/layer/Tile.js';
-import { fromLonLat, getPointResolution } from 'ol/proj.js';
+import { fromLonLat, getPointResolution, transformExtent } from 'ol/proj.js';
 import VectorSource from 'ol/source/Vector.js';
 import Feature from 'ol/Feature.js';
 import { Point } from 'ol/geom.js';
@@ -21,6 +21,8 @@ import * as SLDReader from '@nieuwlandgeo/sldreader';
 
 import MapPart from './MapPart.js';
 
+import { bbox } from 'ol/loadingstrategy';
+
 /** OpenLayers implementation of MapPart. */
 export default class OpenLayersMapPart extends MapPart {
        /** The OpenLayers Map. */
@@ -46,12 +48,14 @@ export default class OpenLayersMapPart extends MapPart {
                                //                                                                      transition: 0,
                                //                                                              }),
                        ],
-                       //                      view: new View({
-                       //                              center: [0, 0],
-                       //                              zoom: 2,
-                       //                      }),
+                       //                                              view: new View({
+                       //                                                      projection: 'EPSG:4326',
+                       //                                                      center: [0, 0],
+                       //                                                      zoom: 2,
+                       //                                              }),
                        target: this.getMapName(),
                });
+               //this.#map.getView().set('projection', 'EPSG:4326', true);
        }
 
        /* GEOGRAPHICAL METHODS */
@@ -282,4 +286,44 @@ export default class OpenLayersMapPart extends MapPart {
                });
                vectorLayer.setStyle(olStyleFunction);
        }
-}
\ No newline at end of file
+
+       //
+       // BBOX
+       //
+       applyBboxStrategy(layerName) {
+               const layer = this.getLayerByName(layerName);
+               const vectorSource = layer.getSource();
+               const baseUrl = vectorSource.getUrl();
+               if (typeof baseUrl === 'function')
+                       throw new Error('A strategy was already applied to layer ' + layerName);
+
+               const loadFunction = function(extent, resolution, projection, success, failure) {
+
+                       const proj = projection.getCode();
+                       var bbox = transformExtent(extent, proj, 'EPSG:4326');
+
+                       const url = baseUrl + '&' +
+                               'bbox=' + bbox.join(',') ;
+//                             'bbox=' + extent.join(',') + ',' + proj;
+                       const xhr = new XMLHttpRequest();
+                       xhr.open('GET', url);
+                       const onError = function() {
+                               vectorSource.removeLoadedExtent(extent);
+                               failure();
+                       }
+                       xhr.onerror = onError;
+                       xhr.onload = function() {
+                               if (xhr.status == 200) {
+                                       const features = vectorSource.getFormat().readFeatures(xhr.responseText);
+                                       vectorSource.addFeatures(features);
+                                       success(features);
+                               } else {
+                                       onError();
+                               }
+                       }
+                       xhr.send();
+               }
+
+               vectorSource.setLoader(loadFunction);
+       }
+}
index 8aaf93727930f2d044152eccfc7bfa52d7144dab..ca10dfc1caf10ca82726a825ab016c6cbd1548dc 100644 (file)
@@ -8,6 +8,7 @@ import TileLayer from 'ol/layer/Tile.js';
 import VectorSource from 'ol/source/Vector.js';
 import VectorLayer from 'ol/layer/Vector.js';
 import GeoJSON from 'ol/format/GeoJSON.js';
+import {bbox} from 'ol/loadingstrategy';
 
 // PSEUDO PACKAGE
 if (typeof globalThis.argeo === 'undefined')
@@ -34,6 +35,7 @@ globalThis.argeo.tp.ol.OSM = OSM;
 globalThis.argeo.tp.ol.VectorSource = VectorSource;
 globalThis.argeo.tp.ol.VectorLayer = VectorLayer;
 globalThis.argeo.tp.ol.GeoJSON = GeoJSON;
+globalThis.argeo.tp.ol.bbox = bbox;
 
 "use strict";
 
index f889fd96bdaf6c0ca170551b896ade0e1ff5ade3..060f1200856c94bb666d8d0c02562b7ee9764210 100644 (file)
@@ -94,6 +94,8 @@ public interface JsClient {
                                return jsObject.newJs();
                        else
                                return jsObject.getJsReference();
+               } else if (o instanceof JsReference jsReference) {
+                       return jsReference.get();
                } else
                        return '\'' + o.toString() + '\'';
        }
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/js/JsReference.java b/org.argeo.app.core/src/org/argeo/app/ux/js/JsReference.java
new file mode 100644 (file)
index 0000000..0cd8d81
--- /dev/null
@@ -0,0 +1,17 @@
+package org.argeo.app.ux.js;
+
+import java.util.function.Supplier;
+
+/** Plain JS reference, to be directly serialised. */
+public class JsReference implements Supplier<String> {
+       private final String reference;
+
+       public JsReference(String reference) {
+               this.reference = reference;
+       }
+
+       public String get() {
+               return reference;
+       }
+
+}
index 6019ee9ae46ab4b20c4d1a55035f49033a56f2cd..f10d89e60ee38486af58cf64e4f9b0125c59b4a6 100644 (file)
@@ -55,7 +55,7 @@ public class GeoEntityUtils {
                } catch (IOException e) {
                        throw new UncheckedIOException("Cannot add geometry " + name + " to " + c, e);
                }
-               updateBoundingBox(c);
+               // updateBoundingBox(c);
        }
 
        public static <T extends Geometry> T getGeometry(Content c, QNamed name, Class<T> clss) {
@@ -81,9 +81,10 @@ public class GeoEntityUtils {
                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 JTS.GEOMETRY_FACTORY_WGS84.createPoint(new Coordinate(lat, lon));
+//                     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;
        }
@@ -118,6 +119,19 @@ public class GeoEntityUtils {
                entity.put(EntityName.maxLon, bbox.getMaxY());
        }
 
+       public static void updateBoundingBox(Content entity, QName prop) {
+               Geometry geom = getGeometry(entity, prop, Geometry.class);
+               if (geom == null)
+                       return;
+               entity.addContentClasses(EntityType.geobounded.qName());
+
+               Envelope bbox = geom.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() {
        }
index 6eb12796572279509400b7ee3a537ee25f842015..580866dec4c5c13577d7aa34f908b17ad8f0184c 100644 (file)
@@ -35,7 +35,6 @@ import org.argeo.cms.acr.json.AcrJsonUtils;
 import org.argeo.cms.http.HttpHeader;
 import org.argeo.cms.http.server.HttpServerUtils;
 import org.argeo.cms.util.LangUtils;
-import org.argeo.cms.util.StreamUtils;
 import org.geotools.data.DataUtilities;
 import org.geotools.data.geojson.GeoJSONWriter;
 import org.geotools.feature.DefaultFeatureCollection;
@@ -43,10 +42,12 @@ import org.geotools.feature.NameImpl;
 import org.geotools.feature.SchemaException;
 import org.geotools.feature.simple.SimpleFeatureBuilder;
 import org.geotools.geometry.jts.JTSFactoryFinder;
+import org.geotools.referencing.CRS;
 import org.geotools.referencing.crs.DefaultGeographicCRS;
 import org.geotools.wfs.GML;
 import org.geotools.wfs.GML.Version;
 import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Envelope;
 import org.locationtech.jts.geom.Geometry;
 import org.locationtech.jts.geom.GeometryFactory;
 import org.locationtech.jts.geom.Point;
@@ -56,6 +57,10 @@ import org.opengis.feature.simple.SimpleFeature;
 import org.opengis.feature.simple.SimpleFeatureType;
 import org.opengis.feature.type.AttributeDescriptor;
 import org.opengis.feature.type.Name;
+import org.opengis.referencing.FactoryException;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
 
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpHandler;
@@ -72,6 +77,7 @@ public class WfsHttpHandler implements HttpHandler {
        final static String OUTPUT_FORMAT = "outputFormat";
        final static String TYPE_NAMES = "typeNames";
        final static String CQL_FILTER = "cql_filter";
+       final static String BBOX = "bbox";
 
        private final Map<QName, FeatureAdapter> featureAdapters = new HashMap<>();
 
@@ -81,6 +87,7 @@ public class WfsHttpHandler implements HttpHandler {
                ContentSession session = HttpServerUtils.getContentSession(contentRepository, exchange);
                // Content content = session.get(path);
 
+               // PARAMETERS
                Map<String, List<String>> parameters = HttpServerUtils.parseParameters(exchange);
                String cql = getKvpParameter(parameters, CQL_FILTER);
                String typeNamesStr = getKvpParameter(parameters, TYPE_NAMES);
@@ -88,6 +95,40 @@ public class WfsHttpHandler implements HttpHandler {
                if (outputFormat == null) {
                        outputFormat = "application/json";
                }
+               String bboxStr = getKvpParameter(parameters, BBOX);
+               log.debug(bboxStr);
+               final Envelope bbox;
+               if (bboxStr != null) {
+                       String srs;
+                       String[] arr = bboxStr.split(",");
+                       // TODO check SRS and convert to WGS84
+                       double minLat = Double.parseDouble(arr[0]);
+                       double minLon = Double.parseDouble(arr[1]);
+                       double maxLat = Double.parseDouble(arr[2]);
+                       double maxLon = Double.parseDouble(arr[3]);
+                       if (arr.length == 5) {
+                               srs = arr[4];
+                       } else {
+                               srs = null;
+                       }
+
+                       if (srs != null) {
+                               try {
+                                       CoordinateReferenceSystem sourceCRS = CRS.decode(srs);
+                                       CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:4326");
+                                       MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS, true);
+                                       bbox = org.geotools.geometry.jts.JTS.transform(
+                                                       new Envelope(new Coordinate(minLat, minLon), new Coordinate(maxLat, maxLon)), transform);
+                               } catch (FactoryException | TransformException e) {
+                                       throw new IllegalArgumentException("Cannot convert bounding box", e);
+                                       // bbox = null;
+                               }
+                       } else {
+                               bbox = new Envelope(new Coordinate(minLat, minLon), new Coordinate(maxLat, maxLon));
+                       }
+               } else {
+                       bbox = null;
+               }
 
                switch (outputFormat) {
                case "application/json" -> {
@@ -114,13 +155,13 @@ public class WfsHttpHandler implements HttpHandler {
                if (typeNames.size() > 1)
                        throw new UnsupportedOperationException("Only one type name is currently supported");
 
+               // QUERY
                Stream<Content> res = session.search((search) -> {
                        if (cql != null) {
                                CqlUtils.filter(search.from(path), cql);
                        } else {
                                search.from(path);
                        }
-//                     search.getWhere().any((f) -> {
                        for (QName typeName : typeNames) {
                                FeatureAdapter featureAdapter = featureAdapters.get(typeName);
                                if (featureAdapter == null)
@@ -128,7 +169,13 @@ public class WfsHttpHandler implements HttpHandler {
                                // f.isContentClass(typeName);
                                featureAdapter.addConstraintsForFeature((AndFilter) search.getWhere(), typeName);
                        }
-//                     });
+
+                       if (bbox != null) {
+                               search.getWhere().gte(EntityName.minLat, bbox.getMinX());
+                               search.getWhere().gte(EntityName.minLon, bbox.getMinY());
+                               search.getWhere().lte(EntityName.maxLat, bbox.getMaxX());
+                               search.getWhere().lte(EntityName.maxLon, bbox.getMaxY());
+                       }
                });
 
                exchange.sendResponseHeaders(200, 0);
@@ -230,7 +277,7 @@ public class WfsHttpHandler implements HttpHandler {
                                if (featureId != null)
                                        generator.write("id", featureId);
 
-//                     GeoJson.writeBBox(generator, defaultGeometry);
+                               GeoJson.writeBBox(generator, defaultGeometry);
                                generator.writeStartObject(GeoJson.GEOMETRY);
                                GeoJson.writeGeometry(generator, defaultGeometry);
                                generator.writeEnd();// geometry object
index 8fad7e26b8ee50a2adc22330a6e6a61aa5e24d01..c66ed3a03164760ec6aa61db61273b68d677e48a 100644 (file)
@@ -28,6 +28,10 @@ public class OpenLayersMapPart extends AbstractGeoJsObject {
                executeMethod(getMethodName(), layerName, styledLayerName);
        }
 
+       public void applyBboxStrategy(String layerName) {
+               executeMethod(getMethodName(), layerName);
+       }
+
        public Layer getLayer(String name) {
                // TODO deal with not found
                String reference = "getLayerByName('" + name + "')";
index 3b60d0b91fba203550c15444d1b79a479e8d5720..122ef36025181b35f7cdc27fdbf582137b55d57c 100644 (file)
@@ -1,5 +1,7 @@
 package org.argeo.app.ol;
 
+import org.argeo.app.ux.js.JsReference;
+
 public class VectorSource extends Source {
 
        public VectorSource(Object... args) {
@@ -7,8 +9,17 @@ public class VectorSource extends Source {
        }
 
        public VectorSource(String url, FeatureFormat format) {
-               setUrl(url);
+               this(url, format, false);
+       }
+
+       public VectorSource(String url, FeatureFormat format, boolean bboxStrategy) {
                setFormat(format);
+               if (bboxStrategy) {
+                       setUrl(url);
+                       getNewOptions().put("strategy", new JsReference(getJsPackage() + ".bbox"));
+               } else {
+                       setUrl(url);
+               }
        }
 
        public void setUrl(String url) {
index 8ed2de35d5295bfda83e589f43e2a14c6a222a98..f607208ab3b449154c4936bdd84efa3732322f85 100644 (file)
@@ -18,4 +18,8 @@ public class View extends AbstractOlObject {
                else
                        executeMethod(getMethodName(), zoom);
        }
+
+//     public void setProjection(String projection) {
+//             doSetValue(getMethodName(), "projection", projection);
+//     }
 }