Introduce tight integration with JavaScript classes
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 25 Sep 2023 10:45:54 +0000 (12:45 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 25 Sep 2023 10:45:54 +0000 (12:45 +0200)
23 files changed:
js/src/geo/MapPart.js
js/src/geo/OpenLayerTileSources.js [deleted file]
js/src/geo/OpenLayersMapPart.js
js/src/geo/SentinelCloudless.js [new file with mode: 0644]
js/src/geo/export-package.js
org.argeo.app.core/src/org/argeo/app/ux/js/AbstractJsObject.java [new file with mode: 0644]
org.argeo.app.core/src/org/argeo/app/ux/js/JsClient.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/ux/AbstractGeoJsObject.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/ux/JsImplementation.java
org.argeo.app.geo/src/org/argeo/app/geo/ux/OpenLayersMapPart.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/geo/ux/SentinelCloudless.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/AbstractOlObject.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/Layer.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/OSM.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/OlMap.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/Source.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/TileLayer.java [new file with mode: 0644]
org.argeo.app.geo/src/org/argeo/app/ol/View.java [new file with mode: 0644]
swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/MapUiProvider.java
swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/SwtJsMapPart.java
swt/org.argeo.app.swt/src/org/argeo/app/swt/chart/SwtJsBarChart.java
swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java
swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultEditionLayer.java

index 5711233a629767c5647405cbcf40e11b32a2c750..d2f712c629fc7711368146300c84c1ff1f89a812 100644 (file)
@@ -69,4 +69,9 @@ export default class MapPart {
        getMapDivCssClass() {
                throw new Error("Abstract method");
        }
+
+       newObject(js) {
+               const func = new Function(js);
+               return (func());
+       }
 }
diff --git a/js/src/geo/OpenLayerTileSources.js b/js/src/geo/OpenLayerTileSources.js
deleted file mode 100644 (file)
index 3425384..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-
-import WMTS from 'ol/source/WMTS.js';
-import WMTSTileGrid from 'ol/tilegrid/WMTS';
-import { getTopLeft } from 'ol/extent';
-import { getWidth } from 'ol/extent';
-import { get as getProjection } from 'ol/proj';
-
-export class SentinelCloudless extends WMTS {
-       static source_s2CL2019;
-       static EPSG4326 = getProjection('EPSG:4326');
-
-       static resolutions;
-       static matrixIds;
-
-       static {
-               let min_zoom = 6;
-               let max_zoom = 17;
-               let zoomOffset = 1;
-
-               // from https://s2maps.eu/
-               let size = getWidth(this.EPSG4326.getExtent()) / 512;
-               this.resolutions = new Array(max_zoom + zoomOffset);
-               this.matrixIds = new Array(max_zoom + zoomOffset);
-               for (let z = min_zoom; z <= max_zoom; ++z) {
-                       // generate resolutions and matrixIds arrays for this WMTS
-                       this.resolutions[z] = size / Math.pow(2, z);
-                       this.matrixIds[z] = z;
-               }
-       }
-       
-       constructor() {
-               super({
-                       urls: [
-                               "//a.s2maps-tiles.eu/wmts/",
-                               "//b.s2maps-tiles.eu/wmts/",
-                               "//c.s2maps-tiles.eu/wmts/",
-                               "//d.s2maps-tiles.eu/wmts/",
-                               "//e.s2maps-tiles.eu/wmts/"
-                       ],
-                       layer: 's2cloudless-2021',
-                       matrixSet: 'WGS84',
-                       format: 'image/jpeg',
-                       projection: SentinelCloudless.EPSG4326,
-                       tileGrid: new WMTSTileGrid({
-                               origin: getTopLeft(SentinelCloudless.EPSG4326.getExtent()),
-                               resolutions: SentinelCloudless.resolutions,
-                               matrixIds: SentinelCloudless.matrixIds,
-                       }),
-                       style: 'default',
-                       transition: 0,
-                       wrapX: true
-               });
-       }
-}
\ No newline at end of file
index e1d8642c0fd99a8bd85098b1706c2e6af957cb26..177ce33862919d750769ae9029e187d1467df6db 100644 (file)
@@ -20,7 +20,6 @@ import { Style, Icon } from 'ol/style.js';
 import * as SLDReader from '@nieuwlandgeo/sldreader';
 
 import MapPart from './MapPart.js';
-import { SentinelCloudless } from './OpenLayerTileSources.js';
 
 /** OpenLayers implementation of MapPart. */
 export default class OpenLayersMapPart extends MapPart {
@@ -38,12 +37,16 @@ export default class OpenLayersMapPart extends MapPart {
                                //                              new TileLayer({
                                //                                      source: new SentinelCloudless(),
                                //                              }),
-                               new TileLayer({
-                                       source: new OSM(),
-                                       opacity: 0.4,
-                                       transition: 0,
-                               }),
+//                                                             new TileLayer({
+//                                                                     source: new OSM(),
+//                                                                     opacity: 0.4,
+//                                                                     transition: 0,
+//                                                             }),
                        ],
+//                     view: new View({
+//                             center: [0, 0],
+//                             zoom: 2,
+//                     }),
                        target: this.getMapName(),
                });
        }
@@ -95,6 +98,15 @@ export default class OpenLayersMapPart extends MapPart {
                this.#map.addLayer(vectorLayer);
        }
 
+       addLayer(js) {
+               const func = new Function(js);
+               const layer = (func)();
+               this.#map.addLayer(layer);
+       }
+
+       getMap() {
+               return this.#map;
+       }
 
        /* CALLBACKS */
        enableFeatureSingleClick() {
diff --git a/js/src/geo/SentinelCloudless.js b/js/src/geo/SentinelCloudless.js
new file mode 100644 (file)
index 0000000..820a440
--- /dev/null
@@ -0,0 +1,54 @@
+
+import WMTS from 'ol/source/WMTS.js';
+import WMTSTileGrid from 'ol/tilegrid/WMTS';
+import { getTopLeft } from 'ol/extent';
+import { getWidth } from 'ol/extent';
+import { get as getProjection } from 'ol/proj';
+
+export default class SentinelCloudless extends WMTS {
+       static source_s2CL2019;
+       static EPSG4326 = getProjection('EPSG:4326');
+
+       static resolutions;
+       static matrixIds;
+
+       static {
+               let min_zoom = 6;
+               let max_zoom = 17;
+               let zoomOffset = 1;
+
+               // from https://s2maps.eu/
+               let size = getWidth(this.EPSG4326.getExtent()) / 512;
+               this.resolutions = new Array(max_zoom + zoomOffset);
+               this.matrixIds = new Array(max_zoom + zoomOffset);
+               for (let z = min_zoom; z <= max_zoom; ++z) {
+                       // generate resolutions and matrixIds arrays for this WMTS
+                       this.resolutions[z] = size / Math.pow(2, z);
+                       this.matrixIds[z] = z;
+               }
+       }
+       
+       constructor() {
+               super({
+                       urls: [
+                               "//a.s2maps-tiles.eu/wmts/",
+                               "//b.s2maps-tiles.eu/wmts/",
+                               "//c.s2maps-tiles.eu/wmts/",
+                               "//d.s2maps-tiles.eu/wmts/",
+                               "//e.s2maps-tiles.eu/wmts/"
+                       ],
+                       layer: 's2cloudless-2021',
+                       matrixSet: 'WGS84',
+                       format: 'image/jpeg',
+                       projection: SentinelCloudless.EPSG4326,
+                       tileGrid: new WMTSTileGrid({
+                               origin: getTopLeft(SentinelCloudless.EPSG4326.getExtent()),
+                               resolutions: SentinelCloudless.resolutions,
+                               matrixIds: SentinelCloudless.matrixIds,
+                       }),
+                       style: 'default',
+                       transition: 0,
+                       wrapX: true
+               });
+       }
+}
\ No newline at end of file
index c322c28111f71bd59d7dfcfddb7f06e7e2b788ec..8aaf93727930f2d044152eccfc7bfa52d7144dab 100644 (file)
@@ -1,4 +1,13 @@
 import OpenLayersMapPart from './OpenLayersMapPart.js';
+import SentinelCloudless from './SentinelCloudless.js';
+
+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 VectorSource from 'ol/source/Vector.js';
+import VectorLayer from 'ol/layer/Vector.js';
+import GeoJSON from 'ol/format/GeoJSON.js';
 
 // PSEUDO PACKAGE
 if (typeof globalThis.argeo === 'undefined')
@@ -8,7 +17,23 @@ if (typeof globalThis.argeo.app === 'undefined')
 if (typeof globalThis.argeo.app.geo === 'undefined')
        globalThis.argeo.app.geo = {};
 
+// THIRD PARTY
+if (typeof globalThis.argeo.tp === 'undefined')
+       globalThis.argeo.tp = {};
+if (typeof globalThis.argeo.tp.ol === 'undefined')
+       globalThis.argeo.tp.ol = {};
+
 // PUBLIC CLASSES
 globalThis.argeo.app.geo.OpenLayersMapPart = OpenLayersMapPart;
+globalThis.argeo.app.geo.SentinelCloudless = SentinelCloudless;
+
+globalThis.argeo.tp.ol.Map = Map;
+globalThis.argeo.tp.ol.View = View;
+globalThis.argeo.tp.ol.TileLayer = TileLayer;
+globalThis.argeo.tp.ol.OSM = OSM;
+globalThis.argeo.tp.ol.VectorSource = VectorSource;
+globalThis.argeo.tp.ol.VectorLayer = VectorLayer;
+globalThis.argeo.tp.ol.GeoJSON = GeoJSON;
 
 "use strict";
+
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/js/AbstractJsObject.java b/org.argeo.app.core/src/org/argeo/app/ux/js/AbstractJsObject.java
new file mode 100644 (file)
index 0000000..8645a77
--- /dev/null
@@ -0,0 +1,114 @@
+package org.argeo.app.ux.js;
+
+import java.util.Optional;
+
+public abstract class AbstractJsObject {
+       /**
+        * JavaScript expression returning a reference to the object. It can be either a
+        * variable or a function call. If it is not set the object is assumed to be a
+        * new.
+        */
+       private String reference;
+
+       private JsClient jsClient;
+
+       private Object[] jsConstructorArgs;
+
+//     public AbstractJsObject(JsClient jsClient, String reference) {
+//             Objects.requireNonNull(jsClient);
+//             Objects.requireNonNull(reference, "JS reference cannot be null");
+//             this.jsClient = jsClient;
+//             this.reference = reference;
+//     }
+
+       public AbstractJsObject(Object... args) {
+               if (args.length == 2 && args[0] instanceof JsClient jsClient) {
+                       this.jsClient = jsClient;
+                       this.reference = args[1].toString();
+               } else {
+                       this.jsConstructorArgs = args;
+               }
+       }
+
+       public abstract String getJsPackage();
+
+       public void create(JsClient jsClient, String varName) {
+               if (!isNew())
+                       throw new IllegalStateException("JS object " + getJsClassName() + " is not new");
+               if (isFunctionReference())
+                       throw new IllegalStateException(
+                                       "JS object " + getJsClassName() + " cannot be created since it is a function reference");
+               jsClient.execute(jsClient.getJsVarName(varName) + " = " + newJs() + ";");
+               reference = varName;
+               this.jsClient = jsClient;
+       }
+
+       public void delete() {
+               if (isNew())
+                       throw new IllegalStateException(
+                                       "JS object " + getJsClassName() + " cannot be deleted since it is anonymous");
+               if (isFunctionReference())
+                       throw new IllegalStateException(
+                                       "JS object " + getJsClassName() + " cannot be deleted since it is a function reference");
+               jsClient.execute(reference + " = undefined; delete " + reference + ";");
+       }
+
+       public boolean isNew() {
+               return reference == null;
+       }
+
+       public boolean isFunctionReference() {
+               return !isNew() && !getReference().endsWith(")");
+       }
+
+       public String getReference() {
+               return reference;
+       }
+
+       String getJsReference() {
+               return jsClient.getJsVarName(reference);
+       }
+
+       protected String newJs() {
+               StringBuilder sb = new StringBuilder();
+               sb.append("new ");
+               sb.append(getJsClassName());
+               sb.append("(");
+               sb.append(JsClient.toJsArgs(jsConstructorArgs));
+               sb.append(")");
+               return sb.toString();
+       }
+
+       public String getJsClassName() {
+               return getJsPackage() + "." + getClass().getSimpleName();
+       }
+
+       public Object callMethod(String methodName, Object... args) {
+               return jsClient.callMethod(getJsReference(), methodName + "(" + JsClient.toJsArgs(args) + ")");
+       }
+
+       public void executeMethod(String methodName, Object... args) {
+               jsClient.executeMethod(getJsReference(), methodName + "(" + JsClient.toJsArgs(args) + ")");
+       }
+
+       protected String getMethodName() {
+               StackWalker walker = StackWalker.getInstance();
+               Optional<String> methodName = walker.walk(frames -> {
+                       return frames.skip(1).findFirst().map(StackWalker.StackFrame::getMethodName);
+               });
+               return methodName.orElseThrow();
+       }
+
+       protected JsClient getJsClient() {
+               return jsClient;
+       }
+
+       protected Object[] getJsConstructorArgs() {
+               return jsConstructorArgs;
+       }
+
+       protected void setJsConstructorArgs(Object[] jsConstructorArgs) {
+               this.jsConstructorArgs = jsConstructorArgs;
+       }
+
+}
diff --git a/org.argeo.app.core/src/org/argeo/app/ux/js/JsClient.java b/org.argeo.app.core/src/org/argeo/app/ux/js/JsClient.java
new file mode 100644 (file)
index 0000000..b7fc724
--- /dev/null
@@ -0,0 +1,131 @@
+package org.argeo.app.ux.js;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.function.Function;
+
+/**
+ * A remote JavaScript view (typically in a web browser) which is tightly
+ * integrated with a local UX part.
+ */
+public interface JsClient {
+
+       /*
+        * TO IMPLEMENT
+        */
+
+       /**
+        * Execute this JavaScript on the client side after making sure that the page
+        * has been loaded and the map object has been created.
+        * 
+        * @param js   the JavaScript code, possibly formatted according to
+        *             {@link String#format}, with {@link Locale#ROOT} as locale (for
+        *             stability of decimal separator, as expected by JavaScript.
+        * @param args the optional arguments of
+        *             {@link String#format(String, Object...)}
+        */
+       Object evaluate(String js, Object... args);
+
+       /**
+        * Executes this JavaScript without expecting a return value.
+        * 
+        * @param js   the JavaScript code, possibly formatted according to
+        *             {@link String#format}, with {@link Locale#ROOT} as locale (for
+        *             stability of decimal separator, as expected by JavaScript.
+        * @param args the optional arguments of
+        *             {@link String#format(String, Object...)}
+        */
+       void execute(String js, Object... args);
+
+       /** @return the globally usable function name. */
+       String createJsFunction(String name, Function<Object[], Object> toDo);
+
+       /** Get a global variable name. */
+       public String getJsVarName(String name);
+
+       /*
+        * DEFAULTS
+        */
+
+       default Object callMethod(String jsObject, String methodCall, Object... args) {
+               return evaluate(jsObject + '.' + methodCall, args);
+       }
+
+       default void executeMethod(String jsObject, String methodCall, Object... args) {
+               execute(jsObject + '.' + methodCall, args);
+       }
+
+       /*
+        * UTILITIES
+        */
+
+       static String toJsValue(Object o) {
+               if (o instanceof CharSequence)
+                       return '\"' + o.toString() + '\"';
+               else if (o instanceof Number)
+                       return o.toString();
+               else if (o instanceof Boolean)
+                       return o.toString();
+               else if (o instanceof Map map)
+                       return toJsMap(map);
+               else if (o instanceof Object[] arr)
+                       return toJsArray(arr);
+               else if (o instanceof int[] arr)
+                       return toJsArray(arr);
+               else if (o instanceof long[] arr)
+                       return toJsArray(arr);
+               else if (o instanceof double[] arr)
+                       return toJsArray(arr);
+               else if (o instanceof AbstractJsObject jsObject) {
+                       if (jsObject.isNew())
+                               return jsObject.newJs();
+                       else
+                               return jsObject.getJsReference();
+               } else
+                       return '\"' + o.toString() + '\"';
+       }
+
+       static String toJsArgs(Object... arr) {
+               StringJoiner sj = new StringJoiner(",");
+               for (Object o : arr) {
+                       sj.add(toJsValue(o));
+               }
+               return sj.toString();
+       }
+
+       static String toJsArray(Object... arr) {
+               StringJoiner sj = new StringJoiner(",", "[", "]");
+               for (Object o : arr) {
+                       sj.add(toJsValue(o));
+               }
+               return sj.toString();
+       }
+
+       static String toJsArray(String... arr) {
+               return toJsArray((Object[]) arr);
+       }
+
+       static String toJsArray(double... arr) {
+               return Arrays.toString(arr);
+       }
+
+       static String toJsArray(long... arr) {
+               return Arrays.toString(arr);
+       }
+
+       static String toJsArray(int... arr) {
+               return Arrays.toString(arr);
+       }
+
+       static String toJsMap(Map<?, ?> map) {
+               StringJoiner sj = new StringJoiner(",", "{", "}");
+               // TODO escape forbidden characters
+               for (Object key : map.keySet()) {
+                       sj.add("'" + key + "':" + toJsValue(map.get(key)));
+               }
+               return sj.toString();
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/ux/AbstractGeoJsObject.java b/org.argeo.app.geo/src/org/argeo/app/geo/ux/AbstractGeoJsObject.java
new file mode 100644 (file)
index 0000000..9b4e360
--- /dev/null
@@ -0,0 +1,15 @@
+package org.argeo.app.geo.ux;
+
+import org.argeo.app.ux.js.AbstractJsObject;
+
+public class AbstractGeoJsObject extends AbstractJsObject {
+       public AbstractGeoJsObject(Object... args) {
+               super(args);
+       }
+
+       @Override
+       public String getJsPackage() {
+               return "argeo.app.geo";
+       }
+
+}
index 3242d68cd69716be86e9d7833aa66d79b6120447..56b892b5fa56ae5fdc0f59e3fbb98afab3e4a95c 100644 (file)
@@ -2,7 +2,7 @@ package org.argeo.app.geo.ux;
 
 /** Known JavaScript implementations for this package. */
 public enum JsImplementation {
-       OPENLAYERS_MAP_PART("globalThis.argeo.app.geo.OpenLayersMapPart");
+       OPENLAYERS_MAP_PART("argeo.app.geo.OpenLayersMapPart");
 
        private String jsClass;
 
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/ux/OpenLayersMapPart.java b/org.argeo.app.geo/src/org/argeo/app/geo/ux/OpenLayersMapPart.java
new file mode 100644 (file)
index 0000000..829b343
--- /dev/null
@@ -0,0 +1,19 @@
+package org.argeo.app.geo.ux;
+
+import org.argeo.app.ol.OlMap;
+import org.argeo.app.ux.js.AbstractJsObject;
+import org.argeo.app.ux.js.JsClient;
+
+public class OpenLayersMapPart extends AbstractGeoJsObject {
+       private String mapPartName;
+
+       public OpenLayersMapPart(JsClient jsClient, String mapPartName) {
+               super(mapPartName);
+               this.mapPartName = mapPartName;
+               create(jsClient, mapPartName);
+       }
+
+       public OlMap getMap() {
+               return new OlMap(getJsClient(), getReference() + ".getMap()");
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/ux/SentinelCloudless.java b/org.argeo.app.geo/src/org/argeo/app/geo/ux/SentinelCloudless.java
new file mode 100644 (file)
index 0000000..36dcfe1
--- /dev/null
@@ -0,0 +1,16 @@
+package org.argeo.app.geo.ux;
+
+import org.argeo.app.ol.Source;
+
+public class SentinelCloudless extends Source {
+
+       public SentinelCloudless(Object... args) {
+               super(args);
+       }
+
+       @Override
+       public String getJsPackage() {
+               return "argeo.app.geo";
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/AbstractOlObject.java b/org.argeo.app.geo/src/org/argeo/app/ol/AbstractOlObject.java
new file mode 100644 (file)
index 0000000..2806afe
--- /dev/null
@@ -0,0 +1,31 @@
+package org.argeo.app.ol;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.argeo.app.ux.js.AbstractJsObject;
+
+public abstract class AbstractOlObject extends AbstractJsObject {
+
+       public AbstractOlObject(Object... args) {
+               super(args.length > 0 ? args : new Object[] { new HashMap<String, Object>() });
+       }
+
+       public AbstractOlObject(Map<String, Object> options) {
+               super(options);
+       }
+
+       public String getJsPackage() {
+               return "argeo.tp.ol";
+       }
+
+       @SuppressWarnings("unchecked")
+       protected Map<String, Object> getNewOptions() {
+               if (!isNew())
+                       throw new IllegalStateException("Object " + getJsClassName() + " is not new");
+               Object[] args = getJsConstructorArgs();
+               if (args.length != 1 || !(args[0] instanceof Map))
+                       throw new IllegalStateException("Object " + getJsClassName() + " has no available options");
+               return (Map<String, Object>) args[0];
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/Layer.java b/org.argeo.app.geo/src/org/argeo/app/ol/Layer.java
new file mode 100644 (file)
index 0000000..8b8ea92
--- /dev/null
@@ -0,0 +1,42 @@
+package org.argeo.app.ol;
+
+import java.util.Objects;
+
+public abstract class Layer extends AbstractOlObject {
+
+       public Layer(Object... args) {
+               super(args);
+       }
+
+       public void setOpacity(double opacity) {
+               if (opacity < 0 || opacity > 1)
+                       throw new IllegalArgumentException("Opacity must be between 0 and 1");
+               if (isNew())
+                       getNewOptions().put("opacity", opacity);
+               else
+                       executeMethod(getMethodName(), opacity);
+       }
+
+       public void setSource(Source source) {
+               Objects.requireNonNull(source);
+               if (isNew())
+                       getNewOptions().put("source", source);
+               else
+                       executeMethod(getMethodName(), source);
+       }
+
+       public void setMinResolution(long minResolution) {
+               if (isNew())
+                       getNewOptions().put("minResolution", minResolution);
+               else
+                       executeMethod(getMethodName(), minResolution);
+       }
+
+       public void setMaxResolution(long maxResolution) {
+               if (isNew())
+                       getNewOptions().put("maxResolution", maxResolution);
+               else
+                       executeMethod(getMethodName(), maxResolution);
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/OSM.java b/org.argeo.app.geo/src/org/argeo/app/ol/OSM.java
new file mode 100644 (file)
index 0000000..6cc769f
--- /dev/null
@@ -0,0 +1,9 @@
+package org.argeo.app.ol;
+
+public class OSM extends Source {
+
+       public OSM(Object... args) {
+               super(args);
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/OlMap.java b/org.argeo.app.geo/src/org/argeo/app/ol/OlMap.java
new file mode 100644 (file)
index 0000000..6ca37e3
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.app.ol;
+
+public class OlMap extends AbstractOlObject {
+
+       public OlMap(Object... args) {
+               super(args);
+       }
+
+       public void addLayer(Layer layer) {
+               executeMethod(getMethodName(), layer);
+       }
+
+       public View getView() {
+               return new View(getJsClient(), getReference() + ".getView()");
+       }
+
+       @Override
+       public String getJsClassName() {
+               return getJsPackage() + ".Map";
+       }
+
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/Source.java b/org.argeo.app.geo/src/org/argeo/app/ol/Source.java
new file mode 100644 (file)
index 0000000..50e25da
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.app.ol;
+
+public class Source extends AbstractOlObject {
+
+       public Source(Object... args) {
+               super(args);
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/TileLayer.java b/org.argeo.app.geo/src/org/argeo/app/ol/TileLayer.java
new file mode 100644 (file)
index 0000000..32f58ac
--- /dev/null
@@ -0,0 +1,11 @@
+package org.argeo.app.ol;
+
+public class TileLayer extends Layer {
+       public TileLayer(Object... args) {
+               super(args);
+       }
+
+       public TileLayer(Source source) {
+               setSource(source);
+       }
+}
diff --git a/org.argeo.app.geo/src/org/argeo/app/ol/View.java b/org.argeo.app.geo/src/org/argeo/app/ol/View.java
new file mode 100644 (file)
index 0000000..8ed2de3
--- /dev/null
@@ -0,0 +1,21 @@
+package org.argeo.app.ol;
+
+public class View extends AbstractOlObject {
+       public View(Object... args) {
+               super(args);
+       }
+
+       public void setCenter(int[] coord) {
+               if (isNew())
+                       getNewOptions().put("center", coord);
+               else
+                       executeMethod(getMethodName(), new Object[] { coord });
+       }
+
+       public void setZoom(int zoom) {
+               if (isNew())
+                       getNewOptions().put("zoom", zoom);
+               else
+                       executeMethod(getMethodName(), zoom);
+       }
+}
index b75892371c68f5b65416d704f230364a8b239681..55c7d6cfb269467d45bc826ec1e61db2fed243c8 100644 (file)
@@ -1,6 +1,13 @@
 package org.argeo.app.geo.swt;
 
 import org.argeo.api.acr.Content;
+import org.argeo.app.geo.ux.OpenLayersMapPart;
+import org.argeo.app.geo.ux.SentinelCloudless;
+import org.argeo.app.ol.Layer;
+import org.argeo.app.ol.OSM;
+import org.argeo.app.ol.TileLayer;
+import org.argeo.app.swt.js.SwtBrowserJsPart;
+import org.argeo.app.ux.js.JsClient;
 import org.argeo.cms.swt.acr.SwtUiProvider;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
@@ -10,14 +17,27 @@ public class MapUiProvider implements SwtUiProvider {
 
        @Override
        public Control createUiPart(Composite parent, Content context) {
-               SwtJsMapPart map = new SwtJsMapPart("defaultOverviewMap", parent, 0);
-               map.setCenter(13.404954, 52.520008); // Berlin
-//             map.setCenter(-74.00597, 40.71427); // NYC
-//             map.addPoint(-74.00597, 40.71427, null);
-               map.setZoom(6);
-               // map.addUrlLayer("https://openlayers.org/en/v4.6.5/examples/data/geojson/countries.geojson",
-               // Format.GEOJSON);
-               return map.getControl();
+               JsClient jsClient = new SwtBrowserJsPart(parent, 0, "/pkg/org.argeo.app.js/geo.html");
+               OpenLayersMapPart mapPart = new OpenLayersMapPart(jsClient, "defaultOverviewMap");
+               mapPart.getMap().getView().setCenter(new int[] { 0, 0 });
+               mapPart.getMap().getView().setZoom(6);
+
+               Layer satelliteLayer = new TileLayer(new SentinelCloudless());
+               satelliteLayer.setMaxResolution(200);
+               mapPart.getMap().addLayer(satelliteLayer);
+               TileLayer baseLayer = new TileLayer();
+               baseLayer.setSource(new OSM());
+               baseLayer.setOpacity(0.5);
+               mapPart.getMap().addLayer(baseLayer);
+
+//             SwtJsMapPart map = new SwtJsMapPart("defaultOverviewMap", parent, 0);
+//             map.setCenter(13.404954, 52.520008); // Berlin
+////           map.setCenter(-74.00597, 40.71427); // NYC
+////           map.addPoint(-74.00597, 40.71427, null);
+//             map.setZoom(6);
+//             // map.addUrlLayer("https://openlayers.org/en/v4.6.5/examples/data/geojson/countries.geojson",
+//             // Format.GEOJSON);
+               return parent;
        }
 
 }
index 8b557cb26ff30b782d0d2c1f2803916efba1121e..c7e915651311176c9f2f08f749b7b9b10e7bf68c 100644 (file)
@@ -48,6 +48,11 @@ public class SwtJsMapPart extends SwtBrowserJsPart implements MapPart {
                executeMapMethod("addUrlLayer('%s', '%s', '%s', true)", url, format.name(), style);
        }
 
+       public void addLayer() {
+               //executeMapMethod("addLayer(\"return new argeo.app.geo.TileLayer({source: new argeo.app.geo.OSM()})\")");
+               executeMapMethod("getMap().addLayer(new argeo.tp.ol.TileLayer({source: new argeo.tp.ol.OSM()}))");
+       }
+
        @Override
        public void setZoom(int zoom) {
                executeMapMethod("setZoom(%d)", zoom);
index 36d80dc8f1bfd29952a1b79a7b2288aca85490ee..bcfb15fc7a94d71f0166a74c13f4a1e6552ef567 100644 (file)
@@ -2,6 +2,7 @@ package org.argeo.app.swt.chart;
 
 import java.io.StringWriter;
 
+import org.argeo.app.ux.js.JsClient;
 import org.eclipse.swt.widgets.Composite;
 
 import jakarta.json.Json;
@@ -19,19 +20,19 @@ public class SwtJsBarChart extends AbstractJsChart {
        }
 
        public void setLabels(String[] labels) {
-               executeChartMethod("setLabels(%s)", toJsArray(labels));
+               executeChartMethod("setLabels(%s)", JsClient.toJsArray(labels));
        }
 
        public void addDataset(String label, int[] values) {
-               executeChartMethod("addDataset('%s', %s)", label, toJsArray(values));
+               executeChartMethod("addDataset('%s', %s)", label, JsClient.toJsArray(values));
        }
 
        public void setData(String[] labels, String label, int[] values) {
-               executeChartMethod("setData(%s, '%s', %s)", toJsArray(labels), label, toJsArray(values));
+               executeChartMethod("setData(%s, '%s', %s)", JsClient.toJsArray(labels), label, JsClient.toJsArray(values));
        }
 
        public void setDatasets(String[] labels, String[] label, int[][] values) {
-               executeChartMethod("setDatasets(%s, %s)", toJsArray(labels), toDatasets(label, values));
+               executeChartMethod("setDatasets(%s, %s)", JsClient.toJsArray(labels), toDatasets(label, values));
        }
 
        protected String toDatasets(String[] label, int[][] values) {
index f479f962d5ecbba6922058202efe6c561e8b4dbe..6282b1e30f1fbac8959cb2730e3c82bbc54cbc78 100644 (file)
@@ -1,16 +1,15 @@
 package org.argeo.app.swt.js;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
-import java.util.StringJoiner;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
 import org.argeo.api.cms.CmsLog;
+import org.argeo.app.ux.js.JsClient;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.browser.Browser;
 import org.eclipse.swt.browser.BrowserFunction;
@@ -26,7 +25,7 @@ import org.eclipse.swt.widgets.Display;
  * A part using a {@link Browser} and remote JavaScript components on the client
  * side.
  */
-public class SwtBrowserJsPart {
+public class SwtBrowserJsPart implements JsClient {
        private final static CmsLog log = CmsLog.getLog(SwtBrowserJsPart.class);
 
        private final static String GLOBAL_THIS_ = "globalThis.";
@@ -111,17 +110,8 @@ public class SwtBrowserJsPart {
         * JAVASCRIPT ACCESS
         */
 
-       /**
-        * Execute this JavaScript on the client side after making sure that the page
-        * has been loaded and the map object has been created.
-        * 
-        * @param js   the JavaScript code, possibly formatted according to
-        *             {@link String#format}, with {@link Locale#ROOT} as locale (for
-        *             stability of decimal separator, as expected by JavaScript.
-        * @param args the optional arguments of
-        *             {@link String#format(String, Object...)}
-        */
-       protected Object evaluate(String js, Object... args) {
+       @Override
+       public Object evaluate(String js, Object... args) {
                assert browser.getDisplay().equals(Display.findDisplay(Thread.currentThread())) : "Not the proper UI thread.";
                if (!readyStage.isDone())
                        throw new IllegalStateException("Methods returning a result can only be called after UI initilaisation.");
@@ -133,7 +123,8 @@ public class SwtBrowserJsPart {
                return result;
        }
 
-       protected void execute(String js, Object... args) {
+       @Override
+       public void execute(String js, Object... args) {
                if (readyStage.isDone()) {
                        boolean success = browser.execute(String.format(Locale.ROOT, js, args));
                        if (!success)
@@ -147,8 +138,8 @@ public class SwtBrowserJsPart {
                }
        }
 
-       /** @return the globally usable function name. */
-       protected String createJsFunction(String name, Function<Object[], Object> toDo) {
+       @Override
+       public String createJsFunction(String name, Function<Object[], Object> toDo) {
                // browser functions must be directly on window (RAP specific)
                new BrowserFunction(browser, name) {
 
@@ -171,53 +162,11 @@ public class SwtBrowserJsPart {
                browser.execute(String.format(Locale.ROOT, js, args));
        }
 
-       protected Object callMethod(String jsObject, String methodCall, Object... args) {
-               return evaluate(jsObject + '.' + methodCall, args);
-       }
-
-       protected void executeMethod(String jsObject, String methodCall, Object... args) {
-               execute(jsObject + '.' + methodCall, args);
-       }
-
-       protected String getJsVarName(String name) {
+       @Override
+       public String getJsVarName(String name) {
                return GLOBAL_THIS_ + name;
        }
 
-       protected static String toJsArray(int... arr) {
-               return Arrays.toString(arr);
-       }
-
-       protected static String toJsArray(long... arr) {
-               return Arrays.toString(arr);
-       }
-
-       protected static String toJsArray(double... arr) {
-               return Arrays.toString(arr);
-       }
-
-       protected static String toJsArray(String... arr) {
-               return toJsArray((Object[]) arr);
-       }
-
-       protected static String toJsArray(Object... arr) {
-               StringJoiner sj = new StringJoiner(",", "[", "]");
-               for (Object o : arr) {
-                       sj.add(toJsValue(o));
-               }
-               return sj.toString();
-       }
-
-       protected static String toJsValue(Object o) {
-               if (o instanceof CharSequence)
-                       return '\"' + o.toString() + '\"';
-               else if (o instanceof Number)
-                       return o.toString();
-               else if (o instanceof Boolean)
-                       return o.toString();
-               else
-                       return '\"' + o.toString() + '\"';
-       }
-
        /*
         * ACCESSORS
         */
index 109dd22d4ffe88188887ff25369b3eea7688ff52..549062f715739ffd43672878dbb6ae16e8b1b8d8 100644 (file)
@@ -16,6 +16,7 @@ import org.argeo.cms.swt.acr.SwtUiProvider;
 import org.argeo.cms.util.LangUtils;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
@@ -59,6 +60,8 @@ public class DefaultEditionLayer implements SwtAppLayer {
                } else {
                        if (this.workArea != null) {
                                Composite area = new Composite(parent, SWT.NONE);
+                               // we set fill layout by default but it can be overridden
+                               area.setLayout(new FillLayout());
                                this.workArea.createUiPart(area, context);
                                return area;
                        }