Merge branch 'master' into leaflet-upgrade

This commit is contained in:
Dave Conway-Jones 2019-04-14 13:06:48 +01:00
commit d4ce4ef023
No known key found for this signature in database
GPG Key ID: 9E7F9C73F5168CD4
11 changed files with 217 additions and 97 deletions

View File

@ -1,5 +1,9 @@
### Change Log for Node-RED Worldmap ### Change Log for Node-RED Worldmap
- v1.5.39 - Add weather-lite icons
- v1.5.38 - Add Esri dark grey and ocean, re-add hikebike, layers
- v1.5.37 - Add .trackpoints to override default number in tracks node. Let tracks optionally be on different layers. Fix marker changing layers Issue #85
- v1.5.36 - Fix contextmenu $name substitution
- v1.5.35 - Add msp.delete command to remove any layers not needed at start (array of names). Issue #83. - v1.5.35 - Add msp.delete command to remove any layers not needed at start (array of names). Issue #83.
- v1.5.34 - Add command.contextmenu to set non-marker context menu (defaults to add marker). - v1.5.34 - Add command.contextmenu to set non-marker context menu (defaults to add marker).
- v1.5.33 - Let blank input disable contextmenu completely. Tidy up help, update dialog polyfill. - v1.5.33 - Let blank input disable contextmenu completely. Tidy up help, update dialog polyfill.

View File

@ -9,21 +9,16 @@ map web page for plotting "things" on.
### Updates ### Updates
- v1.5.35 - Add msp.delete command to remove any layers not needed at start (array of names). Issue #83. - v1.5.39 - Add weather-lite icons
- v1.5.38 - Add Esri dark grey and ocean, re-add hikebike, layers
- v1.5.37 - Add .trackpoints to override default in tracks node. Let tracks optionally be on different layers. Fix marker changing layers Issue #85
- v1.5.36 - Fix contextmenu $name substitution. Issue #84
- v1.5.35 - Add msg.delete command to remove any layers not needed at start (array of names). Issue #83.
- v1.5.34 - Add command.contextmenu to set non-marker context menu (defaults to add marker). - v1.5.34 - Add command.contextmenu to set non-marker context menu (defaults to add marker).
- v1.5.33 - Let blank input disable contextmenu completely. Tidy up help, update dialog polyfill. - v1.5.33 - Let blank input disable contextmenu completely. Tidy up help, update dialog polyfill.
- v1.5.32 - Add .contextmenu custom right click menu, Fix map lock, Close websocket on unload - v1.5.32 - Add .contextmenu custom right click menu, Fix map lock, Close websocket on unload
- v1.5.31 - Fix pan first at start, and coords overlay. Issues #81 and #82 - v1.5.31 - Fix pan first at start, and coords overlay. Issues #81 and #82
- v1.5.30 - Add .tooltip option, ability to remove base layer, search on icon, show mouse pointer co-ordinates - v1.5.30 - Add .tooltip option, ability to remove base layer, search on icon, show mouse pointer co-ordinates
- v1.5.29 - Remove lat/lon from popup if using .popup property. Allow icon to be loaded from http.
- v1.5.28 - Tidy up popup location and timing. Auto add countries overlay if no internet.
- v1.5.27 - Add hide right click option to config panel
- v1.5.26 - Ensure all map tiles loaded over https
- v1.5.25 - Add button command to allow user to add and remove buttons
- v1.5.24 - Ensure hiderightclick does do that, and popup always has close button. Issue #69, #70
- v1.5.23 - Let icon support use of emoji specified as :emoji name:
- v1.5.22 - Slight adjust to label positions for default map marker icon. Add .lineColor for bearing lines
- v1.5.21 - Add .label option to display permanent label. Clean up some excess debug logging
- ... - ...
see [CHANGELOG](https://github.com/dceejay/RedMap/blob/master/CHANGELOG.md) for full list. see [CHANGELOG](https://github.com/dceejay/RedMap/blob/master/CHANGELOG.md) for full list.
@ -58,7 +53,7 @@ Optional properties include
- **bearing** : when combined with speed, draws a vector. - **bearing** : when combined with speed, draws a vector.
- **accuracy** : when combined with bearing, draws a polygon of possible direction. - **accuracy** : when combined with bearing, draws a polygon of possible direction.
- **lineColor** : CSS color name or #rrggbb value for bearing line or accuracy polygon - **lineColor** : CSS color name or #rrggbb value for bearing line or accuracy polygon
- **icon** : <a href="https://fontawesome.com/v4.7.0/icons/" target="mapinfo">font awesome</a> icon name, :emoji name:, or http:// - **icon** : <a href="https://fontawesome.com/v4.7.0/icons/" target="mapinfo">font awesome</a> icon name, <a href="https://github.com/Paul-Reed/weather-icons-lite" target="mapinfo">weather-lite</a> icon, :emoji name:, or http://
- **iconColor** : Standard CSS colour name or #rrggbb hex value. - **iconColor** : Standard CSS colour name or #rrggbb hex value.
- **SIDC** : NATO symbology code (can be used instead of icon). See below. - **SIDC** : NATO symbology code (can be used instead of icon). See below.
- **building** : OSMbulding GeoJSON feature set to add 2.5D buildings to buildings layer. See below. - **building** : OSMbulding GeoJSON feature set to add 2.5D buildings to buildings layer. See below.
@ -80,7 +75,7 @@ by using the **popup** property to supply your own html content.
### Icons ### Icons
You may select any of the Font Awesome set of [icons](https://fontawesome.com/v4.7.0/icons/). You may select any of the Font Awesome set of [icons](https://fontawesome.com/v4.7.0/icons/).
If you use the name without the fa- prefix (eg `male`) you will get the icon inside a generic marker shape. If you use the fa- prefix (eg `fa-male`) you will get the icon on its own. If you use the name without the fa- prefix (eg `male`) you will get the icon inside a generic marker shape. If you use the fa- prefix (eg `fa-male`) you will get the icon on its own. Likewise you can use any of the [Weather-lite](https://github.com/Paul-Reed/weather-icons-lite) icons by using the wi- prefix. These map to icons returned by common weather API such as DarkSky and OpenWeatherMap - for example `"wi-owm-"+msg.payload.weather[0].icon` will pickup the icon returned from the OpenWeatherMap API.
You can also specify an emoji as the icon by using the :emoji name: syntax - for example `:smile:`. Here is a **[list of emojis](https://github.com/dceejay/RedMap/blob/master/emojilist.md)**. You can also specify an emoji as the icon by using the :emoji name: syntax - for example `:smile:`. Here is a **[list of emojis](https://github.com/dceejay/RedMap/blob/master/emojilist.md)**.
@ -256,7 +251,7 @@ The **worldmap in** node can be used to receive various events from the map. Exa
There is a function available to make sending date to Node-RED easier (e.g. from inside a user defined popup), called feedback() - it takes two parameters, name and value, and can be used inside something like an input tag - `onchange='feedback(this.name,this.value)'`. Value can be a more complex object if required as long as it is serialisable. There is a function available to make sending date to Node-RED easier (e.g. from inside a user defined popup), called feedback() - it takes two parameters, name and value, and can be used inside something like an input tag - `onchange='feedback(this.name,this.value)'`. Value can be a more complex object if required as long as it is serialisable.
All actions also include a `msg._sessionid` property that indicates which client session they came from. Any msg sent out that include this will ONLY to that session - so you can target map updates to certain sessions only 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 certain sessions only if required.
## Controlling the map ## Controlling the map
@ -277,7 +272,7 @@ Optional properties include
- **name** - name of the map base layer OR **overlay** - name of overlay layer - **name** - name of the map base layer OR **overlay** - name of overlay layer
- **url** - url of the map layer - **url** - url of the map layer
- **opt** - options object for the new layer - **opt** - options object for the new layer
- **wms** - boolean, specifies if the data is provided by a Web Map Service - **wms** - true/false/grey, specifies if the data is provided by a Web Map Service (if grey sets layer to greyscale)
- **bounds** - sets the bounds of an Overlay-Image. 2 Dimensional Array that defines the top-left and bottom-right Corners (lat/lon Points) - **bounds** - sets the bounds of an Overlay-Image. 2 Dimensional Array that defines the top-left and bottom-right Corners (lat/lon Points)
- **delete** - name or array of names of base layers and/or overlays to delete and remove from layer menu. - **delete** - name or array of names of base layers and/or overlays to delete and remove from layer menu.
- **heatmap** - set heatmap options object see https://github.com/Leaflet/Leaflet.heat#reference - **heatmap** - set heatmap options object see https://github.com/Leaflet/Leaflet.heat#reference
@ -310,7 +305,23 @@ to remove
msg.payload.command = { "button": { "name":"My Fancy Button" } }; msg.payload.command = { "button": { "name":"My Fancy Button" } };
#### To draw a heavily customized Circle on a layer #### To add a custom popup or contextmenu
You can customise a marker's popup, or context menu (right click), by setting the
appropriate property to an html string. Often you will need some embedded javascript
in order to make it do something when you click a button for example. You need to be
careful escaping quotes, and that they remain matched.
For example a popup with a slider (note the \ escaping the internal ' )
popup: '<input name="slide1" type="range" min="1" max="100" value="50" onchange=\'feedback(this.name,this.value)\' style="width:250px;">'
Or a contextmenu with a button
contextmenu: '<button name="Clicker" onclick=\'feedback(this.name)\'>Click me</button>'
#### To draw a heavily customised Circle on a layer
msg.payload.command = { msg.payload.command = {
"name": "circle", "name": "circle",
@ -327,7 +338,8 @@ to remove
#### To add a new base layer #### To add a new base layer
The layer will be called `name`. By default it expects a leaflet Tilelayer style url. You can also use a WMS The layer will be called `name`. By default it expects a leaflet Tilelayer style url. You can also use a WMS
style server by adding a property `wms: true`. (see overlay example below) style server by adding a property `wms: true`. You can also set `wms: "grey"` to set the layer to greyscale which
may let you markers be more visible. (see overlay example below).
msg.payload.command.map = { msg.payload.command.map = {
"name":"OSMhot", "name":"OSMhot",
@ -338,7 +350,7 @@ style server by adding a property `wms: true`. (see overlay example below)
#### To remove base or overlay layers #### To remove base or overlay layers
To remove several layers, either base layers or overlays, you can pass an array of names as follows. To remove several layers, either base layers or overlays, you can pass an array of names as follows.
This can be useful tidy up the initial selections available to the user layer menu. This can be used to tidy up the initial selections available to the user layer menu.
msg.payload.command.map = { msg.payload.command.map = {
"delete":["Watercolor","Ship Nav","Heatmap"] "delete":["Watercolor","Ship Nav","Heatmap"]
@ -353,7 +365,7 @@ To add an overlay instead of a base layer - specify the `overlay` property inste
"url": "https://nowcoast.noaa.gov/arcgis/services/nowcoast/radar_meteo_imagery_nexrad_time/MapServer/WmsServer?", "url": "https://nowcoast.noaa.gov/arcgis/services/nowcoast/radar_meteo_imagery_nexrad_time/MapServer/WmsServer?",
"opt": { "opt": {
"layers": "1", "layers": "1",
"format": 'image/png", "format": "image/png",
"transparent": true, "transparent": true,
"attribution": "NOAA/NWS" "attribution": "NOAA/NWS"
}, },
@ -442,24 +454,26 @@ Create and edit these into an executeable file called **mapserv**, located in th
#! /bin/sh #! /bin/sh
# set this to the path of your WMS map file (which in turn points to your tiles) # set this to the path of your WMS map file (which in turn points to your tiles)
MS_MAPFILE=~/Data/maps/uk.map MS_MAPFILE=/home/pi/maps/gb.map
export MS_MAPFILE export MS_MAPFILE
# and set this to the path of your cgi-mapserv executable # and set this to the path of your cgi-mapserv executable
/usr/bin/mapserv /usr/bin/mapserv
You can then add a new WMS Base layer by injecting a message like You can then add a new WMS Base layer by injecting a message like
msg.payload.command.map = { msg.payload = { command : { map : {
"name": "Local WMS", "name": "Local WMS",
"url": "http://localhost:1880/cgi-bin/mapserv", // we will serve the tiles from this node locally. "url": "/cgi-bin/mapserv", // we will serve the tiles from this node locally.
"opt": { "opt": {
"layers": "gb", // specifies a layer in your map file "layers": "gb", // specifies a layer in your map file
"format": "image/png", "format": "image/png",
"transparent": true, "transparent": true,
"attribution": "© Ordnance Survey, UK" "attribution": "© Ordnance Survey, UK"
}, },
"wms": true // set to true for WMS type mapserver "wms": true // set to true for WMS type mapserver
} }}}
Optionally set `"wms":"grey"` to make the layer to greyscale which may make your markers more visible.
## Demo Flow ## Demo Flow

View File

@ -33,18 +33,20 @@
<option value="OSM">OpenStreetMap</option> <option value="OSM">OpenStreetMap</option>
<option value="Esri">ESRI Streetmap</option> <option value="Esri">ESRI Streetmap</option>
<option value="Esri Satellite">ESRI Satellite</option> <option value="Esri Satellite">ESRI Satellite</option>
<option value="Esri Terrain">ESRI Terrain</option> <option value="Esri Topography">ESRI Topography</option>
// <option value="Esri Terrain">ESRI Terrain</option>
<option value="Esri Dark Grey">ESRI Dark Grey</option>
<option value="Esri Ocean">ESRI Ocean</option> <option value="Esri Ocean">ESRI Ocean</option>
<option value="Nat Geo">National Geographic</option> <option value="Nat Geo">National Geographic</option>
<option value="UK OS Opendata">UK OS Opendata</option> <option value="UK OS Opendata">UK OS Opendata</option>
<!-- <option value="Hike Bike">Hike Bike OSM</option> --> <option value="Hike Bike">Hike Bike OSM</option>
<option value="Terrain">Terrain</option> <option value="Terrain">Terrain</option>
<option value="Watercolor">Stamen Watercolor</option> <option value="Watercolor">Stamen Watercolor</option>
</select> </select>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-cluster"><i class="fa fa-gears"></i> Cluster if</label> <label for="node-input-cluster"><i class="fa fa-dot-circle-o"></i>Cluster when</label>
zoom level is less than <input type="text" id="node-input-cluster" placeholder="13 (0-19)" style="width:80px;"> zoom level is less than <input type="text" id="node-input-cluster" placeholder="0 (0,off - 19)" style="width:100px;">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-maxage"><i class="fa fa-clock-o"></i> Max age</label> <label for="node-input-maxage"><i class="fa fa-clock-o"></i> Max age</label>
@ -102,7 +104,7 @@
<label for="node-input-name"><i class="fa fa-file"></i> Name</label> <label for="node-input-name"><i class="fa fa-file"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name"> <input type="text" id="node-input-name" placeholder="name">
</div> </div>
<div class="form-tips">Set <i>Cluster if</i> to 0 to disable clustering of points.<br/>If <i>Path</i> is left empty, <div class="form-tips">Set <i>Cluster when</i> to 0 to disable clustering of points.<br/>If <i>Path</i> is left empty,
then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the map in a new tab.</div> then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the map in a new tab.</div>
</script> </script>
@ -268,6 +270,14 @@ then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the m
<label for="node-input-depth"><i class="fa fa-map-marker"></i> Number of</label> <label for="node-input-depth"><i class="fa fa-map-marker"></i> Number of</label>
points in track <input type="text" id="node-input-depth" style="width:50%" placeholder="number - default 20"> points in track <input type="text" id="node-input-depth" style="width:50%" placeholder="number - default 20">
</div> </div>
<div class="form-row">
<label for="node-input-layer"><i class="fa fa-map"></i> Track Layer</label>
<select id="node-input-layer">
<option value="combined">on marker layer</option>
<option value="separate">one per marker layer</option>
<option value="single">single Track layer</option>
</select>
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-file"></i> Name</label> <label for="node-input-name"><i class="fa fa-file"></i> Name</label>
<input type="text" id="node-input-name" placeholder="name"> <input type="text" id="node-input-name" placeholder="name">
@ -276,6 +286,8 @@ then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the m
<script type="text/x-red" data-help-name="worldmap-tracks"> <script type="text/x-red" data-help-name="worldmap-tracks">
<p>Creates tracks lines based on a specified number of previous locations.</p> <p>Creates tracks lines based on a specified number of previous locations.</p>
<p>The number of tracked points can be set per marker by specifying <code>msg.payload.trackpoints</code> as part of the update for a marker.</p>
<p>You can also specify the msg.payload.color, weight, opacity and dashArray properties for the track if required.</p>
<p>Holds all the points in memory, so if you have a lot of points held for a <p>Holds all the points in memory, so if you have a lot of points held for a
large depth then memory usage may become excessive.</p> large depth then memory usage may become excessive.</p>
<p>To delete a track send a msg.payload containing both the name of the object and <p>To delete a track send a msg.payload containing both the name of the object and
@ -287,8 +299,9 @@ then by default <code>⌘⇧m</code> - <code>ctrl-shift-m</code> will load the m
category: 'location', category: 'location',
color:"darksalmon", color:"darksalmon",
defaults: { defaults: {
name: {value:""},
depth: {value:20}, depth: {value:20},
name: {value:""} layer: {value:"combined"}
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,

View File

@ -43,10 +43,10 @@ module.exports = function(RED) {
this.path = n.path || "/worldmap"; this.path = n.path || "/worldmap";
if (this.path.charAt(0) != "/") { this.path = "/" + this.path; } if (this.path.charAt(0) != "/") { this.path = "/" + this.path; }
if (!sockets[this.path]) { if (!sockets[this.path]) {
var fullPath = path.posix.join(RED.settings.httpNodeRoot, this.path, 'leaflet', 'sockjs.min.js'); var libPath = path.posix.join(RED.settings.httpNodeRoot, this.path, 'leaflet', 'sockjs.min.js');
sockets[this.path] = sockjs.createServer({sockjs_url:fullPath, log:function() { return; }});
var sockPath = path.posix.join(RED.settings.httpNodeRoot,this.path,'socket'); var sockPath = path.posix.join(RED.settings.httpNodeRoot,this.path,'socket');
sockets[this.path].installHandlers(RED.server, {prefix:sockPath}); sockets[this.path] = sockjs.createServer({prefix:sockPath, sockjs_url:libPath, log:function() { return; }});
sockets[this.path].installHandlers(RED.server);
} }
//this.log("Serving "+__dirname+" as "+this.path); //this.log("Serving "+__dirname+" as "+this.path);
this.log("started at "+this.path); this.log("started at "+this.path);
@ -103,6 +103,7 @@ module.exports = function(RED) {
clients[c].end(); clients[c].end();
} }
} }
clients = {};
sockets[this.path].removeListener('connection', callback); sockets[this.path].removeListener('connection', callback);
for (var i=0; i < RED.httpNode._router.stack.length; i++) { for (var i=0; i < RED.httpNode._router.stack.length; i++) {
var r = RED.httpNode._router.stack[i]; var r = RED.httpNode._router.stack[i];
@ -122,9 +123,9 @@ module.exports = function(RED) {
this.path = n.path || "/worldmap"; this.path = n.path || "/worldmap";
if (this.path.charAt(0) != "/") { this.path = "/" + this.path; } if (this.path.charAt(0) != "/") { this.path = "/" + this.path; }
if (!sockets[this.path]) { if (!sockets[this.path]) {
var fullPath = path.posix.join(RED.settings.httpNodeRoot, this.path, 'leaflet', 'sockjs.min.js'); var libPath = path.posix.join(RED.settings.httpNodeRoot, this.path, 'leaflet', 'sockjs.min.js');
// sockets[this.path] = sockjs.createServer({sockjs_url:fullPath, log:function() { return; }}); var sockPath = path.posix.join(RED.settings.httpNodeRoot,this.path,'socket');
sockets[this.path] = sockjs.createServer({sockjs_url:fullPath, log:function(){return}, prefix:path.posix.join(RED.settings.httpNodeRoot,this.path,'socket'), }); sockets[this.path] = sockjs.createServer({prefix:sockPath, sockjs_url:libPath, log:function() { return; }});
sockets[this.path].installHandlers(RED.server); sockets[this.path].installHandlers(RED.server);
} }
var node = this; var node = this;
@ -141,7 +142,7 @@ module.exports = function(RED) {
client.on('close', function() { client.on('close', function() {
delete clients[client.id]; delete clients[client.id];
node.status({fill:"green",shape:"ring",text:"connected "+Object.keys(clients).length,_sessionid:client.id}); node.status({fill:"green",shape:"ring",text:"connected "+Object.keys(clients).length,_sessionid:client.id});
node.send({payload:{action:"disconnect", clients:Object.keys(clients).length}, topic:"worldmap", _sessionid:client.id}); node.send({payload:{action:"disconnect", clients:Object.keys(clients).length}, topic:node.path.substr(1), _sessionid:client.id});
}); });
} }
@ -151,6 +152,7 @@ module.exports = function(RED) {
clients[c].end(); clients[c].end();
} }
} }
clients = {};
sockets[this.path].removeListener('connection', callback); sockets[this.path].removeListener('connection', callback);
node.status({}); node.status({});
}); });
@ -161,8 +163,9 @@ module.exports = function(RED) {
var WorldMapTracks = function(n) { var WorldMapTracks = function(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.depth = Number(n.depth) || 20; this.depth = parseInt(Number(n.depth) || 20);
this.pointsarray = {}; this.pointsarray = {};
this.layer = n.layer || "combined"; // separate, single
var node = this; var node = this;
node.on("input", function(msg) { node.on("input", function(msg) {
@ -174,12 +177,30 @@ module.exports = function(RED) {
node.send(newmsg); // send the track to be deleted node.send(newmsg); // send the track to be deleted
return; return;
} }
if (!msg.payload.hasOwnProperty("lat") || !msg.payload.hasOwnProperty("lon")) { return; }
if (!node.pointsarray.hasOwnProperty(msg.payload.name)) { if (!node.pointsarray.hasOwnProperty(msg.payload.name)) {
node.pointsarray[msg.payload.name] = []; node.pointsarray[msg.payload.name] = [];
} }
node.pointsarray[msg.payload.name].push(msg.payload); if (msg.payload.hasOwnProperty("trackpoints") && !isNaN(parseInt(msg.payload.trackpoints)) ) {
if (node.pointsarray[msg.payload.name].length > node.depth) { var tl = parseInt(msg.payload.trackpoints);
node.pointsarray[msg.payload.name].shift(); if (tl < 0) { tl = 0; }
if (node.pointsarray[msg.payload.name].length > tl) {
node.pointsarray[msg.payload.name] = node.pointsarray[msg.payload.name].slice(-tl);
}
node.depth = tl;
}
if (node.depth < 2) { return; } // if set less than 2 then don't bother.
var still = false;
if (node.pointsarray[msg.payload.name].length > 0) {
var oldlat = node.pointsarray[msg.payload.name][node.pointsarray[msg.payload.name].length-1].lat;
var oldlon = node.pointsarray[msg.payload.name][node.pointsarray[msg.payload.name].length-1].lon;
if (msg.payload.lat === oldlat && msg.payload.lon === oldlon) { still = true; }
}
if (!still) { node.pointsarray[msg.payload.name].push(msg.payload);
if (node.pointsarray[msg.payload.name].length > node.depth) {
node.pointsarray[msg.payload.name].shift();
}
} }
var line = []; var line = [];
for (var i=0; i<node.pointsarray[msg.payload.name].length; i++) { for (var i=0; i<node.pointsarray[msg.payload.name].length; i++) {
@ -202,6 +223,15 @@ module.exports = function(RED) {
if (line.length > 1) { // only send track if two points or more if (line.length > 1) { // only send track if two points or more
newmsg.payload.line = line; newmsg.payload.line = line;
newmsg.payload.name = msg.payload.name + "_"; newmsg.payload.name = msg.payload.name + "_";
if (node.layer === "separate") {
newmsg.payload.layer = msg.payload.layer + " tracks";
if (newmsg.payload.layer.indexOf('_') === 0) {
newmsg.payload.layer = newmsg.payload.layer.substr(1);
}
}
if (node.layer === "single") {
newmsg.payload.layer = "Tracks";
}
node.send(newmsg); // send the track node.send(newmsg); // send the track
} }
} }

View File

@ -4,6 +4,7 @@ body {
margin:0; margin:0;
padding:0; padding:0;
font:14px Verdana, Arial, sans-serif; font:14px Verdana, Arial, sans-serif;
overflow:hidden;
} }
p, h1, h2, h3, h4 { p, h1, h2, h3, h4 {

View File

@ -23,6 +23,7 @@
<link rel="stylesheet",type="text/css" href="css/map.css"/> <link rel="stylesheet",type="text/css" href="css/map.css"/>
<link rel="stylesheet",type="text/css" href="leaflet/leaflet.css"/> <link rel="stylesheet",type="text/css" href="leaflet/leaflet.css"/>
<link rel="stylesheet",type="text/css" href="leaflet/font-awesome/css/font-awesome.min.css"/> <link rel="stylesheet",type="text/css" href="leaflet/font-awesome/css/font-awesome.min.css"/>
<link rel="stylesheet",type="text/css" href="leaflet/weather-icons-lite/css/weather-icons-lite.min.css"/>
<link rel="stylesheet",type="text/css" href="leaflet/leaflet-vector-markers.css"> <link rel="stylesheet",type="text/css" href="leaflet/leaflet-vector-markers.css">
<link rel="stylesheet",type="text/css" href="leaflet/MarkerCluster.css"> <link rel="stylesheet",type="text/css" href="leaflet/MarkerCluster.css">
<link rel="stylesheet",type="text/css" href="leaflet/MarkerCluster.Default.css"> <link rel="stylesheet",type="text/css" href="leaflet/MarkerCluster.Default.css">
@ -79,7 +80,7 @@
<div id="menu"><table> <div id="menu"><table>
<tr><td><input type='text' name='search' id='search' size='20' style="width:150px;"/>&nbsp;<span onclick='doSearch();'><i class="fa fa-search fa-lg"></i></span></td></tr> <tr><td><input type='text' name='search' id='search' size='20' style="width:150px;"/>&nbsp;<span onclick='doSearch();'><i class="fa fa-search fa-lg"></i></span></td></tr>
<tr><td style="cursor:default"><i class="fa fa-spinner fa-lg fa-fw"></i> Set Max Age <input type='text' name='maxage' id='maxage' value="600" size="5" onchange='setMaxAge();'/>s</td></tr> <tr><td style="cursor:default"><i class="fa fa-spinner fa-lg fa-fw"></i> Set Max Age <input type='text' name='maxage' id='maxage' value="600" size="5" onchange='setMaxAge();'/>s</td></tr>
<tr><td style="cursor:default"><i class="fa fa-search-plus fa-lg fa-fw"></i> Cluster at zoom <<input type='text' name='setclus' id='setclus' size="2" onchange='setCluster();'/></td></tr> <tr><td style="cursor:default"><i class="fa fa-search-plus fa-lg fa-fw"></i> Cluster at zoom <<input type='text' name='setclus' id='setclus' size="2" onchange='setCluster(this.value);'/></td></tr>
<tr><td style="cursor:default"><input type='checkbox' id='panit' onclick='doPanit(this.checked);'/> Auto Pan Map</td></tr> <tr><td style="cursor:default"><input type='checkbox' id='panit' onclick='doPanit(this.checked);'/> Auto Pan Map</td></tr>
<tr><td style="cursor:default"><input type='checkbox' id='lockit' onclick='doLock(this.checked);'/> Lock Map</td></tr> <tr><td style="cursor:default"><input type='checkbox' id='lockit' onclick='doLock(this.checked);'/> Lock Map</td></tr>
<tr><td style="cursor:default"><input type='checkbox' id='heatall' onclick='doHeatAll(this.checked);'/> Heatmap all layers</td></tr> <tr><td style="cursor:default"><input type='checkbox' id='heatall' onclick='doHeatAll(this.checked);'/> Heatmap all layers</td></tr>
@ -127,7 +128,7 @@ var buttons = {};
var marksIndex = 0; var marksIndex = 0;
var popid = ""; var popid = "";
var menuOpen = false; var menuOpen = false;
var clusterAt = 13; var clusterAt = 0;
var maxage = 600; // default max age of icons on map in seconds - cleared after 10 mins var maxage = 600; // default max age of icons on map in seconds - cleared after 10 mins
var baselayername = "OSM grey"; // Default base layer OSM but uniform grey var baselayername = "OSM grey"; // Default base layer OSM but uniform grey
var ibmfoot = "&nbsp;&copy; IBM 2015,2019" var ibmfoot = "&nbsp;&copy; IBM 2015,2019"
@ -139,20 +140,20 @@ var sidebyside;
var layercontrol; var layercontrol;
var iconSz = { var iconSz = {
"Team/Crew": 17.5, "Team/Crew": 24,
"Squad": 20, "Squad": 24,
"Section": 22.5, "Section": 24,
"Platoon/detachment": 25, "Platoon/detachment": 26,
"Company/battery/troop": 27.5, "Company/battery/troop": 28,
"Battalion/squadron": 30, "Battalion/squadron": 30,
"Regiment/group": 32.5, "Regiment/group": 32,
"Brigade": 35, "Brigade": 34,
"Division": 37.5, "Division": 36,
"Corps/MEF": 40, "Corps/MEF": 36,
"Army": 45, "Army": 40,
"Army Group/front": 50, "Army Group/front": 40,
"Region/Theater": 50, "Region/Theater": 44,
"Command": 50 "Command": 44
}; };
// Create the socket // Create the socket
@ -704,18 +705,29 @@ var Esri_WorldImagery = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest
}); });
basemaps["Esri Satellite"] = Esri_WorldImagery; basemaps["Esri Satellite"] = Esri_WorldImagery;
var Esri_WorldShadedRelief = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}', { var Esri_WorldTopoMap = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri', attribution: 'Tiles &copy; Esri &mdash; Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community'
maxNativeZoom:13
}); });
basemaps["Esri Terrain"] = Esri_WorldShadedRelief; basemaps["Esri Topography"] = Esri_WorldTopoMap;
// var Esri_WorldShadedRelief = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}', {
// attribution: 'Tiles &copy; Esri',
// maxNativeZoom:13
// });
// basemaps["Esri Terrain"] = Esri_WorldShadedRelief;
var Esri_OceanBasemap = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Ocean_Basemap/MapServer/tile/{z}/{y}/{x}', { var Esri_OceanBasemap = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Ocean_Basemap/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri', attribution: 'Tiles &copy; Esri &mdash; Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri',
maxNativeZoom:10 maxZoom: 13
}); });
basemaps["Esri Ocean"] = Esri_OceanBasemap; basemaps["Esri Ocean"] = Esri_OceanBasemap;
var Esri_WorldGrayCanvas = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Esri, DeLorme, NAVTEQ',
maxZoom: 16
});
basemaps["Esri Dark Grey"] = Esri_WorldGrayCanvas;
// var OpenMapSurfer_Roads = L.tileLayer('https://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}', { // var OpenMapSurfer_Roads = L.tileLayer('https://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}', {
// maxZoom: 18, // maxZoom: 18,
// attribution: 'Imagery from <a href="https://giscience.uni-hd.de/">University of Heidelberg</a> &mdash; Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' // attribution: 'Imagery from <a href="https://giscience.uni-hd.de/">University of Heidelberg</a> &mdash; Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
@ -745,12 +757,11 @@ var NLS_OS_opendata = L.tileLayer('https://geo.nls.uk/maps/opendata/{z}/{x}/{y}.
}); });
basemaps["UK OS Opendata"] = NLS_OS_opendata; basemaps["UK OS Opendata"] = NLS_OS_opendata;
// //https://tiles.wmflabs.org/hikebike/{zoom}/{x}/{y}.png var HikeBike_HikeBike = L.tileLayer('https://tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', {
// var HikeBike_HikeBike = L.tileLayer('https://tiles.wmflabs.org/hikebike/{z}/{x}/{y}.png', { maxZoom: 19,
// maxZoom: 19, attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
// attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' });
// }); basemaps["Hike Bike"] = HikeBike_HikeBike;
// basemaps["Hike Bike"] = HikeBike_HikeBike;
var NLS_OS_1919_1947 = L.tileLayer( 'https://nls-{s}.tileserver.com/nls/{z}/{x}/{y}.jpg', { var NLS_OS_1919_1947 = L.tileLayer( 'https://nls-{s}.tileserver.com/nls/{z}/{x}/{y}.jpg', {
attribution: 'Historical Maps Layer, from <a href="https://maps.nls.uk/projects/api/">NLS Maps</a>', attribution: 'Historical Maps Layer, from <a href="https://maps.nls.uk/projects/api/">NLS Maps</a>',
@ -935,7 +946,7 @@ function setMarker(data) {
delMarker(data.name); delMarker(data.name);
return; return;
} }
data = allData[data.name] = Object.assign(allData[data.name] || {}, data);
var ll; var ll;
var lli = null; var lli = null;
var opt = {}; var opt = {};
@ -962,30 +973,46 @@ function setMarker(data) {
map.addLayer(layers["buildings"]); map.addLayer(layers["buildings"]);
return; return;
} }
var lay = data.layer || "unknown"; var lay = data.layer || "unknown";
if (typeof layers[lay] == "undefined") { // add layer if if doesn't exist if (!data.hasOwnProperty("action") || data.action.indexOf("layer") === -1) {
if (clusterAt > 0) { if (typeof layers[lay] == "undefined") { // add layer if if doesn't exist
layers[lay] = new L.MarkerClusterGroup({ if (clusterAt > 0) {
maxClusterRadius:50, layers[lay] = new L.MarkerClusterGroup({
spiderfyDistanceMultiplier:1.8, maxClusterRadius:50,
disableClusteringAtZoom:clusterAt spiderfyDistanceMultiplier:1.8,
//zoomToBoundsOnClick:false disableClusteringAtZoom:clusterAt
}); //zoomToBoundsOnClick:false
});
}
else {
layers[lay] = new L.LayerGroup();
}
overlays[lay] = layers[lay];
if (showLayerMenu !== false) {
layercontrol.addOverlay(layers[lay],lay);
}
map.addLayer(overlays[lay]);
//console.log("ADDED LAYER",lay,layers);
} }
else { if (!allData.hasOwnProperty(data.name)) { allData[data.name] = {}; }
layers[lay] = new L.LayerGroup(); delete data.action;
} Object.keys(data).forEach(key => {
overlays[lay] = layers[lay]; if (data[key] == null) { delete allData[data.name][key]; }
if (showLayerMenu !== false) { else { allData[data.name][key] = data[key]; }
layercontrol.addOverlay(layers[lay],lay); });
} data = Object.assign({},allData[data.name]);
map.addLayer(overlays[lay]);
//console.log("ADDED LAYER",lay,layers);
} }
delete data.action;
if (typeof markers[data.name] != "undefined") { if (typeof markers[data.name] != "undefined") {
try {layers[lay].removeLayer(markers[data.name]); } if (markers[data.name].lay !== data.layer) {
catch(e) { console.log("OOPS"); } delMarker(data.name);
}
else {
try {layers[lay].removeLayer(markers[data.name]); }
catch(e) { console.log("OOPS"); }
}
} }
if (typeof polygons[data.name] != "undefined") { layers[lay].removeLayer(polygons[data.name]); } if (typeof polygons[data.name] != "undefined") { layers[lay].removeLayer(polygons[data.name]); }
@ -1033,7 +1060,6 @@ function setMarker(data) {
layers[lay].addLayer(polycirc); layers[lay].addLayer(polycirc);
} }
} }
//console.log("handling",data.name);
if (typeof data.coordinates == "object") { ll = new L.LatLng(data.coordinates[1],data.coordinates[0]); } 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")) { else if (data.hasOwnProperty("position") && data.position.hasOwnProperty("lat") && data.position.hasOwnProperty("lon")) {
data.lat = data.position.lat*1; data.lat = data.position.lat*1;
@ -1053,8 +1079,6 @@ function setMarker(data) {
else if (data.hasOwnProperty("latitude") && data.hasOwnProperty("longitude") && data.hasOwnProperty("intensity")) { lli = new L.LatLng((data.latitude*1), (data.longitude*1), (data.intensity*1)); } else if (data.hasOwnProperty("latitude") && data.hasOwnProperty("longitude") && data.hasOwnProperty("intensity")) { lli = new L.LatLng((data.latitude*1), (data.longitude*1), (data.intensity*1)); }
else { lli = ll } else { lli = ll }
var words="";
// Create the icons... handle plane, car, ship, wind, earthquake as specials // Create the icons... handle plane, car, ship, wind, earthquake as specials
var marker, myMarker; var marker, myMarker;
var icon, q; var icon, q;
@ -1258,6 +1282,19 @@ function setMarker(data) {
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag}); marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
labelOffset = [8,-8]; labelOffset = [8,-8];
} }
else if (data.icon && (data.icon.substr(0,3) === "wi-")) {
var col = data.iconColor || "#910000";
var imod = "";
if (data.icon.indexOf(" ") === -1) { imod = "wi-2x "; }
myMarker = L.divIcon({
className:"wiicon",
html: '<center><i class="wi wi-fw '+imod+data.icon+'" style="color:'+col+'"></i></center>',
iconSize: [32, 32],
popupAnchor: [0, -16]
});
marker = L.marker(ll, {title:data.name, icon:myMarker, draggable:drag});
labelOffset = [16,-16];
}
else { else {
myMarker = L.VectorMarkers.icon({ myMarker = L.VectorMarkers.icon({
icon: data.icon || "circle", icon: data.icon || "circle",
@ -1300,7 +1337,7 @@ function setMarker(data) {
delete data.photoUrl; delete data.photoUrl;
} }
if (data.hasOwnProperty("videoUrl")) { if (data.hasOwnProperty("videoUrl")) {
words += '<video controls autoplay><source src="'+data.videoUrl+'" type="video/mp4">Your browser does not support the video tag.</video>'; words += '<video controls muted autoplay width="320"><source src="'+data.videoUrl+'" type="video/mp4">Your browser does not support the video tag.</video>';
delete data.videoUrl; delete data.videoUrl;
} }
if (data.hasOwnProperty("ttl")) { // save expiry time for this marker if (data.hasOwnProperty("ttl")) { // save expiry time for this marker
@ -1340,6 +1377,12 @@ function setMarker(data) {
delete data.tooltip; delete data.tooltip;
} }
} }
// customise right click context menu
var rightcontext = "<button id='delbutton' onclick='delMarker(\""+data.name+"\",true);'>Delete</button>";
if ((data.contextmenu !== undefined) && (typeof data.contextmenu === "string")) {
rightcontext = data.contextmenu.replace(/$name/g,'\""+'+data.name+'+"\"');
delete data.contextmenu;
}
// Add any remaining properties to the info box // Add any remaining properties to the info box
var llc = data.lineColor; var llc = data.lineColor;
@ -1347,6 +1390,11 @@ function setMarker(data) {
delete data.lon; delete data.lon;
if (data.layer) { delete data.layer; } if (data.layer) { delete data.layer; }
if (data.lineColor) { delete data.lineColor; } if (data.lineColor) { delete data.lineColor; }
if (data.color) { delete data.color; }
if (data.weight) { delete data.weight; }
if (data.tracklength) { delete data.tracklength; }
if (data.dashArray) { delete data.dashArray; }
if (data.fill) { delete data.fill; }
if (data.draggable) { delete data.draggable; } if (data.draggable) { delete data.draggable; }
for (var i in data) { for (var i in data) {
if ((i != "name") && (i != "length")) { if ((i != "name") && (i != "length")) {

View File

@ -17,7 +17,7 @@ JSON.parse(a)}catch(d){}c(b)})}}}(),F={loadedItems:{},items:[],getPixelFootprint
(c[k]=1,m.push(d),l.push(k),m.push(k),l.push(f));d=m.pop();f=l.pop()}for(e=0;e<a;e++)c[e]&&n.push(b[2*e],b[2*e+1]);a=n;if(!(8>a.length))return a},resetItems:function(){this.items=[];this.loadedItems={};Y.reset()},addRenderItems:function(b,a){for(var c,d,f,e=Ja.read(b),g=0,h=e.length;g<h;g++)c=e[g],f=c.id||[c.footprint[0],c.footprint[1],c.height,c.minHeight].join(),!this.loadedItems[f]&&(d=this.scale(c))&&(d.scale=a?0:1,this.items.push(d),this.loadedItems[f]=1);Ca()},scale:function(b){var a={},c=6/ (c[k]=1,m.push(d),l.push(k),m.push(k),l.push(f));d=m.pop();f=l.pop()}for(e=0;e<a;e++)c[e]&&n.push(b[2*e],b[2*e+1]);a=n;if(!(8>a.length))return a},resetItems:function(){this.items=[];this.loadedItems={};Y.reset()},addRenderItems:function(b,a){for(var c,d,f,e=Ja.read(b),g=0,h=e.length;g<h;g++)c=e[g],f=c.id||[c.footprint[0],c.footprint[1],c.height,c.minHeight].join(),!this.loadedItems[f]&&(d=this.scale(c))&&(d.scale=a?0:1,this.items.push(d),this.loadedItems[f]=1);Ca()},scale:function(b){var a={},c=6/
oa(2,x-G);b.id&&(a.id=b.id);a.height=U(b.height/c,ga);a.minHeight=isNaN(b.minHeight)?0:b.minHeight/c;if(!(a.minHeight>ga)&&(a.footprint=this.getPixelFootprint(b.footprint),a.footprint)){for(var d=a.footprint,f=Infinity,e=-Infinity,g=Infinity,h=-Infinity,k=0,m=d.length-3;k<m;k+=2)f=U(f,d[k]),e=K(e,d[k]),g=U(g,d[k+1]),h=K(h,d[k+1]);a.center={x:f+(e-f)/2<<0,y:g+(h-g)/2<<0};b.radius&&(a.radius=b.radius*na);b.shape&&(a.shape=b.shape);b.roofShape&&(a.roofShape=b.roofShape);"cone"!==a.roofShape&&"dome"!== oa(2,x-G);b.id&&(a.id=b.id);a.height=U(b.height/c,ga);a.minHeight=isNaN(b.minHeight)?0:b.minHeight/c;if(!(a.minHeight>ga)&&(a.footprint=this.getPixelFootprint(b.footprint),a.footprint)){for(var d=a.footprint,f=Infinity,e=-Infinity,g=Infinity,h=-Infinity,k=0,m=d.length-3;k<m;k+=2)f=U(f,d[k]),e=K(e,d[k]),g=U(g,d[k+1]),h=K(h,d[k+1]);a.center={x:f+(e-f)/2<<0,y:g+(h-g)/2<<0};b.radius&&(a.radius=b.radius*na);b.shape&&(a.shape=b.shape);b.roofShape&&(a.roofShape=b.roofShape);"cone"!==a.roofShape&&"dome"!==
a.roofShape||a.shape||!va(a.footprint)||(a.shape="cylinder");if(b.holes){a.holes=[];for(var l,d=0,f=b.holes.length;d<f;d++)(l=this.getPixelFootprint(b.holes[d]))&&a.holes.push(l)}var n;b.wallColor&&(n=I.parse(b.wallColor))&&(n=n.alpha(C),a.altColor=""+n.lightness(0.8),a.wallColor=""+n);b.roofColor&&(n=I.parse(b.roofColor))&&(a.roofColor=""+n.alpha(C));b.relationId&&(a.relationId=b.relationId);a.hitColor=Y.idToColor(b.relationId||b.id);a.roofHeight=isNaN(b.roofHeight)?0:b.roofHeight/c;if(!(a.height+ a.roofShape||a.shape||!va(a.footprint)||(a.shape="cylinder");if(b.holes){a.holes=[];for(var l,d=0,f=b.holes.length;d<f;d++)(l=this.getPixelFootprint(b.holes[d]))&&a.holes.push(l)}var n;b.wallColor&&(n=I.parse(b.wallColor))&&(n=n.alpha(C),a.altColor=""+n.lightness(0.8),a.wallColor=""+n);b.roofColor&&(n=I.parse(b.roofColor))&&(a.roofColor=""+n.alpha(C));b.relationId&&(a.relationId=b.relationId);a.hitColor=Y.idToColor(b.relationId||b.id);a.roofHeight=isNaN(b.roofHeight)?0:b.roofHeight/c;if(!(a.height+
a.roofHeight<=a.minHeight))return a}},set:function(b){this.isStatic=!0;this.resetItems();this._staticData=b;this.addRenderItems(this._staticData,!0)},load:function(b,a){this.src=b||"http://{s}.data.osmbuildings.org/0.2/{k}/tile/{z}/{x}/{y}.json".replace("{k}",a||"anonymous");this.update()},update:function(){function b(a){g.addRenderItems(a)}this.resetItems();if(!(x<G))if(this.isStatic&&this._staticData)this.addRenderItems(this._staticData);else if(this.src){var a=16<x?256<<x-16:256>>16-x,c=p/a<<0, a.roofHeight<=a.minHeight))return a}},set:function(b){this.isStatic=!0;this.resetItems();this._staticData=b;this.addRenderItems(this._staticData,!0)},load:function(b,a){this.src=b||"https://{s}.data.osmbuildings.org/0.2/{k}/tile/{z}/{x}/{y}.json".replace("{k}",a||"anonymous");this.update()},update:function(){function b(a){g.addRenderItems(a)}this.resetItems();if(!(x<G))if(this.isStatic&&this._staticData)this.addRenderItems(this._staticData);else if(this.src){var a=16<x?256<<x-16:256>>16-x,c=p/a<<0,
d=n/a<<0,f=qa((p+B)/a),a=qa((n+v)/a),e,g=this;for(e=d;e<=a;e++)for(d=c;d<=f;d++)this.loadTile(d,e,16,b)}},loadTile:function(b,a,c,d){b=this.src.replace("{s}","abcd"[(b+a)%4]).replace("{x}",b).replace("{y}",a).replace("{z}",c);return Ka.loadJSON(b,d)}},Z={draw:function(b,a,c,d,f,e,g,h){var k,m=this._extrude(b,a,d,f,e,g),l=[];if(c)for(a=0,k=c.length;a<k;a++)l[a]=this._extrude(b,c[a],d,f,e,g);b.fillStyle=h;b.beginPath();this._ring(b,m);if(c)for(a=0,k=l.length;a<k;a++)this._ring(b,l[a]);b.closePath(); d=n/a<<0,f=qa((p+B)/a),a=qa((n+v)/a),e,g=this;for(e=d;e<=a;e++)for(d=c;d<=f;d++)this.loadTile(d,e,16,b)}},loadTile:function(b,a,c,d){b=this.src.replace("{s}","abcd"[(b+a)%4]).replace("{x}",b).replace("{y}",a).replace("{z}",c);return Ka.loadJSON(b,d)}},Z={draw:function(b,a,c,d,f,e,g,h){var k,m=this._extrude(b,a,d,f,e,g),l=[];if(c)for(a=0,k=c.length;a<k;a++)l[a]=this._extrude(b,c[a],d,f,e,g);b.fillStyle=h;b.beginPath();this._ring(b,m);if(c)for(a=0,k=l.length;a<k;a++)this._ring(b,l[a]);b.closePath();
b.stroke();b.fill()},_extrude:function(b,a,c,d,f,e){c=q/(q-c);for(var g=q/(q-d),h={x:0,y:0},k={x:0,y:0},m,l,y=[],s=0,t=a.length-3;s<t;s+=2)h.x=a[s]-p,h.y=a[s+1]-n,k.x=a[s+2]-p,k.y=a[s+3]-n,m=r.project(h,c),l=r.project(k,c),d&&(h=r.project(h,g),k=r.project(k,g)),(k.x-h.x)*(m.y-h.y)>(m.x-h.x)*(k.y-h.y)&&(b.fillStyle=h.x<k.x&&h.y<k.y||h.x>k.x&&h.y>k.y?e:f,b.beginPath(),this._ring(b,[k.x,k.y,h.x,h.y,m.x,m.y,l.x,l.y]),b.closePath(),b.fill()),y[s]=m.x,y[s+1]=m.y;return y},_ring:function(b,a){b.moveTo(a[0], b.stroke();b.fill()},_extrude:function(b,a,c,d,f,e){c=q/(q-c);for(var g=q/(q-d),h={x:0,y:0},k={x:0,y:0},m,l,y=[],s=0,t=a.length-3;s<t;s+=2)h.x=a[s]-p,h.y=a[s+1]-n,k.x=a[s+2]-p,k.y=a[s+3]-n,m=r.project(h,c),l=r.project(k,c),d&&(h=r.project(h,g),k=r.project(k,g)),(k.x-h.x)*(m.y-h.y)>(m.x-h.x)*(k.y-h.y)&&(b.fillStyle=h.x<k.x&&h.y<k.y||h.x>k.x&&h.y>k.y?e:f,b.beginPath(),this._ring(b,[k.x,k.y,h.x,h.y,m.x,m.y,l.x,l.y]),b.closePath(),b.fill()),y[s]=m.x,y[s+1]=m.y;return y},_ring:function(b,a){b.moveTo(a[0],
a[1]);for(var c=2,d=a.length-1;c<d;c+=2)b.lineTo(a[c],a[c+1])},simplified:function(b,a,c){b.beginPath();this._ringAbs(b,a);if(c){a=0;for(var d=c.length;a<d;a++)this._ringAbs(b,c[a])}b.closePath();b.stroke();b.fill()},_ringAbs:function(b,a){b.moveTo(a[0]-p,a[1]-n);for(var c=2,d=a.length-1;c<d;c+=2)b.lineTo(a[c]-p,a[c+1]-n)},shadow:function(b,a,c,d,f){for(var e=null,g={x:0,y:0},h={x:0,y:0},k,m,l=0,q=a.length-3;l<q;l+=2)g.x=a[l]-p,g.y=a[l+1]-n,h.x=a[l+2]-p,h.y=a[l+3]-n,k=z.project(g,d),m=z.project(h, a[1]);for(var c=2,d=a.length-1;c<d;c+=2)b.lineTo(a[c],a[c+1])},simplified:function(b,a,c){b.beginPath();this._ringAbs(b,a);if(c){a=0;for(var d=c.length;a<d;a++)this._ringAbs(b,c[a])}b.closePath();b.stroke();b.fill()},_ringAbs:function(b,a){b.moveTo(a[0]-p,a[1]-n);for(var c=2,d=a.length-1;c<d;c+=2)b.lineTo(a[c]-p,a[c+1]-n)},shadow:function(b,a,c,d,f){for(var e=null,g={x:0,y:0},h={x:0,y:0},k,m,l=0,q=a.length-3;l<q;l+=2)g.x=a[l]-p,g.y=a[l+1]-n,h.x=a[l+2]-p,h.y=a[l+3]-n,k=z.project(g,d),m=z.project(h,
@ -41,8 +41,8 @@ case "dome":w.hitArea(b,a.center,a.radius,a.radius/2,c+a.roofHeight,c,g);break;c
$,A={container:document.createElement("DIV"),items:[],init:function(){this.container.style.pointerEvents="none";this.container.style.position="absolute";this.container.style.left=0;this.container.style.top=0;z.context=this.createContext(this.container);ia.context=this.createContext(this.container);r.context=this.createContext(this.container);Y.context=this.createContext()},render:function(b){Ga(function(){b||(z.render(),ia.render(),Y.render());r.render()})},createContext:function(b){var a=document.createElement("CANVAS"); $,A={container:document.createElement("DIV"),items:[],init:function(){this.container.style.pointerEvents="none";this.container.style.position="absolute";this.container.style.left=0;this.container.style.top=0;z.context=this.createContext(this.container);ia.context=this.createContext(this.container);r.context=this.createContext(this.container);Y.context=this.createContext()},render:function(b){Ga(function(){b||(z.render(),ia.render(),Y.render());r.render()})},createContext:function(b){var a=document.createElement("CANVAS");
a.style.transform="translate3d(0, 0, 0)";a.style.imageRendering="optimizeSpeed";a.style.position="absolute";a.style.left=0;a.style.top=0;var c=a.getContext("2d");c.lineCap="round";c.lineJoin="round";c.lineWidth=1;c.imageSmoothingEnabled=!1;this.items.push(a);b&&b.appendChild(a);return c},appendTo:function(b){b.appendChild(this.container)},remove:function(){this.container.parentNode.removeChild(this.container)},setSize:function(b,a){for(var c=0,d=this.items.length;c<d;c++)this.items[c].width=b,this.items[c].height= a.style.transform="translate3d(0, 0, 0)";a.style.imageRendering="optimizeSpeed";a.style.position="absolute";a.style.left=0;a.style.top=0;var c=a.getContext("2d");c.lineCap="round";c.lineJoin="round";c.lineWidth=1;c.imageSmoothingEnabled=!1;this.items.push(a);b&&b.appendChild(a);return c},appendTo:function(b){b.appendChild(this.container)},remove:function(){this.container.parentNode.removeChild(this.container)},setSize:function(b,a){for(var c=0,d=this.items.length;c<d;c++)this.items[c].width=b,this.items[c].height=
a},setPosition:function(b,a){this.container.style.left=b+"px";this.container.style.top=a+"px"}};A.init();u=function(b){this.offset={x:0,y:0};b&&b.addLayer(this)};t=u.prototype=L.Layer?new L.Layer:{};t.addTo=function(b){b.addLayer(this);return this};t.onAdd=function(b){this.map=b;A.appendTo(b._panes.overlayPane);var a=this.getOffset(),c=b.getPixelOrigin();la({width:b._size.x,height:b._size.y});var d=c.y-a.y;p=c.x-a.x;n=d;ma(b._zoom);A.setPosition(-a.x,-a.y);b.on({move:this.onMove,moveend:this.onMoveEnd, a},setPosition:function(b,a){this.container.style.left=b+"px";this.container.style.top=a+"px"}};A.init();u=function(b){this.offset={x:0,y:0};b&&b.addLayer(this)};t=u.prototype=L.Layer?new L.Layer:{};t.addTo=function(b){b.addLayer(this);return this};t.onAdd=function(b){this.map=b;A.appendTo(b._panes.overlayPane);var a=this.getOffset(),c=b.getPixelOrigin();la({width:b._size.x,height:b._size.y});var d=c.y-a.y;p=c.x-a.x;n=d;ma(b._zoom);A.setPosition(-a.x,-a.y);b.on({move:this.onMove,moveend:this.onMoveEnd,
zoomstart:this.onZoomStart,zoomend:this.onZoomEnd,resize:this.onResize,viewreset:this.onViewReset,click:this.onClick},this);if(b.options.zoomAnimation)b.on("zoomanim",this.onZoom,this);b.attributionControl&&b.attributionControl.addAttribution('&copy; <a href="http://osmbuildings.org">OSM Buildings</a>');F.update()};t.onRemove=function(){var b=this.map;b.attributionControl&&b.attributionControl.removeAttribution('&copy; <a href="http://osmbuildings.org">OSM Buildings</a>');b.off({move:this.onMove, zoomstart:this.onZoomStart,zoomend:this.onZoomEnd,resize:this.onResize,viewreset:this.onViewReset,click:this.onClick},this);if(b.options.zoomAnimation)b.on("zoomanim",this.onZoom,this);b.attributionControl&&b.attributionControl.addAttribution('&copy; <a href="https://osmbuildings.org">OSM Buildings</a>');F.update()};t.onRemove=function(){var b=this.map;b.attributionControl&&b.attributionControl.removeAttribution('&copy; <a href="https://osmbuildings.org">OSM Buildings</a>');b.off({move:this.onMove,
moveend:this.onMoveEnd,zoomstart:this.onZoomStart,zoomend:this.onZoomEnd,resize:this.onResize,viewreset:this.onViewReset,click:this.onClick},this);b.options.zoomAnimation&&b.off("zoomanim",this.onZoom,this);A.remove()};t.onMove=function(b){b=this.getOffset();ea({x:this.offset.x-b.x,y:this.offset.y-b.y})};t.onMoveEnd=function(b){if(this.noMoveEnd)this.noMoveEnd=!1;else{var a=this.map;b=this.getOffset();var c=a.getPixelOrigin();this.offset=b;A.setPosition(-b.x,-b.y);ea({x:0,y:0});la({width:a._size.x, moveend:this.onMoveEnd,zoomstart:this.onZoomStart,zoomend:this.onZoomEnd,resize:this.onResize,viewreset:this.onViewReset,click:this.onClick},this);b.options.zoomAnimation&&b.off("zoomanim",this.onZoom,this);A.remove()};t.onMove=function(b){b=this.getOffset();ea({x:this.offset.x-b.x,y:this.offset.y-b.y})};t.onMoveEnd=function(b){if(this.noMoveEnd)this.noMoveEnd=!1;else{var a=this.map;b=this.getOffset();var c=a.getPixelOrigin();this.offset=b;A.setPosition(-b.x,-b.y);ea({x:0,y:0});la({width:a._size.x,
height:a._size.y});a=c.y-b.y;p=c.x-b.x;n=a;A.render();F.update()}};t.onZoomStart=function(b){Q=!0;A.render()};t.onZoom=function(b){};t.onZoomEnd=function(b){b=this.map;var a=this.getOffset(),c=b.getPixelOrigin(),d=c.y-a.y;p=c.x-a.x;n=d;b=b._zoom;Q=!1;ma(b);F.update();A.render();this.noMoveEnd=!0};t.onResize=function(){};t.onViewReset=function(){var b=this.getOffset();this.offset=b;A.setPosition(-b.x,-b.y);ea({x:0,y:0})};t.onClick=function(b){var a=Y.getIdFromXY(b.containerPoint.x,b.containerPoint.y); height:a._size.y});a=c.y-b.y;p=c.x-b.x;n=a;A.render();F.update()}};t.onZoomStart=function(b){Q=!0;A.render()};t.onZoom=function(b){};t.onZoomEnd=function(b){b=this.map;var a=this.getOffset(),c=b.getPixelOrigin(),d=c.y-a.y;p=c.x-a.x;n=d;b=b._zoom;Q=!1;ma(b);F.update();A.render();this.noMoveEnd=!0};t.onResize=function(){};t.onViewReset=function(){var b=this.getOffset();this.offset=b;A.setPosition(-b.x,-b.y);ea({x:0,y:0})};t.onClick=function(b){var a=Y.getIdFromXY(b.containerPoint.x,b.containerPoint.y);
a&&ua({feature:a,lat:b.latlng.lat,lon:b.latlng.lng})};t.getOffset=function(){return L.DomUtil.getPosition(this.map._mapPane)};t.style=function(b){b=b||{};var a;if(a=b.color||b.wallColor)H=I.parse(a),ha=""+H.alpha(C),ba=H.lightness(0.8),aa=""+ba.alpha(C),O=H.lightness(1.2),X=""+O.alpha(C);b.roofColor&&(O=I.parse(b.roofColor),X=""+O.alpha(C));void 0!==b.shadows&&(z.enabled=!!b.shadows);A.render();return this};t.date=function(b){z.date=b;z.render();return this};t.load=function(b){F.load(b);return this}; a&&ua({feature:a,lat:b.latlng.lat,lon:b.latlng.lng})};t.getOffset=function(){return L.DomUtil.getPosition(this.map._mapPane)};t.style=function(b){b=b||{};var a;if(a=b.color||b.wallColor)H=I.parse(a),ha=""+H.alpha(C),ba=H.lightness(0.8),aa=""+ba.alpha(C),O=H.lightness(1.2),X=""+O.alpha(C);b.roofColor&&(O=I.parse(b.roofColor),X=""+O.alpha(C));void 0!==b.shadows&&(z.enabled=!!b.shadows);A.render();return this};t.date=function(b){z.date=b;z.render();return this};t.load=function(b){F.load(b);return this};
t.set=function(b){F.set(b);return this};var ta=function(){};t.each=function(b){ta=function(a){return b(a)};return this};var ua=function(){};t.click=function(b){ua=function(a){return b(a)};return this};u.VERSION="0.2.2b";u.ATTRIBUTION='&copy; <a href="http://osmbuildings.org">OSM Buildings</a>';ca.OSMBuildings=u})(this); t.set=function(b){F.set(b);return this};var ta=function(){};t.each=function(b){ta=function(a){return b(a)};return this};var ua=function(){};t.click=function(b){ua=function(a){return b(a)};return this};u.VERSION="0.2.2b";u.ATTRIBUTION='&copy; <a href="https://osmbuildings.org">OSM Buildings</a>';ca.OSMBuildings=u})(this);

View File

@ -35,8 +35,9 @@ L.TileLayer.GrayWMS = L.TileLayer.WMS.extend({
var imgd = ctx.getImageData(0, 0, this._layer.options.tileSize, this._layer.options.tileSize); var imgd = ctx.getImageData(0, 0, this._layer.options.tileSize, this._layer.options.tileSize);
var pix = imgd.data; var pix = imgd.data;
for (var i = 0, n = pix.length; i < n; i += 4) { for (var i = 0, n = pix.length; i < n; i += 4) {
pix[i] = pix[i + 1] = pix[i + 2] = (3 * pix[i] + 4 * pix[i + 1] + pix[i + 2]) / 8; //pix[i] = pix[i + 1] = pix[i + 2] = (3 * pix[i] + 4 * pix[i + 1] + pix[i + 2]) / 8;
//pix[i] = pix[i + 1] = pix[i + 2] = (2 * pix[i] + 3 * pix[i + 1] + 3 * pix[i + 2]) / 6; // Lighten the scale slightly to make markers more obvious
pix[i] = pix[i + 1] = pix[i + 2] = (3 * pix[i] + 4 * pix[i + 1] + pix[i + 2]) / 8 * 3 / 4 + 64;
} }
ctx.putImageData(imgd, 0, 0); ctx.putImageData(imgd, 0, 0);
this.removeAttribute("crossorigin"); this.removeAttribute("crossorigin");

View File

@ -0,0 +1,9 @@
/*!
* Weather Icons Lite
* Weather themed icons for Bootstrap
* Author - Paul Reed
* Maintained at https://github.com/Paul-Reed/weather-icons-lite
* Description - A lighweight version of Weather Icons
* Credit to Erik Flowers - erik@helloerik.com
*
*/@font-face{font-family:'weather-icons-lite';font-weight:normal;font-style:normal;src:url('../fonts/weather-icons-lite.woff2') format('woff2'),url('../fonts/weather-icons-lite.woff') format('woff'),url('../fonts/weather-icons-lite.ttf') format('truetype'),url('../fonts/weather-icons-lite.eot') format('embedded-opentype')}.wi{display:inline-block;font-family:'weather-icons-lite';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.wi-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.wi-xs{font-size:.75em}.wi-sm{font-size:.875em}.wi-1x{font-size:1em}.wi-2x{font-size:2em}.wi-3x{font-size:3em}.wi-4x{font-size:4em}.wi-5x{font-size:5em}.wi-6x{font-size:6em}.wi-7x{font-size:7em}.wi-8x{font-size:8em}.wi-9x{font-size:9em}.wi-10x{font-size:10em}.wi-fw{text-align:center;width:1.4em}.wi-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.wi-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.wi-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.wi-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0,mirror=1);-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.wi-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2,mirror=1);-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}.wi-darksky-clear-day:before{content:"\f00d"}.wi-darksky-clear-night:before{content:"\f02e"}.wi-darksky-rain:before{content:"\f019"}.wi-darksky-snow:before{content:"\f01b"}.wi-darksky-sleet:before{content:"\f0b5"}.wi-darksky-wind:before{content:"\f050"}.wi-darksky-fog:before{content:"\f014"}.wi-darksky-cloudy:before{content:"\f013"}.wi-darksky-partly-cloudy-day:before{content:"\f002"}.wi-darksky-partly-cloudy-night:before{content:"\f086"}.wi-darksky-hail:before{content:"\f015"}.wi-darksky-thunderstorm:before{content:"\f01e"}.wi-darksky-tornado:before{content:"\f056"}.wi-owm-01d:before{content:"\f00d"}.wi-owm-02d:before{content:"\f00c"}.wi-owm-03d:before{content:"\f002"}.wi-owm-04d:before{content:"\f013"}.wi-owm-09d:before{content:"\f017"}.wi-owm-10d:before{content:"\f019"}.wi-owm-11d:before{content:"\f01e"}.wi-owm-13d:before{content:"\f01b"}.wi-owm-50d:before{content:"\f014"}.wi-owm-01n:before{content:"\f02e"}.wi-owm-02n:before{content:"\f081"}.wi-owm-03n:before{content:"\f07e"}.wi-owm-04n:before{content:"\f086"}.wi-owm-09n:before{content:"\f026"}.wi-owm-10n:before{content:"\f028"}.wi-owm-11n:before{content:"\f02c"}.wi-owm-13n:before{content:"\f02a"}.wi-owm-50n:before{content:"\f04a"}.wi-wu-chanceflurries:before{content:"\f064"}.wi-wu-chancerain:before{content:"\f019"}.wi-wu-chancesleet:before{content:"\f0b5"}.wi-wu-chancesnow:before{content:"\f01b"}.wi-wu-chancetstorms:before{content:"\f01e"}.wi-wu-clear:before{content:"\f00d"}.wi-wu-cloudy:before{content:"\f002"}.wi-wu-flurries:before{content:"\f064"}.wi-wu-fog:before{content:"\f014"}.wi-wu-hazy:before{content:"\f0b6"}.wi-wu-mostlycloudy:before{content:"\f002"}.wi-wu-mostlysunny:before{content:"\f00d"}.wi-wu-partlycloudy:before{content:"\f002"}.wi-wu-partlysunny:before{content:"\f00d"}.wi-wu-rain:before{content:"\f01a"}.wi-wu-sleet:before{content:"\f0b5"}.wi-wu-snow:before{content:"\f01b"}.wi-wu-sunny:before{content:"\f00d"}.wi-wu-tstorms:before{content:"\f01e"}.wi-wu-nt_chanceflurries:before{content:"\f067"}.wi-wu-nt_chancerain:before{content:"\f028"}.wi-wu-nt_chancesleet:before{content:"\f0b4"}.wi-wu-nt_chancesnow:before{content:"\f02a"}.wi-wu-nt_chancetstorms:before{content:"\f02d"}.wi-wu-nt_clear:before{content:"\f02e"}.wi-wu-nt_flurries:before{content:"\f067"}.wi-wu-nt_fog:before{content:"\f04a"}.wi-wu-nt_hazy:before{content:"\f07e"}.wi-wu-nt_mostlycloudy:before{content:"\f081"}.wi-wu-nt_mostlysunny:before{content:"\f02e"}.wi-wu-nt_partlycloudy:before{content:"\f081"}.wi-wu-nt_partlysunny:before{content:"\f086"}.wi-wu-nt_rain:before{content:"\f028"}.wi-wu-nt_sleet:before{content:"\f0b4"}.wi-wu-nt_snow:before{content:"\f02a"}.wi-wu-nt_sunny:before{content:"\f02e"}.wi-wu-nt_tstorms:before{content:"\f02d"}.wi-wu-nt_cloudy:before{content:"\f031"}