improve geojson overlay rendering options
update readme
This commit is contained in:
parent
f75b417a0b
commit
d964482414
@ -1,6 +1,6 @@
|
|||||||
### Change Log for Node-RED Worldmap
|
### Change Log for Node-RED Worldmap
|
||||||
|
|
||||||
- v3.1.0 - Add esri overlay layers
|
- v3.1.0 - Add esri overlay layers, and let geojson overlay rendering be customised
|
||||||
- v3.0.0 - Bump to Leaflet 1.9.4
|
- v3.0.0 - Bump to Leaflet 1.9.4
|
||||||
Move to geoman for drawing shapes.
|
Move to geoman for drawing shapes.
|
||||||
Allow command.rotation to set rotation of map.
|
Allow command.rotation to set rotation of map.
|
||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
- v2.43.1 - Tweak drawing layer double click
|
- v2.43.1 - Tweak drawing layer double click
|
||||||
- v2.43.0 - Revert leaflet update as it broke Draw
|
- v2.43.0 - Revert leaflet update as it broke Draw
|
||||||
- v2.42.3 - More KML and GEOJson drag drop fixes
|
- v2.42.3 - More KML and GeoJson drag drop fixes
|
||||||
- v2.42.1 - Remove extraneous debug logging, fix KMZ icons
|
- v2.42.1 - Remove extraneous debug logging, fix KMZ icons
|
||||||
- v2.42.0 - Add handling for TAK type spots, waypoints, alerts, sensors. Better KML/KMZ handling.
|
- v2.42.0 - Add handling for TAK type spots, waypoints, alerts, sensors. Better KML/KMZ handling.
|
||||||
- v2.41.0 - Bump leaflet libs to latest stable (1.9.4)
|
- v2.41.0 - Bump leaflet libs to latest stable (1.9.4)
|
||||||
|
46
README.md
46
README.md
@ -13,14 +13,14 @@ Feel free to [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%
|
|||||||
|
|
||||||
### Updates
|
### Updates
|
||||||
|
|
||||||
- v3.1.0 - Add esri overlay layers
|
- v3.1.0 - Add esri overlay layers, and let geojson overlay rendering be customised
|
||||||
- v3.0.0 - Bump to Leaflet 1.9.4
|
- v3.0.0 - Bump to Leaflet 1.9.4
|
||||||
Move to geoman for drawing shapes.
|
Move to geoman for drawing shapes.
|
||||||
Allow command.rotation to set rotation of map.
|
Allow command.rotation to set rotation of map.
|
||||||
Allow editing of multipoint geojson tracks.
|
Allow editing of multipoint geojson tracks.
|
||||||
- v2.43.1 - Tweak drawing layer double click
|
- v2.43.1 - Tweak drawing layer double click
|
||||||
- v2.43.0 - Revert leaflet update as it broke Draw
|
- v2.43.0 - Revert leaflet update as it broke Draw
|
||||||
- v2.42.3 - More KML and GEOJson drag drop fixes
|
- v2.42.3 - More KML and GeoJson drag drop fixes
|
||||||
- v2.42.1 - Remove extraneous debug logging, fix KMZ icons
|
- v2.42.1 - Remove extraneous debug logging, fix KMZ icons
|
||||||
- v2.42.0 - Add handling for TAK type spots, waypoints, alerts, sensors. Better KML/KMZ handling.
|
- v2.42.0 - Add handling for TAK type spots, waypoints, alerts, sensors. Better KML/KMZ handling.
|
||||||
- v2.41.0 - Bump leaflet libs to latest stable (1.9.4)
|
- v2.41.0 - Bump leaflet libs to latest stable (1.9.4)
|
||||||
@ -58,7 +58,7 @@ The minimum **msg.payload** must contain `name`, `lat` and `lon` properties, for
|
|||||||
|
|
||||||
`name` must be a unique identifier across the whole map. Repeated location updates to the same `name` move the marker.
|
`name` must be a unique identifier across the whole map. Repeated location updates to the same `name` move the marker.
|
||||||
|
|
||||||
Optional properties include
|
Optional properties for **msg.payload** include
|
||||||
|
|
||||||
- **deleted** : set to <i>true</i> to remove the named marker. (default <i>false</i>)
|
- **deleted** : set to <i>true</i> to remove the named marker. (default <i>false</i>)
|
||||||
- **draggable** : set to <i>true</i> to allow marker to be moved by the mouse. (default <i>false</i>)
|
- **draggable** : set to <i>true</i> to allow marker to be moved by the mouse. (default <i>false</i>)
|
||||||
@ -220,7 +220,9 @@ Defaults are shown above.
|
|||||||
|
|
||||||
### GeoJSON
|
### GeoJSON
|
||||||
|
|
||||||
If the msg.payload contains a **geojson** property, and no **lat** and **lon**, then
|
There are several ways to send GeoJSON to the map.
|
||||||
|
|
||||||
|
1) If the msg.payload contains a **geojson** property, and no **lat** and **lon**, then
|
||||||
rather than draw a point it will render the geojson.
|
rather than draw a point it will render the geojson.
|
||||||
|
|
||||||
msg.payload = {
|
msg.payload = {
|
||||||
@ -255,8 +257,9 @@ Often geojson may not have a `properties` or `style` property in which case you
|
|||||||
clickable: true
|
clickable: true
|
||||||
}
|
}
|
||||||
|
|
||||||
**Note**: you can just send a msg.payload containing the geojson itself - but obviously you then can't style
|
2) You can just send a msg.payload containing the geojson itself - but obviously you then can't style it, set the name, layer, etc.
|
||||||
it, set the name, layer, etc.
|
|
||||||
|
3) You can also add the geojson as a specific overlay, in which case you can also have more control of styles, and per feature customisations. See the section on overlays [below](#to-add-a-new-geojson-overlay). This is the most complex but customisable.
|
||||||
|
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
@ -415,7 +418,7 @@ msg.payload = { command: { "contextmenu":menu } }
|
|||||||
|
|
||||||
You can also control the map via the node, by sending in a msg.payload containing a **command** object. Multiple parameters can be specified in one command.
|
You can also control the map via the node, by sending in a msg.payload containing a **command** object. Multiple parameters can be specified in one command.
|
||||||
|
|
||||||
Optional properties include
|
Optional properties for **msg.payload.command** include
|
||||||
|
|
||||||
- **lat** - move map to specified latitude.
|
- **lat** - move map to specified latitude.
|
||||||
- **lon** - move map to specified longitude.
|
- **lon** - move map to specified longitude.
|
||||||
@ -583,17 +586,24 @@ By default the overlay will be instantly visible. To load it hidden add a proper
|
|||||||
"overlay": "myGeoJSON",
|
"overlay": "myGeoJSON",
|
||||||
"geojson": { your geojson feature as an object },
|
"geojson": { your geojson feature as an object },
|
||||||
"opt": { optional geojson options, style, etc },
|
"opt": { optional geojson options, style, etc },
|
||||||
"fit": true
|
"fit": true,
|
||||||
"clickable": false
|
"clickable": false
|
||||||
};
|
};
|
||||||
|
|
||||||
The geojson features may contain a `properties` property. That may also include a `style` with properties - stroke, stroke-width, stroke-opacity, fill, fill-opacity. Any other properties will be listed in the popup.
|
The geojson features may contain a `properties` property. That may also include a `style` with properties - stroke, stroke-width, stroke-opacity, fill, fill-opacity. Any other properties will be listed in the popup.
|
||||||
|
|
||||||
The `opt` property is optional. See the <a href="https://leafletjs.com/examples/geojson/">Leaflet geojson docs</a> for more info on possible options. Note: only simple options are supported as functions cannot be serialised.
|
The `opt` property is optional. See the <a href="https://leafletjs.com/examples/geojson/">Leaflet geojson docs</a> for more info on possible options.
|
||||||
|
|
||||||
The `fit` property is optional, and you can also use `fly` if you wish. If boolean true the map will automatically zoom to fit the area relevant to the geojson, or use the 'fly' to animation style. You can also set `clickable` true to return the properties of the clicked feature to the worldmap-in node.
|
NOTE: In order to pass over **style**, **pointToLayer**, **onEachFeature**, or **filter** functions they need to be serialised as follows... for example
|
||||||
|
|
||||||
see https://leafletjs.com/examples/geojson/ for more details about options for opt.
|
const style = function () {
|
||||||
|
return { color: "#910000", weight: 2 };
|
||||||
|
};
|
||||||
|
msg.payload.command.map.opt.style = style.toString();
|
||||||
|
|
||||||
|
This may cause the function node setting them to be in error, for example if it references L (the leaflet map), which is unknown on the server side. The flow should still deploy and run ok.
|
||||||
|
|
||||||
|
The `fit` property is optional, and you can also use `fly` if you wish. If boolean true the map will automatically zoom to fit the area relevant to the geojson, or use 'fly' to set the animated style. You can also set `clickable` true to return the properties of the clicked feature to the worldmap-in node.
|
||||||
|
|
||||||
#### To add a new KML, GPX, or TOPOJSON overlay
|
#### To add a new KML, GPX, or TOPOJSON overlay
|
||||||
|
|
||||||
@ -619,10 +629,20 @@ As per the geojson overlay you can also inject an ESRI ArcGIS FeatureLayer layer
|
|||||||
msg.payload.command.map = {
|
msg.payload.command.map = {
|
||||||
"overlay": "myFeatureLayer",
|
"overlay": "myFeatureLayer",
|
||||||
"esri": "https://services3.arcgis.com/...../0",
|
"esri": "https://services3.arcgis.com/...../0",
|
||||||
"options": { object of options }
|
"opt": { object of options }
|
||||||
};
|
};
|
||||||
|
|
||||||
NOTE: you can set various options as [specified here](https://developers.arcgis.com/esri-leaflet/api-reference/layers/feature-layer/#options) - but these don't currently indlude the style oo onEachFeature fnctions as they are non-serialisable across the websocket link.
|
NOTE: you can set various options as [specified here](https://developers.arcgis.com/esri-leaflet/api-reference/layers/feature-layer/#options).
|
||||||
|
|
||||||
|
In order to pass over **style**, **pointToLayer**, or **onEachFeature** functions they need to be serialised as follows... for example
|
||||||
|
|
||||||
|
const style = function () {
|
||||||
|
return { color: "#910000", weight: 2 };
|
||||||
|
};
|
||||||
|
msg.payload.command.map.opt.style = style.toString();
|
||||||
|
|
||||||
|
This may cause the function node setting them to be in error, for example if it references L.marker, which is unknown on the server side. The flow should still deploy and run ok.
|
||||||
|
|
||||||
|
|
||||||
#### To add a Velocity Grid Overlay
|
#### To add a Velocity Grid Overlay
|
||||||
|
|
||||||
|
@ -2467,57 +2467,69 @@ function doCommand(cmd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add a new geojson overlay layer
|
// Add a new geojson overlay layer
|
||||||
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("geojson") ) {
|
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("geojson")) {
|
||||||
if (overlays.hasOwnProperty(cmd.map.overlay)) {
|
if (overlays.hasOwnProperty(cmd.map.overlay)) {
|
||||||
map.removeLayer(overlays[cmd.map.overlay]);
|
map.removeLayer(overlays[cmd.map.overlay]);
|
||||||
existsalready = true;
|
existsalready = true;
|
||||||
}
|
}
|
||||||
var opt = cmd.map.opt || { style:function(feature) {
|
try {
|
||||||
var st = { stroke:true, weight:2, fill:true };
|
var opt = cmd.map.opt || {};
|
||||||
if (feature.hasOwnProperty("properties")) {
|
if (opt.hasOwnProperty("style")) { opt.style = new Function('return ' + opt.style)(); }
|
||||||
st.color = feature.properties.color||feature.properties.roofColor||"black";
|
else {
|
||||||
if (feature.properties.hasOwnProperty("color")) { delete feature.properties.color; }
|
opt.style = function(feature) {
|
||||||
if (feature.properties.hasOwnProperty("roofColor")) { delete feature.properties.roofColor; }
|
var st = { stroke:true, weight:2, fill:true };
|
||||||
|
if (feature.hasOwnProperty("properties")) {
|
||||||
|
st.color = feature.properties.color||feature.properties.roofColor||"black";
|
||||||
|
if (feature.properties.hasOwnProperty("color")) { delete feature.properties.color; }
|
||||||
|
if (feature.properties.hasOwnProperty("roofColor")) { delete feature.properties.roofColor; }
|
||||||
|
}
|
||||||
|
if (feature.hasOwnProperty("properties") && feature.properties.hasOwnProperty('style')) {
|
||||||
|
if (feature.properties.style.hasOwnProperty('stroke')) {
|
||||||
|
st.color = feature.properties.style.stroke;
|
||||||
|
}
|
||||||
|
if (feature.properties.style.hasOwnProperty('stroke-width')) {
|
||||||
|
st.weight = feature.properties.style["stroke-width"];
|
||||||
|
}
|
||||||
|
if (feature.properties.style.hasOwnProperty('stroke-opacity')) {
|
||||||
|
st.opacity = feature.properties.style["stroke-opacity"];
|
||||||
|
}
|
||||||
|
if (feature.properties.style.hasOwnProperty('fill')) {
|
||||||
|
if (feature.properties.style.fill == "none") { st.fill = false; }
|
||||||
|
else { st.fillColor = feature.properties.style.fill; }
|
||||||
|
}
|
||||||
|
if (feature.properties.style.hasOwnProperty('fill-opacity')) {
|
||||||
|
st.fillOpacity = feature.properties.style["fill-opacity"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete feature.properties.style;
|
||||||
|
return st;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (feature.hasOwnProperty("properties") && feature.properties.hasOwnProperty('style')) {
|
if (opt.hasOwnProperty("pointToLayer")) { opt.pointToLayer = new Function('return ' + opt.pointToLayer)(); }
|
||||||
if (feature.properties.style.hasOwnProperty('stroke')) {
|
if (opt.hasOwnProperty("filter")) { opt.filter = new Function('return ' + opt.filter)(); }
|
||||||
st.color = feature.properties.style.stroke;
|
if (opt.hasOwnProperty("onEachFeature")) { opt.onEachFeature = new Function('return ' + opt.onEachFeature)(); }
|
||||||
}
|
else {
|
||||||
if (feature.properties.style.hasOwnProperty('stroke-width')) {
|
opt.onEachFeature = function (f,l) {
|
||||||
st.weight = feature.properties.style["stroke-width"];
|
var pw = '<pre>'+JSON.stringify(f.properties,null,' ').replace(/[\{\}"]/g,'')+'</pre>';
|
||||||
}
|
if (pw.length > 11) { l.bindPopup(pw); }
|
||||||
if (feature.properties.style.hasOwnProperty('stroke-opacity')) {
|
if (cmd.map.hasOwnProperty("clickable") && cmd.map.clickable === true) {
|
||||||
st.opacity = feature.properties.style["stroke-opacity"];
|
l.on('click', function (e) {
|
||||||
}
|
ws.send(JSON.stringify({action:"clickgeo",name:cmd.map.overlay,type:f.type,properties:f.properties,geometry:f.geometry}));
|
||||||
if (feature.properties.style.hasOwnProperty('fill')) {
|
});
|
||||||
if (feature.properties.style.fill == "none") { st.fill = false; }
|
}
|
||||||
else { st.fillColor = feature.properties.style.fill; }
|
|
||||||
}
|
|
||||||
if (feature.properties.style.hasOwnProperty('fill-opacity')) {
|
|
||||||
st.fillOpacity = feature.properties.style["fill-opacity"];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete feature.properties.style;
|
overlays[cmd.map.overlay] = L.geoJson(cmd.map.geojson,opt);
|
||||||
return st;
|
if (!existsalready) {
|
||||||
}};
|
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
|
||||||
opt.onEachFeature = function (f,l) {
|
|
||||||
var pw = '<pre>'+JSON.stringify(f.properties,null,' ').replace(/[\{\}"]/g,'')+'</pre>';
|
|
||||||
if (pw.length > 11) { l.bindPopup(pw); }
|
|
||||||
if (cmd.map.hasOwnProperty("clickable") && cmd.map.clickable === true) {
|
|
||||||
l.on('click', function (e) {
|
|
||||||
ws.send(JSON.stringify({action:"clickgeo",name:cmd.map.overlay,type:f.type,properties:f.properties,geometry:f.geometry}));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
|
||||||
overlays[cmd.map.overlay] = L.geoJson(cmd.map.geojson,opt);
|
map.addLayer(overlays[cmd.map.overlay]);
|
||||||
if (!existsalready) {
|
}
|
||||||
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
|
if (cmd.map.hasOwnProperty("fly") && (cmd.map.fly === true)) { map.flyToBounds(overlays[cmd.map.overlay].getBounds()); }
|
||||||
}
|
else if (cmd.map.hasOwnProperty("fit") && (cmd.map.fit === true)) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); }
|
||||||
if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
|
}
|
||||||
map.addLayer(overlays[cmd.map.overlay]);
|
catch(e) { console.log(e); }
|
||||||
}
|
|
||||||
if (cmd.map.hasOwnProperty("fly") && (cmd.map.fly === true)) { map.flyToBounds(overlays[cmd.map.overlay].getBounds()); }
|
|
||||||
else if (cmd.map.hasOwnProperty("fit") && (cmd.map.fit === true)) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); }
|
|
||||||
}
|
}
|
||||||
// Add a new NVG XML overlay layer
|
// Add a new NVG XML overlay layer
|
||||||
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("nvg") ) {
|
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("nvg") ) {
|
||||||
@ -2663,31 +2675,25 @@ function doCommand(cmd) {
|
|||||||
// Add a new ESRI feature layer
|
// Add a new ESRI feature layer
|
||||||
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("esri") ) {
|
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("esri") ) {
|
||||||
try {
|
try {
|
||||||
if (overlays.hasOwnProperty(cmd.map.overlay)) {
|
if (overlays.hasOwnProperty(cmd.map.overlay)) {
|
||||||
overlays[cmd.map.overlay].removeFrom(map);
|
overlays[cmd.map.overlay].removeFrom(map);
|
||||||
existsalready = true;
|
existsalready = true;
|
||||||
}
|
}
|
||||||
var opt = {};
|
var opt = {};
|
||||||
if (cmd.map.hasOwnProperty("options")) { opt = cmd.map.options; }
|
if (cmd.map.hasOwnProperty("opt")) { opt = cmd.map.opt; }
|
||||||
console.log("OPTS",opt)
|
if (opt.hasOwnProperty("style")) { opt.style = new Function('return ' + opt.style)(); }
|
||||||
opt.url = cmd.map.esri;
|
if (opt.hasOwnProperty("pointToLayer")) { opt.pointToLayer = new Function('return ' + opt.pointToLayer)(); }
|
||||||
map.createPane("blockpoints");
|
if (opt.hasOwnProperty("onEachFeature")) { opt.onEachFeature = new Function('return ' + opt.onEachFeature)(); }
|
||||||
opt.pointToLayer = function (geojson, latlng) {
|
opt.url = cmd.map.esri;
|
||||||
console.log("Point geo",latlng, geojson);
|
overlays[cmd.map.overlay] = L.esri.featureLayer(opt);
|
||||||
return L.marker(latlng);
|
if (!existsalready) {
|
||||||
};
|
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
|
||||||
opt.onEachFeature = function (geojson, layer) {
|
}
|
||||||
console.log("Feature",layer, geojson);
|
if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
|
||||||
};
|
overlays[cmd.map.overlay].addTo(map);
|
||||||
overlays[cmd.map.overlay] = L.esri.featureLayer(opt);
|
}
|
||||||
if (!existsalready) {
|
// NOTE can't fit or fly to bounds as they keep reloading
|
||||||
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
|
} catch(e) { console.log(e); }
|
||||||
}
|
|
||||||
if (!cmd.map.hasOwnProperty("visible") || (cmd.map.visible != false)) {
|
|
||||||
overlays[cmd.map.overlay].addTo(map);
|
|
||||||
}
|
|
||||||
// NOTE can't fit or fly to bounds as they keep reloading
|
|
||||||
} catch(e) { console.log(e); }
|
|
||||||
}
|
}
|
||||||
// Add a new TOPOJSON overlay layer
|
// Add a new TOPOJSON overlay layer
|
||||||
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("topojson") ) {
|
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("topojson") ) {
|
||||||
@ -2896,7 +2902,7 @@ function doCommand(cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle any incoming GEOJSON directly - may style badly
|
// handle any incoming GEOJSON directly - may style badly
|
||||||
function doGeojson(n,g,l,o) {
|
function doGeojson(n,g,l,o) { // name, geojson, layer, options
|
||||||
var lay = l ?? g.name ?? "unknown";
|
var lay = l ?? g.name ?? "unknown";
|
||||||
// if (!basemaps[lay]) {
|
// if (!basemaps[lay]) {
|
||||||
var opt = { style: function(feature) {
|
var opt = { style: function(feature) {
|
||||||
|
Loading…
Reference in New Issue
Block a user