--- /dev/null
+import * as SLDReader from '@nieuwlandgeo/sldreader';
+
+export default class LayerStyles {
+ #sld;
+
+}
\ No newline at end of file
/** The OpenLayers Map. */
#map;
+ /** Styled layer descriptor */
+ #sld;
+
/** Externally added callback functions. */
callbacks = {};
// 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,
-// }),
+ // view: new View({
+ // center: [0, 0],
+ // zoom: 2,
+ // }),
target: this.getMapName(),
});
}
return this.#map;
}
+ getLayerByName(name) {
+ let layers = this.#map.getLayers();
+ for (let i = 0; i < layers.getLength(); i++) {
+ let layer = layers.item(i);
+ let n = layer.get('name');
+ if (n !== undefined) {
+ if (name === n)
+ return layer;
+ }
+ }
+ return undefined;
+ }
+
/* CALLBACKS */
enableFeatureSingleClick() {
// we cannot use 'this' in the function provided to OpenLayers
//
// SLD STYLING
//
+
+ setSld(xml) {
+ this.#sld = SLDReader.Reader(xml);
+ }
+
+ /** Get a FeatureTypeStyle (https://nieuwlandgeo.github.io/SLDReader/api.html#FeatureTypeStyle). */
+ getFeatureTypeStyle(styledLayerName, styleName) {
+ const sldLayer = SLDReader.getLayer(this.#sld, styledLayerName);
+ const style = styleName === undefined ? SLDReader.getStyle(sldLayer) : SLDReader.getStyle(sldLayer, styleName);
+ // OpenLayers can only use one definition
+ const featureTypeStyle = style.featuretypestyles[0];
+ return featureTypeStyle;
+ }
+
+ applyStyle(layerName, styledLayerName, styleName) {
+ const layer = this.getLayerByName(layerName);
+ const featureTypeStyle = this.getFeatureTypeStyle(styledLayerName, styleName);
+ const viewProjection = this.#map.getView().getProjection();
+ const olStyleFunction = SLDReader.createOlStyleFunction(featureTypeStyle, {
+ // Use the convertResolution option to calculate a more accurate resolution.
+ convertResolution: viewResolution => {
+ const viewCenter = this.#map.getView().getCenter();
+ return getPointResolution(viewProjection, viewResolution, viewCenter);
+ },
+ // If you use point icons with an ExternalGraphic, you have to use imageLoadCallback
+ // to update the vector layer when an image finishes loading.
+ // If you do not do this, the image will only be visible after next layer pan/zoom.
+ imageLoadedCallback: () => {
+ layer.changed();
+ },
+ });
+ layer.setStyle(olStyleFunction);
+ }
+
#applySLD(vectorLayer, text) {
const sldObject = SLDReader.Reader(text);
const sldLayer = SLDReader.getLayer(sldObject);
import java.util.Locale;
import java.util.Map;
import java.util.StringJoiner;
+import java.util.concurrent.CompletionStage;
import java.util.function.Function;
/**
/** Get a global variable name. */
public String getJsVarName(String name);
+ /**
+ * Completion stage when the client is ready (typically the page has loaded in
+ * the browser).
+ */
+ CompletionStage<Boolean> getReadyStage();
+
/*
* DEFAULTS
*/
execute(jsObject + '.' + methodCall, args);
}
+ default boolean isInstanceOf(String reference, String jsClass) {
+ return (Boolean) evaluate(getJsVarName(reference) + " instanceof " + jsClass);
+ }
+
/*
* UTILITIES
*/
static String toJsValue(Object o) {
if (o instanceof CharSequence)
- return '\"' + o.toString() + '\"';
+ return '\'' + o.toString() + '\'';
else if (o instanceof Number)
return o.toString();
else if (o instanceof Boolean)
else
return jsObject.getJsReference();
} else
- return '\"' + o.toString() + '\"';
+ return '\'' + o.toString() + '\'';
}
static String toJsArgs(Object... arr) {
return sj.toString();
}
+ static String escapeQuotes(String str) {
+ return str.replace("'", "\\'").replace("\"", "\\\"");
+ }
+
}
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Graphic;
+import org.geotools.styling.NamedLayer;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.Stroke;
}
}
- public static org.opengis.style.Style createStyleFromCss(String css) {
+ /*
+ * STYLING
+ */
+
+ public static Style createStyleFromCss(String css) {
Stylesheet ss = CssParser.parse(css);
CssTranslator translator = new CssTranslator();
org.opengis.style.Style style = translator.translate(ss);
// e.printStackTrace();
// }
- return style;
+ return (Style) style;
}
public static String createSldFromCss(String name, String title, String css) {
-
- StyleFactory sf = CommonFactoryFinder.getStyleFactory();
-
- StyledLayerDescriptor sld = sf.createStyledLayerDescriptor();
+ StyledLayerDescriptor sld = GeoTools.STYLE_FACTORY.createStyledLayerDescriptor();
sld.setName(name);
sld.setTitle(title);
- UserLayer layer = sf.createUserLayer();
- layer.setName("default");
+ NamedLayer layer = GeoTools.STYLE_FACTORY.createNamedLayer();
+ layer.setName(name);
- org.opengis.style.Style style = createStyleFromCss(css);
- layer.userStyles().add((Style) style);
+ Style style = createStyleFromCss(css);
+ layer.styles().add(style);
sld.layers().add(layer);
+ return sldToXml(sld);
+ }
+
+ public static String sldToXml(StyledLayerDescriptor sld) {
try {
SLDTransformer styleTransform = new SLDTransformer();
String xml = styleTransform.transform(sld);
// System.out.println(xml);
return xml;
} catch (TransformerException e) {
- throw new IllegalStateException(e);
+ throw new IllegalStateException("Cannot write SLD as XML", e);
}
}
}
+ public static long getScaleFromResolution(long resolution) {
+ // see https://gis.stackexchange.com/questions/242424/how-to-get-map-units-to-find-current-scale-in-openlayers
+ final double INCHES_PER_UNIT = 39.37;// m
+ // final double INCHES_PER_UNIT = 4374754;// dd
+ final long DOTS_PER_INCH = 72;
+ return Math.round(INCHES_PER_UNIT * DOTS_PER_INCH * resolution);
+ }
+
/** Singleton. */
private GeoUtils() {
}
import org.argeo.app.ux.js.AbstractJsObject;
public class AbstractGeoJsObject extends AbstractJsObject {
+ public final static String ARGEO_APP_GEO_JS_URL = "/pkg/org.argeo.app.js/geo.html";
+ public final static String JS_PACKAGE = "argeo.app.geo";
+
public AbstractGeoJsObject(Object... args) {
super(args);
}
@Override
public String getJsPackage() {
- return "argeo.app.geo";
+ return JS_PACKAGE;
}
-
}
package org.argeo.app.geo.ux;
+import org.argeo.app.ol.AbstractOlObject;
+import org.argeo.app.ol.Layer;
import org.argeo.app.ol.OlMap;
-import org.argeo.app.ux.js.AbstractJsObject;
+import org.argeo.app.ol.TileLayer;
+import org.argeo.app.ol.VectorLayer;
import org.argeo.app.ux.js.JsClient;
public class OpenLayersMapPart extends AbstractGeoJsObject {
public OlMap getMap() {
return new OlMap(getJsClient(), getReference() + ".getMap()");
}
+
+ public void setSld(String xml) {
+ executeMethod(getMethodName(), JsClient.escapeQuotes(xml));
+ }
+
+ public void applyStyle(String layerName, String styledLayerName) {
+ executeMethod(getMethodName(), layerName, styledLayerName);
+ }
+
+ public Layer getLayer(String name) {
+ // TODO deal with not found
+ String reference = "getLayerByName('" + name + "')";
+ if (getJsClient().isInstanceOf(reference, AbstractOlObject.getJsClassName(VectorLayer.class))) {
+ return new VectorLayer(getJsClient(), reference);
+ } else if (getJsClient().isInstanceOf(reference, AbstractOlObject.getJsClassName(TileLayer.class))) {
+ return new TileLayer(getJsClient(), reference);
+ } else {
+ return new Layer(getJsClient(), reference);
+ }
+ }
}
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
import org.argeo.app.ux.js.AbstractJsObject;
public abstract class AbstractOlObject extends AbstractJsObject {
+ public final static String JS_PACKAGE = "argeo.tp.ol";
public AbstractOlObject(Object... args) {
super(args.length > 0 ? args : new Object[] { new HashMap<String, Object>() });
}
- public AbstractOlObject(Map<String, Object> options) {
- super(options);
- }
+// public AbstractOlObject(Map<String, Object> options) {
+// super(new Object[] { options });
+// }
public String getJsPackage() {
- return "argeo.tp.ol";
+ return JS_PACKAGE;
}
@SuppressWarnings("unchecked")
throw new IllegalStateException("Object " + getJsClassName() + " has no available options");
return (Map<String, Object>) args[0];
}
+
+ protected void doSetValue(String methodName, String newOption, Object value) {
+ if (isNew()) {
+ Objects.requireNonNull(newOption, "Value cannot be set as an option for " + getJsClassName() + ", use "
+ + methodName + "() after the object has been created");
+ getNewOptions().put(newOption, value);
+ } else {
+ Objects.requireNonNull(methodName, "Value cannot be set via a method for " + getJsClassName() + ", use "
+ + newOption + " before the object is created");
+ executeMethod(methodName, value);
+ }
+ }
+
+ public void set(String key, Object value) {
+ set(key, value, false);
+ }
+
+ public void set(String key, Object value, boolean silent) {
+ if (isNew()) {
+ getNewOptions().put(key, value);
+ } else {
+ executeMethod(getMethodName(), new Object[] { key, value, silent });
+ }
+ }
+
+ public Object get(String key) {
+ if (isNew()) {
+ return getNewOptions().get(key);
+ } else {
+ // TDO deal with reference if we are trying to get an object
+ return callMethod(getMethodName(), key);
+ }
+
+ }
+
+ public static String getJsClassName(Class<?> clss) {
+ if (AbstractOlObject.class.isAssignableFrom(clss)) {
+ // NB: would failed for renamed classes
+ return JS_PACKAGE + "." + clss.getSimpleName();
+ }
+ throw new IllegalArgumentException(clss + " is not an OpenLayers object");
+ }
+
}
--- /dev/null
+package org.argeo.app.ol;
+
+public abstract class FeatureFormat extends AbstractOlObject {
+
+ public FeatureFormat(Object... args) {
+ super(args);
+ }
+
+}
--- /dev/null
+package org.argeo.app.ol;
+
+public class GeoJSON extends FeatureFormat {
+
+ public GeoJSON(Object... args) {
+ super(args);
+ }
+
+}
import java.util.Objects;
-public abstract class Layer extends AbstractOlObject {
+public class Layer extends AbstractOlObject {
+ public final static String NAME_KEY = "name";
+
+ // cached
+ private String name;
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);
+// if (isNew())
+// getNewOptions().put("opacity", opacity);
+// else
+// executeMethod(getMethodName(), opacity);
+ doSetValue(getMethodName(), "opacity", opacity);
}
public void setSource(Source source) {
executeMethod(getMethodName(), maxResolution);
}
+ public void setName(String name) {
+ set(NAME_KEY, name);
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
}
--- /dev/null
+package org.argeo.app.ol;
+
+public class VectorLayer extends Layer {
+ public VectorLayer(Object... args) {
+ super(args);
+ }
+
+ public VectorLayer(String name, Source source) {
+ this(source);
+ setName(name);
+ }
+
+ public VectorLayer(Source source) {
+ setSource(source);
+ }
+}
--- /dev/null
+package org.argeo.app.ol;
+
+public class VectorSource extends Source {
+
+ public VectorSource(Object... args) {
+ super(args);
+ }
+
+ public VectorSource(String url, FeatureFormat format) {
+ setUrl(url);
+ setFormat(format);
+ }
+
+ public void setUrl(String url) {
+ doSetValue(getMethodName(), "url", url);
+ }
+
+ public void setFormat(FeatureFormat format) {
+ doSetValue(null, "format", format);
+ }
+}
package org.argeo.app.geo.swt;
import org.argeo.api.acr.Content;
+import org.argeo.app.geo.ux.AbstractGeoJsObject;
import org.argeo.app.geo.ux.OpenLayersMapPart;
import org.argeo.app.geo.ux.SentinelCloudless;
+import org.argeo.app.ol.GeoJSON;
import org.argeo.app.ol.Layer;
import org.argeo.app.ol.OSM;
import org.argeo.app.ol.TileLayer;
+import org.argeo.app.ol.VectorLayer;
+import org.argeo.app.ol.VectorSource;
import org.argeo.app.swt.js.SwtBrowserJsPart;
import org.argeo.app.ux.js.JsClient;
import org.argeo.cms.swt.acr.SwtUiProvider;
@Override
public Control createUiPart(Composite parent, Content context) {
- JsClient jsClient = new SwtBrowserJsPart(parent, 0, "/pkg/org.argeo.app.js/geo.html");
+ JsClient jsClient = new SwtBrowserJsPart(parent, 0, AbstractGeoJsObject.ARGEO_APP_GEO_JS_URL);
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);
+ Layer dataLayer = new VectorLayer(new VectorSource(
+ "https://openlayers.org/en/v4.6.5/examples/data/geojson/countries.geojson", new GeoJSON()));
+ mapPart.getMap().addLayer(dataLayer);
+
// SwtJsMapPart map = new SwtJsMapPart("defaultOverviewMap", parent, 0);
// map.setCenter(13.404954, 52.520008); // Berlin
//// map.setCenter(-74.00597, 40.71427); // NYC
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;
* Tasks that were requested before the context was ready. Typically
* configuration methods on the part while the user interfaces is being build.
*/
- private List<Supplier<Boolean>> preReadyToDos = new ArrayList<>();
+ private List<PreReadyToDo> preReadyToDos = new ArrayList<>();
public SwtBrowserJsPart(Composite parent, int style, String url) {
this.browser = new Browser(parent, 0);
init();
loadExtensions();
// execute todos in order
- for (Supplier<Boolean> toDo : preReadyToDos) {
- boolean success = toDo.get();
- if (!success)
- throw new IllegalStateException("Post-initalisation JavaScript execution failed");
+ for (PreReadyToDo toDo : preReadyToDos) {
+ toDo.run();
}
preReadyToDos.clear();
readyStage.complete(true);
protected void init() {
}
- /** To be overridden with calls to {@link #loadExtension(String)}. */
+ /**
+ * To be overridden with calls to {@link #loadExtension( Supplier<Boolean> toDo
+ * = () -> { boolean success = browser.execute(); return success; }; String)}.
+ */
protected void loadExtensions() {
}
browser.evaluate(String.format(Locale.ROOT, "import('%s')", url));
}
- protected CompletionStage<Boolean> getReadyStage() {
+ public CompletionStage<Boolean> getReadyStage() {
return readyStage.minimalCompletionStage();
}
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.");
+ throw new IllegalStateException("Methods returning a result can only be called after UI initialisation.");
// wait for the context to be ready
// boolean ready = readyStage.join();
// if (!ready)
@Override
public void execute(String js, Object... args) {
+ String jsToExecute = String.format(Locale.ROOT, js, args);
if (readyStage.isDone()) {
- boolean success = browser.execute(String.format(Locale.ROOT, js, args));
+ boolean success = browser.execute(jsToExecute);
if (!success)
throw new RuntimeException("JavaScript execution failed.");
} else {
- Supplier<Boolean> toDo = () -> {
- boolean success = browser.execute(String.format(Locale.ROOT, js, args));
- return success;
- };
+ PreReadyToDo toDo = new PreReadyToDo(jsToExecute);
preReadyToDos.add(toDo);
}
}
return GLOBAL_THIS_ + name;
}
+ class PreReadyToDo implements Runnable {
+ private String js;
+
+ public PreReadyToDo(String js) {
+ this.js = js;
+ }
+
+ @Override
+ public void run() {
+ boolean success = browser.execute(js);
+ if (!success)
+ throw new IllegalStateException("Pre-ready JavaScript failed: " + js);
+ }
+ }
+
/*
* ACCESSORS
*/