From: Mathieu Baudier Date: Mon, 25 Sep 2023 10:45:54 +0000 (+0200) Subject: Introduce tight integration with JavaScript classes X-Git-Tag: v2.3.16~20 X-Git-Url: https://git.argeo.org/?p=gpl%2Fargeo-suite.git;a=commitdiff_plain;h=59da7271e876ca8a429beb86b67e7350eef1e1ca Introduce tight integration with JavaScript classes --- diff --git a/js/src/geo/MapPart.js b/js/src/geo/MapPart.js index 5711233..d2f712c 100644 --- a/js/src/geo/MapPart.js +++ b/js/src/geo/MapPart.js @@ -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 index 3425384..0000000 --- a/js/src/geo/OpenLayerTileSources.js +++ /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 diff --git a/js/src/geo/OpenLayersMapPart.js b/js/src/geo/OpenLayersMapPart.js index e1d8642..177ce33 100644 --- a/js/src/geo/OpenLayersMapPart.js +++ b/js/src/geo/OpenLayersMapPart.js @@ -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 index 0000000..820a440 --- /dev/null +++ b/js/src/geo/SentinelCloudless.js @@ -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 diff --git a/js/src/geo/export-package.js b/js/src/geo/export-package.js index c322c28..8aaf937 100644 --- a/js/src/geo/export-package.js +++ b/js/src/geo/export-package.js @@ -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 index 0000000..8645a77 --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/ux/js/AbstractJsObject.java @@ -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 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 index 0000000..b7fc724 --- /dev/null +++ b/org.argeo.app.core/src/org/argeo/app/ux/js/JsClient.java @@ -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 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 index 0000000..9b4e360 --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/geo/ux/AbstractGeoJsObject.java @@ -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"; + } + +} diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/ux/JsImplementation.java b/org.argeo.app.geo/src/org/argeo/app/geo/ux/JsImplementation.java index 3242d68..56b892b 100644 --- a/org.argeo.app.geo/src/org/argeo/app/geo/ux/JsImplementation.java +++ b/org.argeo.app.geo/src/org/argeo/app/geo/ux/JsImplementation.java @@ -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 index 0000000..829b343 --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/geo/ux/OpenLayersMapPart.java @@ -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 index 0000000..36dcfe1 --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/geo/ux/SentinelCloudless.java @@ -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 index 0000000..2806afe --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/ol/AbstractOlObject.java @@ -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() }); + } + + public AbstractOlObject(Map options) { + super(options); + } + + public String getJsPackage() { + return "argeo.tp.ol"; + } + + @SuppressWarnings("unchecked") + protected Map 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) 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 index 0000000..8b8ea92 --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/ol/Layer.java @@ -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 index 0000000..6cc769f --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/ol/OSM.java @@ -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 index 0000000..6ca37e3 --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/ol/OlMap.java @@ -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 index 0000000..50e25da --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/ol/Source.java @@ -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 index 0000000..32f58ac --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/ol/TileLayer.java @@ -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 index 0000000..8ed2de3 --- /dev/null +++ b/org.argeo.app.geo/src/org/argeo/app/ol/View.java @@ -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); + } +} diff --git a/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/MapUiProvider.java b/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/MapUiProvider.java index b758923..55c7d6c 100644 --- a/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/MapUiProvider.java +++ b/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/MapUiProvider.java @@ -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; } } diff --git a/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/SwtJsMapPart.java b/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/SwtJsMapPart.java index 8b557cb..c7e9156 100644 --- a/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/SwtJsMapPart.java +++ b/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/SwtJsMapPart.java @@ -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); diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/chart/SwtJsBarChart.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/chart/SwtJsBarChart.java index 36d80dc..bcfb15f 100644 --- a/swt/org.argeo.app.swt/src/org/argeo/app/swt/chart/SwtJsBarChart.java +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/chart/SwtJsBarChart.java @@ -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) { diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java index f479f96..6282b1e 100644 --- a/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java @@ -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 toDo) { + @Override + public String createJsFunction(String name, Function 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 */ diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultEditionLayer.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultEditionLayer.java index 109dd22..549062f 100644 --- a/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultEditionLayer.java +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/ux/DefaultEditionLayer.java @@ -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; }