diff --git a/README.md b/README.md index 81b1a70..61b8a25 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ There are also several special icons... - **bus** : a bus/coach icon that aligns with the heading of travel. - **uav** : a small uav like icon that aligns with the heading of travel. - **helicopter** : a small helicopter icon that aligns with the heading of travel. + - **sensor** : a camera icon that points to the heading angle. - **arrow** : a map GPS arrow type pointer that aligns with the heading of travel. - **wind** : a wind arrow that points in the direction the wind is coming FROM. - **satellite** : a small satellite icon. @@ -153,7 +154,7 @@ following the great circle between the two co-ordinates is plotted. msg.payload = {name:"GC1", color:"#ff00ff", greatcircle:[ [51.464,0], [25.76,-80.18] ] } -Shapes can also have a **popup** property containing html, but you MUST also set a property `clickable:true` in order to allow it to be seen. +Shapes can also have a **popup** property containing html, but you MUST also set a property `clickable:true` in order to allow it to be seen. You can also set **tooltip** to create a label that appears when you hover the mouse over the shape. There are extra optional properties you can specify - see Options below. @@ -192,7 +193,7 @@ You can add supplemental arc(s) to a marker by adding an **arc** property as bel Supplemental means that you can also specify a line using a **bearing** and **length** property. ``` -msg.payload = { name:"Camera01", icon:"fa-camera", lat:51.05, lon:-1.35, +msg.payload = { name:"Camera01", icon:"sensor", lat:51.05, lon:-1.35, bearing: 235, length: 2200, arc: { @@ -251,7 +252,7 @@ Often geojson may not have a `properties` or `style` property in which case you 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. -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. +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 also the most customisable. ### Options @@ -267,6 +268,7 @@ Areas, Rectangles, Lines, Circles and Ellipses can also specify more optional pr - **clickable** : boolean - set to true to allow click to show popup. - **popup** : html string to display in popup (as well as name). - **editable** : boolean - set to true to allow simple edit/delete right click contextmenu. + - **tooltip** : Text string to display on mouse hover over the shape. - **contextmenu** : html string to display a more complex right click contextmenu. - **weight** : the width of the line or outline. @@ -377,7 +379,7 @@ The **worldmap in** node can be used to receive various events from the map. Exa { "action": "feedback", "name": "some name", "value": "some value", "lat":51, "lon":0, "layer":"unknown" } // when a user calls the feedback function - see below -If File Drop is enabled - then the map can accept files of type gpx, kml, nvg, jpeg, png and geojson. The file content property will always be a binary buffer. The lat, lon of the cursor drop point will be included. Tracks will be locally rendered on the map. The node-red-node-exif node can be used to extract location information from a jpeg image and then geolocate it back on the map. Png images will be located where they are dropped but can then be dragged if required. +If File Drop is enabled - then the map can accept files of type gpx, kml, nvg, jpeg, png and geojson. The file content property will always be a binary buffer. The lat, lon of the cursor drop point will be included. Tracks will be locally rendered on the map. The `node-red-node-exif` node can be used to extract location information from a jpeg image and then geolocate it back on the map. Png images will be located where they are dropped but can then be dragged if required. All actions also include a: `msg._sessionid` property that indicates which client session they came from. Any msg sent out that includes this property will ONLY be sent to that session - so you can target map updates to specific sessions if required. @@ -419,15 +421,15 @@ Optional properties for **msg.payload.command** include - **lat** - move map to specified latitude. - **lon** - move map to specified longitude. - - **rotation** - rotate the base map to the specified compass angle. - **zoom** - move map to specified zoom level (1 - world, 13 to 20 max zoom depending on map). - **bounds** - if set to an array `[ [ lat(S), lon(W) ], [lat(N), lon(E)] ]` - sets the overall map bounds. + - **rotation** - rotate the base map to the specified compass angle. - **layer** - set map to specified base layer name - `{"command":{"layer":"Esri"}}` - **search** - search markers on map for name containing `string`. If not found in existing markers, will then try geocoding looking using Nominatim. An empty string `""` clears the search results. - `{"command":{"search":"Winchester"}}` - **showlayer** - show the named overlay(s) - `{"command":{"showlayer":"foo"}}` or `{"command":{"showlayer":["foo","bar"]}}` - **hidelayer** - hide the named overlay(s) - `{"command":{"hidelayer":"bar"}}` or `{"command":{"hidelayer":["bar","another"]}}` - **side** - add a second map alongside with slide between them. Use the name of a *baselayer* to add - or "none" to remove the control. - `{"command":{"side":"Esri Satellite"}}` - - **split** - once you have split the screen - the split value is the % across the screen of the split line. - `{"command":{"split":50}}` + - **split** - once you have split the screen with the *side* command - the split value is then the % across the screen of the split line. - `{"command":{"split":50}}` - **map** - Object containing details of a new map layer: - **name** - name of the map base layer OR **overlay** - name of overlay layer - **url** - url of the map layer @@ -448,7 +450,7 @@ Optional properties for **msg.payload.command** include a msg `{"action":"button", "name":"the_button_name"}` to the worldmap in node. If supplied with a `name` property only, it will remove the button. Optional `position` property can be 'bottomright', 'bottomleft', 'topleft' or 'topright' (default). button can also be an array of button objects. - **contextmenu** - html string to define the right click menu when not on a marker. Defaults to the simple add marker input. Empty string `""` disables this right click. - **toptitle** - Words to replace title in title bar (if not in iframe) - - **toplogo** - URL to logo image for top tile bar (if not in iframe) - ideally 60px by 24px. + - **toplogo** - URL to logo image for top title bar (if not in iframe) - ideally 60px by 24px. - **trackme** - Turns on/off the browser self locating. Boolean false = off, true = cyan circle showing accuracy error, or an object like `{"command":{"trackme":{"name":"Dave","icon":"car","iconColor":"blue","layer":"mytrack","accuracy":false}}}`. Usual marker options can be applied. - **showmenu** - Show or hide the display of the hamberger menu control in the top right . Values can be "show" or "hide". - `{"command":{"showmenu": "hide"}}` - **showlayers** - Show or hide the display of selectable layers. Does not control the display of an individual layer, rather a users ability to interact with them. Values can be "show" or "hide". - `{"command":{"showlayers": "hide"}}` @@ -508,7 +510,6 @@ Example simple form [{"id":"7351100bacb1f5fe","type":"function","z":"4aa2ed2fd1b11362","name":"","func":"msg.payload = { command: {\ncontextmenu: String.raw`\nText \nNumber \n\n`\n}}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":360,"wires":[["a6a82f2e8efc44fc"]]},{"id":"7b595f0c8f6ac710","type":"worldmap in","z":"4aa2ed2fd1b11362","name":"","path":"/worldmap","events":"connect","x":195,"y":360,"wires":[["7351100bacb1f5fe"]]}] ``` - See the section on **Utility Functions** for details of the feedback function. #### To add and remove a legend @@ -604,7 +605,7 @@ The `fit` property is optional, and you can also use `fly` if you wish. If boole #### To add a new KML, GPX, or TOPOJSON overlay -As per the geojson overlay you can also inject a KML layer, GPX layer or TOPOJSON layer. The syntax is the same but with either a `kml` property containing the KML string - a `gpx` property containing a GPX string - or a `topojson` property containing the topojson. +As with the geojson overlay, you can also inject a KML layer, GPX layer or TOPOJSON layer. The syntax is the same but with either a `kml` property containing the KML string - a `gpx` property containing a GPX string - or a `topojson` property containing the topojson. msg.payload.command.map = { "overlay": "myKML", @@ -700,7 +701,7 @@ Feeding this into the tracks node will also remove the tracks stored for that la ### Using a local Map Server (WMS server) -IMHO the easiest map server to make work is the mapserver package in Ubuntu / Debian. Usually you will start with +IMHO the easiest WMS map server to make work is the mapserver package in Ubuntu / Debian. Usually you will start with sudo apt-get install mapserver-bin cgi-mapserver gdal-bin @@ -730,7 +731,6 @@ You can then add a new WMS Base layer by injecting a message like "wms": true // set to true for WMS type mapserver }}} - #### Using a Docker Map Server You can use a docker container like https://hub.docker.com/r/camptocamp/mapserver, then assuming you have the mapfile 'my-app.map' in the current working directory, you could mount it as: @@ -768,7 +768,7 @@ The following example gets recent earthquakes from USGS, parses the result, formats up the msg as per above and sends to the node to plot on the map. It also shows how to zoom and move the map or add a new layer. - [{"id":"86457344.50e6b","type":"inject","z":"745a133b.dd6dec","name":"","topic":"","payload":"","payloadType":"none","repeat":"","crontab":"","once":false,"x":190,"y":2420,"wires":[["9a142026.fa47f"]]},{"id":"9a142026.fa47f","type":"function","z":"745a133b.dd6dec","name":"add new layer","func":"msg.payload = {};\nmsg.payload.command = {};\n\nvar u = 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png';\nvar o = { maxZoom: 19, attribution: '© OpenStreetMap'};\n\nmsg.payload.command.map = {name:\"OSMhot\", url:u, opt:o};\nmsg.payload.command.layer = \"OSMhot\";\n\nreturn msg;","outputs":1,"noerr":0,"x":420,"y":2420,"wires":[["c643e022.1816c"]]},{"id":"c643e022.1816c","type":"worldmap","z":"745a133b.dd6dec","name":"","x":750,"y":2460,"wires":[]},{"id":"2998e233.4ba64e","type":"function","z":"745a133b.dd6dec","name":"USGS Quake monitor csv re-parse","func":"msg.payload.lat = msg.payload.latitude;\nmsg.payload.lon = msg.payload.longitude;\nmsg.payload.layer = \"earthquake\";\nmsg.payload.name = msg.payload.id;\nmsg.payload.icon = \"globe\";\nmsg.payload.iconColor = \"orange\";\n\ndelete msg.payload.latitude;\ndelete msg.payload.longitude;\t\nreturn msg;","outputs":1,"noerr":0,"x":540,"y":2560,"wires":[["c643e022.1816c"]]},{"id":"e72c5732.9fa198","type":"function","z":"745a133b.dd6dec","name":"move and zoom","func":"msg.payload = { command:{layer:\"Esri Terrain\",lat:0,lon:0,zoom:3} };\nreturn msg;","outputs":1,"noerr":0,"x":420,"y":2460,"wires":[["c643e022.1816c"]]},{"id":"12317723.589249","type":"csv","z":"745a133b.dd6dec","name":"","sep":",","hdrin":true,"hdrout":"","multi":"one","ret":"\\n","temp":"","x":390,"y":2500,"wires":[["2998e233.4ba64e"]]},{"id":"10e5e5f0.8daeaa","type":"inject","z":"745a133b.dd6dec","name":"","topic":"","payload":"","payloadType":"none","repeat":"","crontab":"","once":false,"x":190,"y":2460,"wires":[["e72c5732.9fa198"]]},{"id":"b6917d83.d1bac","type":"http request","z":"745a133b.dd6dec","name":"","method":"GET","url":"http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.csv","x":270,"y":2560,"wires":[["12317723.589249"]]},{"id":"3842171.4d487e8","type":"inject","z":"745a133b.dd6dec","name":"Quakes","topic":"","payload":"","payloadType":"none","repeat":"900","crontab":"","once":false,"x":200,"y":2500,"wires":[["b6917d83.d1bac"]]}] + [{"id":"86457344.50e6b","type":"inject","z":"cb7b09e3354afd4c","name":"","repeat":"","crontab":"","once":false,"topic":"","payload":"","payloadType":"none","x":170,"y":500,"wires":[["9a142026.fa47f"]]},{"id":"9a142026.fa47f","type":"function","z":"cb7b09e3354afd4c","name":"add new layer","func":"msg.payload = {};\nmsg.payload.command = {};\n\nvar u = 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png';\nvar o = { maxZoom: 19, attribution: '© OpenStreetMap'};\n\nmsg.payload.command.map = {name:\"OSMhot\", url:u, opt:o};\nmsg.payload.command.layer = \"OSMhot\";\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":500,"wires":[["c643e022.1816c"]]},{"id":"c643e022.1816c","type":"worldmap","z":"cb7b09e3354afd4c","name":"","lat":"30","lon":"0","zoom":"3","layer":"OSMG","cluster":"","maxage":"","usermenu":"show","layers":"show","panit":"false","panlock":"false","zoomlock":"false","hiderightclick":"false","coords":"deg","showgrid":"false","showruler":"false","allowFileDrop":"false","path":"worldmap","overlist":"CO,RA,DN","maplist":"OSMG,OSMH,EsriS","mapname":"","mapurl":"","mapopt":"","mapwms":false,"x":640,"y":540,"wires":[]},{"id":"2998e233.4ba64e","type":"function","z":"cb7b09e3354afd4c","name":"USGS Quake monitor csv re-parse","func":"msg.payload.lat = msg.payload.latitude;\nmsg.payload.lon = msg.payload.longitude;\nmsg.payload.layer = \"earthquake\";\nmsg.payload.name = msg.payload.id;\nmsg.payload.icon = \"globe\";\nmsg.payload.iconColor = \"orange\";\n\ndelete msg.payload.latitude;\ndelete msg.payload.longitude;\t\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":640,"wires":[["c643e022.1816c"]]},{"id":"e72c5732.9fa198","type":"function","z":"cb7b09e3354afd4c","name":"move and zoom","func":"msg.payload = { command:{layer:\"Esri Terrain\",lat:0,lon:-90,zoom:2} };\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":540,"wires":[["c643e022.1816c"]]},{"id":"12317723.589249","type":"csv","z":"cb7b09e3354afd4c","name":"","sep":",","hdrin":true,"hdrout":"","multi":"one","ret":"\\n","temp":"","x":370,"y":580,"wires":[["2998e233.4ba64e"]]},{"id":"10e5e5f0.8daeaa","type":"inject","z":"cb7b09e3354afd4c","name":"","repeat":"","crontab":"","once":false,"topic":"","payload":"","payloadType":"none","x":170,"y":540,"wires":[["e72c5732.9fa198"]]},{"id":"b6917d83.d1bac","type":"http request","z":"cb7b09e3354afd4c","name":"","method":"GET","url":"http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.csv","x":250,"y":640,"wires":[["12317723.589249"]]},{"id":"3842171.4d487e8","type":"inject","z":"cb7b09e3354afd4c","name":"Quakes","repeat":"900","crontab":"","once":false,"topic":"","payload":"","payloadType":"none","x":180,"y":580,"wires":[["b6917d83.d1bac"]]}] --- diff --git a/worldmap/worldmap.js b/worldmap/worldmap.js index 83ee0eb..02d54d1 100644 --- a/worldmap/worldmap.js +++ b/worldmap/worldmap.js @@ -16,7 +16,7 @@ var buttons = {}; var marksIndex = 0; var menuOpen = false; var clusterAt = 0; -var maxage = 900; // default max age of icons on map in seconds - cleared after 10 mins +var maxage = 900; // default max age of icons on map in seconds - cleared after 15 mins var baselayername = "OSM grey"; // Default base layer OSM but uniform grey var pagefoot = " © DCJ 2023"; var inIframe = false; @@ -58,32 +58,31 @@ var iconSz = { var filesAdded = ''; -var loadStatic = function(fileName){ - if(filesAdded.indexOf(fileName) !== -1) - return +var loadStatic = function(fileName) { + if (filesAdded.indexOf(fileName) !== -1) { return; } var head = document.getElementsByTagName('head')[0] - if(fileName.indexOf('js') !== -1) { - var script = document.createElement('script') - script.src = fileName - script.type = 'text/javascript' - console.log("Loading: ",fileName) - head.append(script) - filesAdded += ' ' + fileName - } else if (fileName.indexOf('css') !== -1) { - var style = document.createElement('link') - style.href = fileName - style.type = 'text/css' - style.rel = 'stylesheet' - console.log("Loading: ",fileName) - head.append(style); - filesAdded += ' ' + fileName - } else { - console.log("Unsupported file type: ",fileName) + if (fileName.indexOf('js') !== -1) { + var script = document.createElement('script'); + script.src = fileName; + script.type = 'text/javascript'; + console.log("Loading: ",fileName); + head.append(script); + filesAdded += ' ' + fileName; + } + else if (fileName.indexOf('css') !== -1) { + var style = document.createElement('link'); + style.href = fileName; + style.type = 'text/css'; + style.rel = 'stylesheet'; + console.log("Loading: ",fileName); + head.append(style);; + filesAdded += ' ' + fileName; + } + else { + console.log("Unsupported file type: ",fileName); } } -// L.PM.setOptIn(true); - // Create the socket var connect = function() { // var transports = ["websocket", "xhr-streaming", "xhr-polling"], @@ -118,8 +117,6 @@ console.log("CONNECT TO",location.pathname + 'socket'); var handleData = function(data) { if (Array.isArray(data)) { //console.log("ARRAY"); - // map.closePopup(); - // var bnds= L.latLngBounds([0,0]); for (var prop in data) { if (data[prop].command) { doCommand(data[prop].command); delete data[prop].command; } if (data[prop].hasOwnProperty("name")) { @@ -130,11 +127,11 @@ var handleData = function(data) { data = {command:{map:{overlay:"KML", kml:data[prop].payload}}}; doCommand(data.command); return; } - else { console.log("SKIP A",data[prop]); } + else { console.log("SKIP array item",data[prop]); } } - // map.fitBounds(bnds.pad(0.25)); } else { + // Handle some raw string data overlays if (typeof data === "string" && data.indexOf("Found "+marks.length+" results within bounds."; - } else { + } + else { document.getElementById('searchResult').innerHTML = " Found "+marks.length+" results."; } } @@ -668,7 +659,7 @@ function clearSearch() { marks = []; marksIndex = 0; for (var key in markers) { - if ( (~(key.toLowerCase()).indexOf(value.toLowerCase())) && (mb.contains(markers[key].getLatLng()))) { + if ( (~(key.toLowerCase()).indexOf(value.toLowerCase())) && (mbnds.contains(markers[key].getLatLng()))) { marks.push(markers[key]); } } @@ -694,7 +685,8 @@ function toggleMenu() { menuOpen = !menuOpen; if (menuOpen) { document.getElementById("menu").style.display = 'block'; - } else { + } + else { document.getElementById("menu").style.display = 'none'; dialogue.close(); } @@ -930,7 +922,6 @@ map.on('contextmenu', function(e) { } }); - // Layer control based on select box rather than radio buttons. //var layercontrol = L.control.selectLayers(basemaps, overlays).addTo(map); layercontrol = L.control.layers(basemaps, overlays); @@ -1271,7 +1262,8 @@ var addOverlays = function(overlist) { numbers.push(current); current = 0; shift = 0; - } else { + } + else { shift += 5; } } @@ -1395,10 +1387,11 @@ var addOverlays = function(overlist) { if (!inIframe) { layercontrol.addTo(map); } else { showLayerMenu = false;} +// Add optional mouse co-ordinates display var coords = L.control.mouseCoordinate({position:"bottomleft"}); // Add an optional legend -var legend = L.control({ position: "bottomleft" }); +var legend = L.control({position:"bottomleft"}); // Add the dialog box for messages // var dialogue = L.control.dialog({initOpen:false, size:[600,400], anchor:[50,150]}).addTo(map); @@ -1482,7 +1475,6 @@ var editPoly = function(pname,fun) { }) } - var rangerings = function(latlng, options) { options = L.extend({ ranges: [250,500,750,1000], @@ -1503,7 +1495,7 @@ var rangerings = function(latlng, options) { return rings; } -// the MAIN add something to map function +// the MAIN add marker or shape to map function function setMarker(data) { var rightmenu = function(m) { m.on('click', function(e) { @@ -1630,30 +1622,23 @@ function setMarker(data) { if (typeof polygons[data.name] != "undefined") { layers[lay].removeLayer(polygons[data.name]); } + if (data.hasOwnProperty("drawCount")) { drawCount = data.drawCount; } + // Draw lines if (data.hasOwnProperty("line") && Array.isArray(data.line)) { delete opt.fill; if (!data.hasOwnProperty("weight")) { opt.weight = 3; } //Standard settings different for lines if (!data.hasOwnProperty("opacity")) { opt.opacity = 0.8; } var polyln = L.polyline(data.line, opt); polygons[data.name] = rightmenu(polyln); - if (data.hasOwnProperty("fly") && data.fly === true) { - map.flyToBounds(polygons[data.name].getBounds(),{padding:[50,50]}) - } else if (data.hasOwnProperty("fit") && data.fit === true) { - map.fitBounds(polygons[data.name].getBounds(),{padding:[50,50]}) - } } + // Draw Areas else if (data.hasOwnProperty("area") && Array.isArray(data.area)) { var polyarea; if (data.area.length === 2) { polyarea = L.rectangle(data.area, opt); } else { polyarea = L.polygon(data.area, opt); } polygons[data.name] = rightmenu(polyarea); - if (data.hasOwnProperty("fly") && data.fly === true) { - map.flyToBounds(polygons[data.name].getBounds(),{padding:[50,50]}) - } else if (data.hasOwnProperty("fit") && data.fit === true) { - map.fitBounds(polygons[data.name].getBounds(),{padding:[50,50]}) - } } - if (data.hasOwnProperty("drawCount")) { drawCount = data.drawCount; } + // Draw Great circles if (data.hasOwnProperty("greatcircle") && Array.isArray(data.greatcircle) && data.greatcircle.length === 2) { delete opt.fill; opt.vertices = opt.vertices || 20; @@ -1661,25 +1646,20 @@ function setMarker(data) { if (!data.hasOwnProperty("opacity")) { opt.opacity = 0.8; } var greatc = L.Polyline.Arc(data.greatcircle[0], data.greatcircle[1], opt); var aml = new L.Wrapped.Polyline(greatc._latlngs, opt); - polygons[data.name] = rightmenu(aml); - if (data.hasOwnProperty("fly") && data.fly === true) { - map.flyToBounds(polygons[data.name].getBounds(),{padding:[50,50]}) - } else if (data.hasOwnProperty("fit") && data.fit === true) { - map.fitBounds(polygons[data.name].getBounds(),{padding:[50,50]}) - } } + // Draw error ellipses else if (data.hasOwnProperty("sdlat") && data.hasOwnProperty("sdlon")) { if (!data.hasOwnProperty("iconColor")) { opt.color = "blue"; } //different standard Color Settings if (!data.hasOwnProperty("fillColor")) { opt.fillColor = "blue"; } var ellipse = L.ellipse(new L.LatLng((data.lat*1), (data.lon*1)), [200000*data.sdlon*Math.cos(data.lat*Math.PI/180), 200000*data.sdlat], 0, opt); polygons[data.name] = rightmenu(ellipse); } + // Draw circles and ellipses else if (data.hasOwnProperty("radius")) { if (data.hasOwnProperty("lat") && data.hasOwnProperty("lon")) { var polycirc; if (Array.isArray(data.radius)) { - //polycirc = L.ellipse(new L.LatLng((data.lat*1), (data.lon*1)), [data.radius[0]*Math.cos(data.lat*Math.PI/180), data.radius[1]], data.tilt || 0, opt); polycirc = L.ellipse(new L.LatLng((data.lat*1), (data.lon*1)), [data.radius[0], data.radius[1]], data.tilt || 0, opt); } else { @@ -1692,27 +1672,41 @@ function setMarker(data) { } } } + // Draw arcs (and range rings) else if (data.hasOwnProperty("arc")) { if (data.hasOwnProperty("lat") && data.hasOwnProperty("lon")) { polygons[data.name] = rangerings(new L.LatLng((data.lat*1), (data.lon*1)), data.arc); } } + // Draw a geojson "shape" else if (data.hasOwnProperty("geojson")) { doGeojson(data.name,data.geojson,(data.layer || "unknown"),opt); } + // If we created a shape then apply some generic things to it if (polygons[data.name] !== undefined) { + // Set the layer polygons[data.name].lay = lay; + // if clickable then add popup if (opt.clickable === true) { var words = ""+data.name+""; if (data.popup) { words = words + "" + data.popup; } - polygons[data.name].bindPopup(words, {autoClose:false, closeButton:true, closeOnClick:false, minWidth:200}); + polygons[data.name].bindPopup(words, {autoClose:false, closeButton:true, closeOnClick:true, minWidth:200}); } + // add a tooltip (if supplied) if (data.hasOwnProperty("tooltip")) { polygons[data.name].bindTooltip(data.tooltip); } - //polygons[data.name] = rightmenu(polygons[data.name]); // DCJ Investigate + // add to the layers layers[lay].addLayer(polygons[data.name]); + // fly or fit to the bounds if required + if (data.hasOwnProperty("fly") && data.fly === true) { + map.flyToBounds(polygons[data.name].getBounds(),{padding:[50,50]}) + } + else if (data.hasOwnProperty("fit") && data.fit === true) { + map.fitBounds(polygons[data.name].getBounds(),{padding:[50,50]}) + } } + // Now handle the markers if (typeof data.coordinates == "object") { ll = new L.LatLng(data.coordinates[1],data.coordinates[0]); } else if (data.hasOwnProperty("position") && data.position.hasOwnProperty("lat") && data.position.hasOwnProperty("lon")) { data.lat = data.position.lat*1; @@ -1853,6 +1847,17 @@ function setMarker(data) { }); marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag}); } + else if (data.icon === "sensor") { + data.iconColor = data.iconColor || "#F39C12"; + icon = ''; + var svgcam = "data:image/svg+xml;base64," + btoa(icon); + myMarker = L.divIcon({ + className:"camicon", + iconAnchor: [12, 12], + html:'', + }); + marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag}); + } else if (data.icon === "arrow") { data.iconColor = data.iconColor || "black"; icon = ''; @@ -1958,7 +1963,7 @@ function setMarker(data) { else if (data.icon === "earthquake") { marker = L.marker(ll, { icon: L.divIcon({ className: 'circle e', iconSize: [data.mag*5, data.mag*5] }), title: data.name, draggable:drag }); } - else if (data.icon.match(/^:.*:$/g)) { + else if (data.icon.match(/^:.*:$/g)) { // emoji icon :smile: var em = emojify(data.icon); var col = data.iconColor ?? "#910000"; myMarker = L.divIcon({ @@ -1969,7 +1974,7 @@ function setMarker(data) { marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag}); labelOffset = [12,-4]; } - else if (data.icon.match(/^https?:.*$|^\//)) { + else if (data.icon.match(/^https?:.*$|^\//)) { // web url icon https://... var sz = data.iconSize ?? 32; myMarker = L.icon({ iconUrl: data.icon, @@ -1981,7 +1986,7 @@ function setMarker(data) { labelOffset = [sz/2-4,-4]; delete data.iconSize; } - else if (data.icon.substr(0,3) === "fa-") { + else if (data.icon.substr(0,3) === "fa-") { // fa icon var col = data.iconColor ?? "#910000"; var imod = ""; if (data.icon.indexOf(" ") === -1) { imod = "fa-2x "; } @@ -1995,7 +2000,7 @@ function setMarker(data) { marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag}); labelOffset = [8,-8]; } - else if (data.icon.substr(0,3) === "wi-") { + else if (data.icon.substr(0,3) === "wi-") { // weather icon var col = data.iconColor ?? "#910000"; var imod = ""; if (data.icon.indexOf(" ") === -1) { imod = "wi-2x "; } @@ -2010,7 +2015,7 @@ function setMarker(data) { labelOffset = [16,-16]; } else { - myMarker = L.VectorMarkers.icon({ + myMarker = L.VectorMarkers.icon({ // default - fa-icon in a marker shape icon: data.icon ?? "circle", markerColor: (data.iconColor ?? "#910000"), prefix: 'fa', @@ -2020,7 +2025,7 @@ function setMarker(data) { labelOffset = [6,-6]; } } - else if (data.hasOwnProperty("SIDC")) { + else if (data.hasOwnProperty("SIDC")) { // NATO mil2525 icons // "SIDC":"SFGPU------E***","name":"1.C2 komp","fullname":"1.C2 komp/FTS/INSS" myMarker = new ms.Symbol( data.SIDC.toUpperCase(), { uniqueDesignation:unescape(encodeURIComponent(data.name)) }); // Now that we have a symbol we can ask for the echelon and set the symbol size @@ -2049,7 +2054,7 @@ function setMarker(data) { marker = L.marker(ll, { title:data.name, icon:myicon, draggable:drag }); edgeAware(); } - else { + else { // Otherwise just a generic map marker pin myMarker = L.VectorMarkers.icon({ icon: "circle", markerColor: (data.iconColor ?? "#910000"), @@ -2094,7 +2099,8 @@ function setMarker(data) { } // tidy up altitude - if (data.hasOwnProperty("alt")) { + if (data.hasOwnProperty("alt")||data.hasOwnProperty("altitude")) { + data.alt = data.alt ?? data.altitude; var reft = new RegExp('feet|ft','i'); var refm = new RegExp('metres|m','i'); if ( reft.test(""+data.alt) ) { @@ -2108,8 +2114,8 @@ function setMarker(data) { } } - // remove icon from list of properties, then add all others to popup - if (data.hasOwnProperty("SIDC") && data.hasOwnProperty("options")) { delete data.options; } + // remove items from list of properties, then add all others to popup + if (data.hasOwnProperty("options")) { delete data.options; } if (data.hasOwnProperty("icon")) { delete data.icon; } if (data.hasOwnProperty("iconColor")) { delete data.iconColor; } if (data.hasOwnProperty("photourl")) { @@ -2137,13 +2143,14 @@ function setMarker(data) { if (!Array.isArray(data.weblink) || !data.weblink.length) { if (typeof data.weblink === "string") { words += "more information..."; - } else { + } + else { var tgt = data.weblink.target || "_new"; words += "" + data.weblink.name + ""; } } else { - data.weblink.forEach(function(weblink){ + data.weblink.forEach(function(weblink) { if (typeof weblink === "string") { words += "more information..."; } @@ -2185,10 +2192,11 @@ function setMarker(data) { } } + // Add right click contextmenu marker = rightmenu(marker); - // Add any remaining properties to the info box - var llc = data.lineColor || data.color; + // Delete more already handled properties + var llc = data.lineColor ?? data.color; delete data.lat; delete data.lon; if (data.arc) { delete data.arc; } @@ -2204,15 +2212,17 @@ function setMarker(data) { if (data.hasOwnProperty("fillColor")) { delete data.fillColor; } if (data.hasOwnProperty("radius")) { delete data.radius; } if (data.hasOwnProperty("greatcircle")) { delete data.greatcircle; } + + // then any remaining properties to the info box if (data.popup) { words = data.popup; } else { words += ''; for (var i in data) { if ((i != "name") && (i != "length") && (i != "clickable")) { if (typeof data[i] === "object") { - // words += ''+ i +'' + JSON.stringify(data[i]) + ''; - } else { + } + else { // words += i +" : "+data[i]+""; words += ''+ i +'' + data[i] + ''; } @@ -2223,7 +2233,7 @@ function setMarker(data) { } words = ""+data.name+"" + words.replace(/\${name}/g,data.name); //"X" + words; var wopt = {autoClose:false, closeButton:true, closeOnClick:false, minWidth:200}; - if (words.indexOf('=0 || words.indexOf('=0 ) { wopt.maxWidth="640"; } + if (words.indexOf('=0 || words.indexOf('=0 ) { wopt.maxWidth="640"; } // make popup wider if it has an image or video if (!data.hasOwnProperty("clickable") && data.clickable != false) { marker.bindPopup(words, wopt); marker._popup.dname = data.name; @@ -2241,11 +2251,15 @@ function setMarker(data) { } markers[data.name] = marker; layers[lay].addLayer(marker); - var track; - if (data.track !== undefined) { track = data.track; } - else if (data.hdg !== undefined) { track = data.hdg; } - else if (data.heading !== undefined) { track = data.heading; } - else if (data.bearing !== undefined) { track = data.bearing; } + + // var track; + // if (data.track !== undefined) { track = data.track; } + // else if (data.hdg !== undefined) { track = data.hdg; } + // else if (data.heading !== undefined) { track = data.heading; } + // else if (data.bearing !== undefined) { track = data.bearing; } + + // Now add any leader lines + var track = data.track ?? data.hdg ?? data.heading ?? data.bearing; if (track != undefined) { // if there is a heading if (data.speed != null && data.length === undefined) { // and a speed - lets convert to a leader length data.length = parseFloat(data.speed || "0") * 60; @@ -2278,7 +2292,8 @@ function setMarker(data) { var x3 = x + Math.cos((90-angle-data.accuracy)/180*Math.PI)*lengthAsDegrees/Math.cos(y/180*Math.PI); var ll3 = new L.LatLng(y3,x3); polygon = L.polygon([ ll1, ll2, ll3 ], {weight:2, color:llc||'#900', fillOpacity:0.06, clickable:false}); - } else { + } + else { var ya = y + Math.sin((90-angle)/180*Math.PI)*lengthAsDegrees; var xa = x + Math.cos((90-angle)/180*Math.PI)*lengthAsDegrees/Math.cos(y/180*Math.PI); var lla = new L.LatLng(ya,xa); @@ -2336,10 +2351,6 @@ function doCommand(cmd) { else { panit = false; } document.getElementById("panit").checked = panit; } - if (cmd.hasOwnProperty("hiderightclick")) { - if (cmd.hiderightclick == "true" || cmd.hiderightclick == true) { hiderightclick = true; } - else { hiderightclick = false; } - } if (cmd.hasOwnProperty("showmenu")) { if ((cmd.showmenu === "hide") && (showUserMenu === true)) { showUserMenu = false; @@ -2432,6 +2443,10 @@ function doCommand(cmd) { if (trackMeButton !== undefined) { trackMeButton.state('track-on'); } } } + if (cmd.hasOwnProperty("hiderightclick")) { + if (cmd.hiderightclick == "true" || cmd.hiderightclick == true) { hiderightclick = true; } + else { hiderightclick = false; } + } if (cmd.hasOwnProperty("contextmenu")) { if (typeof cmd.contextmenu === "string") { addmenu = cmd.contextmenu; @@ -2905,7 +2920,7 @@ function doCommand(cmd) { } } } - // Lock the pan so map can be moved + // Lock the pan so map can't be moved if (cmd.hasOwnProperty("panlock")) { if (cmd.panlock == "true" || cmd.panlock == true) { lockit = true; } else { lockit = false; doLock(false); } @@ -2929,7 +2944,6 @@ function doCommand(cmd) { } } } - if (cmd.hasOwnProperty("cluster")) { clusterAt = cmd.cluster; document.getElementById("setclus").value = cmd.cluster; @@ -2966,7 +2980,8 @@ function doCommand(cmd) { if (cmd.bounds.length === 2 && cmd.bounds[0].length === 2 && cmd.bounds[1].length === 2) { if (cmd.hasOwnProperty("fly") && cmd.fly === true) { map.flyToBounds(cmd.bounds); - } else { + } + else { map.fitBounds(cmd.bounds); } }