node-red-contrib-jwht-map/worldmap/index.html

963 lines
42 KiB
HTML
Raw Normal View History

2016-04-01 18:31:07 +08:00
<!DOCTYPE HTML>
<!--
Copyright 2015, 2016 IBM Corp.
2016-04-01 18:31:07 +08:00
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
2016-06-09 05:07:54 +08:00
<html manifest="worldmap.appcache">
2016-04-01 18:31:07 +08:00
<head>
<title>Node-RED map all the things</title>
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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/font-awesome/css/font-awesome.min.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.Default.css">
<link rel="stylesheet",type="text/css" href="leaflet/leaflet.draw.css">
<link rel="stylesheet",type="text/css" href="leaflet/leaflet.measurecontrol.css">
<link rel="stylesheet",type="text/css" href="leaflet/easy-button.css">
<link rel="stylesheet",type="text/css" href="leaflet/leaflet.fullscreen.css">
2016-04-01 18:31:07 +08:00
<link rel="stylesheet" type="text/css" href="leaflet/leaflet-openweathermap.css"/>
<link rel="shortcut icon" type="image/ico" href="favicon.ico"/>
<script type="text/javascript" src="leaflet/socket.io-1.4.5.js"></script>
2016-04-01 18:31:07 +08:00
<script type="text/javascript" src="leaflet/leaflet.js"></script>
<script type="text/javascript" src="leaflet/Leaflet.vector-markers.min.js"></script>
<script type="text/javascript" src="leaflet/leaflet.boatmarker.js"></script>
<script type="text/javascript" src="leaflet/leaflet.markercluster.js"></script>
2016-06-27 18:55:30 +08:00
<script type="text/javascript" src="leaflet/leaflet.markercluster.freezable-src.js"></script>
2016-04-01 18:31:07 +08:00
<script type="text/javascript" src="leaflet/leaflet.active-layers.min.js"></script>
<script type="text/javascript" src="leaflet/leaflet.select-layers.min.js"></script>
<script type="text/javascript" src="leaflet/leaflet.draw.js"></script>
<script type="text/javascript" src="leaflet/leaflet.measurecontrol.js"></script>
<script type="text/javascript" src="leaflet/easy-button.js"></script>
<script type="text/javascript" src="leaflet/Leaflet.fullscreen.min.js"></script>
2016-04-01 18:31:07 +08:00
<script type="text/javascript" src="leaflet/l.ellipse.min.js"></script>
<script type="text/javascript" src="leaflet/leaflet-heat.js"></script>
<script type="text/javascript" src="leaflet/TileLayer.Grayscale.js"></script>
<script type="text/javascript" src="leaflet/TileLayer.GrayscaleWMS.js"></script>
<script type="text/javascript" src="leaflet/L.Terminator.js"></script>
<script type="text/javascript" src="leaflet/tile.stamen.js"></script>
<script type="text/javascript" src="leaflet/leaflet-openweathermap.js"></script>
<script type="text/javascript" src="leaflet/dialog-polyfill.js"></script>
<link rel="stylesheet" type="text/css" href="leaflet/dialog-polyfill.css"/>
2016-04-01 18:31:07 +08:00
</head>
<body onunload="ws.emit('end');">
2016-04-01 18:31:07 +08:00
<div id="topbar">
<a href="http://nodered.org"><img src="images/node-red.png" width="60px" height="24px"/></a>
<span class="topbar"> Node-RED - map all the things</span>
2016-04-01 18:31:07 +08:00
</div>
<div id="results">
<span id="searchRes"></span>
<span id="bars" onclick='toggleMenu()' onmouseover='toggleMenu()'><i class="fa fa-bars fa-2x fa-inverse"></i></span>
2016-04-01 18:31:07 +08:00
</div>
<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 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' value="10" size="2" onchange='setCluster();'/></td></tr>
<tr><td style="cursor:default"><input type='checkbox' name='panit' onclick='doPanit();'/> Auto Pan Map</td></tr>
<tr><td style="cursor:default"><input type='checkbox' name='lockit' onclick='doLock();'/> Lock Map</td></tr>
<tr><td style="cursor:default"><input type='checkbox' name='heatall' onclick='doHeatAll();'/> Heatmap all layers</td></tr>
<tr><td style="cursor:default"><span id="showHelp" ><i class="fa fa-info fa-lg fa-fw"></i>Help</span></td></tr>
2016-04-01 18:31:07 +08:00
</table></div>
<div id="map"></div>
<div id="foot">&nbsp;&copy; IBM 2015,2016</div>
2016-04-01 18:31:07 +08:00
<!-- <div id="heat"><button type="button" onclick="clearHeat();">Clear Heatmap</button></div> -->
<dialog id="helpWindow">
<h3>Node-RED - Map all the things</h3>
<p><i class="fa fa-search fa-lg fa-fw"></i> Search - You may enter a name or partial name of an object to search for.<br/>
The map will then jump to centre on each of the results.</p>
<p><i class="fa fa-spinner fa-lg fa-fw"></i> Set Max Age - You can set the time after which points
2016-04-01 18:31:07 +08:00
that haven't been updated get removed.</p>
2016-05-17 19:22:04 +08:00
<p><i class="fa fa-search-plus fa-lg fa-fw"></i> Cluster at zoom - by default multiple objects will be clustered at zoom levels less than 12.</p>
2016-04-01 18:31:07 +08:00
<p><i class="fa fa-arrows fa-lg fa-fw"></i> Auto Pan - When selected, the map will
automatically move to centre on each data point as they arrive.</p>
<p><i class="fa fa-lock fa-lg fa-fw"></i> Lock Map - When selected will save the
currently displayed area and basemap.<br/>
Reloading the map in the current browser will return to the same view.<br/>
This can be used to set your initial start position.<br/>
While active it also restricts the "auto pan" to within that area.</p>
<p><i class="fa fa-globe fa-lg fa-fw"></i> Heatmap all layers - When selected
all layers whether hidden or not will contribute to the heatmap.<br/>
The default is that only visible layers add to the heatmap.</p>
<button id="exitHelp">Close</button>
</dialog>
<script language="javascript" type="text/javascript">
2016-05-08 18:07:09 +08:00
var startpos = [51.03, -1.379]; // Start location - somewhere in UK :-)
2016-04-01 18:31:07 +08:00
var startzoom = 10;
var map;
var markers = {};
var polygons = {};
var layers = {};
var overlays = {};
var basemaps = {};
var marks = [];
var marksIndex = 0;
var popid = "";
var menuOpen = false;
2016-06-27 18:55:30 +08:00
var clusterAt = 12;
2016-05-08 18:07:09 +08:00
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 ibmfoot = "&nbsp;&copy; IBM 2015,2016"
var initialposition = false;
2016-04-01 18:31:07 +08:00
2016-05-25 04:34:18 +08:00
// Create the socket
var ws = io();
ws.on('connect', function() {
console.log("CONNECTED");
document.getElementById("foot").innerHTML = "<font color='#494'>"+ibmfoot+"</font>";
ws.emit("worldmap",{action:"connected"});
});
ws.on('disconnect', function() {
console.log("DISCONNECTED");
document.getElementById("foot").innerHTML = "<font color='#900'>"+ibmfoot+"</font>";
setTimeout(function() { ws.connect(); }, 2500);
2016-05-25 04:34:18 +08:00
});
ws.on('error', function() {
console.log("ERROR");
document.getElementById("foot").innerHTML = "<font color='#C00'>"+ibmfoot+"</font>";
setTimeout(function() { ws.connect(); }, 2500);
2016-05-25 04:34:18 +08:00
});
ws.on('worldmapdata', function(data) {
if (data.command) { doCommand(data.command); delete data.command; }
if (data.hasOwnProperty("name")) { setMarker(data); }
2016-05-25 04:34:18 +08:00
else { console.log("SKIP",data); }
});
2016-04-01 18:31:07 +08:00
if ( window.localStorage.hasOwnProperty("lastpos") ) {
initialposition = true;
var sp = JSON.parse(window.localStorage.getItem("lastpos"));
2016-04-01 18:31:07 +08:00
startpos = [ sp.lat, sp.lng ];
}
if ( window.localStorage.hasOwnProperty("lastzoom") ) {
startzoom = window.localStorage.getItem("lastzoom");
}
2016-06-27 18:55:30 +08:00
// if ( window.localStorage.hasOwnProperty("clusterat") ) {
// clusterAt = window.localStorage.getItem("clusterat");
// document.getElementById("setclus").value = clusterAt;
// }
if ( window.localStorage.hasOwnProperty("maxage") ) {
maxage = window.localStorage.getItem("maxage");
document.getElementById("maxage").value = maxage;
2016-04-01 18:31:07 +08:00
}
2016-04-01 18:31:07 +08:00
// Create the Initial Map object.
map = new L.map('map').setView(startpos, startzoom);
// Move some bits around if in an iframe
if (window.self !== window.top) {
console.log("IN an iframe");
(document.getElementById("topbar").style.display="none");
(document.getElementById("map").style.top="0px");
(document.getElementById("results").style.right="50px");
(document.getElementById("results").style.top="10px");
(document.getElementById("results").style.zIndex="1");
(document.getElementById("results").style.height="31px");
(document.getElementById("results").style.paddingTop="6px");
(document.getElementById("bars").style.display="none");
(document.getElementById("menu").style.right="8px");
(document.getElementById("menu").style.borderRadius="6px");
L.easyButton( 'fa-bars fa-lg', function() { toggleMenu(); }, "Toggle menu", "topright").addTo(map);
}
else {
console.log("NOT in an iframe")
window.onbeforeunload = function(e) {
return 'Reloading will delete all the local markers, including any drawing on the "drawing" layer';
};
}
// Add the fullscreen button
L.control.fullscreen().addTo(map);
// map.on('fullscreenchange', function () {
// if (map.isFullscreen()) { console.log('entered fullscreen') }
// else { console.log('exited fullscreen'); }
// });
// Add the locate my position button
L.easyButton( 'fa-crosshairs fa-lg', function() {
map.locate({setView:true, maxZoom:16});
}, "Locate me").addTo(map);
// Add the measure/ruler button
L.Control.measureControl().addTo(map);
2016-04-01 18:31:07 +08:00
// Create the clear heatmap button
var clrHeat = L.easyButton( '<b>Reset Heatmap</b>', function() {
console.log("reset heatmap");
heat.setLatLngs([]);
}, "Clears the current heatmap", "bottomright");
// Handle the dialog for popup help
var dialog = document.querySelector('dialog');
dialogPolyfill.registerDialog(dialog);
2016-04-01 18:31:07 +08:00
document.getElementById('showHelp').onclick = function() {
dialog.show();
};
document.getElementById('exitHelp').onclick = function() {
dialog.close();
};
var panit = false;
function doPanit() {
panit = !panit;
console.log("Panit set :",panit);
}
var heatAll = false;
function doHeatAll() {
heatAll = !heatAll;
console.log("Heatall set :",heatAll);
}
var lockit = false;
var mb = new L.LatLngBounds([[-120,-360],[120,360]]);
function doLock() {
if (lockit) {
lockit = false;
mb = new L.LatLngBounds([[-120,-360],[120,360]]);
}
else {
lockit = true;
mb = map.getBounds();
window.localStorage.setItem("lastpos",JSON.stringify(map.getCenter()));
window.localStorage.setItem("lastzoom", map.getZoom());
window.localStorage.setItem("lastlayer", baselayername);
//window.localStorage.setItem("clusterat", clusterAt);
window.localStorage.setItem("maxage", maxage);
2016-04-01 18:31:07 +08:00
console.log("Saved :",JSON.stringify(map.getCenter()),map.getZoom(),baselayername);
}
map.setMaxBounds(mb);
console.log("Map bounds lock :",lockit);
}
// Remove old markers
function doTidyUp() {
var d = parseInt(Date.now()/1000);
for (var m in markers) {
if (typeof markers[m].ts != "undefined") {
//console.log(m,markers[m].ts,markers[m]);
if (((Number(markers[m].ts) + Number(maxage)) < d) && (markers[m].lay !== "drawing")) {
//if ((Number(markers[m].ts) + Number(maxage)) < d) {
2016-04-01 18:31:07 +08:00
//console.log("STALE :",m);
layers[markers[m].lay].removeLayer(markers[m]);
if (typeof polygons[m] != "undefined") {
layers[markers[m].lay].removeLayer(polygons[m]);
delete polygons[m];
}
delete markers[m];
}
}
}
}
2016-05-08 18:07:09 +08:00
// move the daylight / nighttime boundary (if enabled) every minute
2016-04-01 18:31:07 +08:00
function moveTerminator() { // if terminator line plotted move it every minute
if (layers["_daynight"].getLayers().length > 0) {
layers["_daynight"].clearLayers();
layers["_daynight"].addLayer(L.terminator());
}
}
setInterval( function() {moveTerminator()}, 60000 );
2016-05-08 18:07:09 +08:00
// Call tidyup every {maxage} seconds - default 10 mins
2016-04-01 18:31:07 +08:00
var stale = null;
function setMaxAge() {
maxage = document.getElementById('maxage').value;
if (stale) { clearInterval(stale); }
if (maxage > 0) { stale = setInterval( function() {doTidyUp()}, (maxage*1000) ); }
//console.log("Stale time set :",maxage+"s");
}
setMaxAge();
2016-05-17 19:22:04 +08:00
function setCluster() {
2016-06-27 18:55:30 +08:00
clusterAt = parseInt(document.getElementById('setclus').value) || 0;
console.log("clusterAt set:",clusterAt);
showMapCurrentZoom();
2016-05-17 19:22:04 +08:00
}
setCluster();
2016-04-01 18:31:07 +08:00
// Search for markers with names of ....
function doSearch() {
var value = document.getElementById('search').value;
console.log("Search for :",value);
marks = [];
marksIndex = 0;
for (var key in markers) {
if ( (~(key.toLowerCase()).indexOf(value.toLowerCase())) && (mb.contains(markers[key].getLatLng()))) {
marks.push(markers[key]);
}
}
moveToMarks();
if (lockit) {
document.getElementById('searchRes').innerHTML = "&nbsp;<font color='#ff0'>Found "+marks.length+" results within bounds.</font>";
} else {
document.getElementById('searchRes').innerHTML = "&nbsp;<font color='#ff0'>Found "+marks.length+" results.</font>";
}
}
// Jump to a markers position - centralise it on map
function moveToMarks() {
if (marks.length > marksIndex) {
var m = marks[marksIndex];
map.setView(m.getLatLng(), map.getZoom());
m.openPopup();
marksIndex++;
2016-05-08 18:07:09 +08:00
setTimeout(moveToMarks, 2500);
2016-04-01 18:31:07 +08:00
}
}
function toggleMenu() {
menuOpen = !menuOpen;
if (menuOpen) {
document.getElementById("menu").style.display = 'block';
} else {
document.getElementById("menu").style.display = 'none';
}
}
document.getElementById("menu").style.display = 'none';
//function clearHeat() {
//console.log("reset heatmap");
//heat.setLatLngs([]);
2016-04-01 18:31:07 +08:00
//}
//document.getElementById("heat").style.display = 'none';
var popped = false;
var popmark = null;
map.on('popupopen', function(e) {
popped = true;
popmark = e.popup._source;
popid = e.popup.dname;
});
map.on('popupclose', function(e) {
popped = false;
});
map.on('overlayadd', function(e) {
if (typeof overlays[e.name].bringToFront === "function") {
overlays[e.name].bringToFront();
}
2016-04-01 18:31:07 +08:00
if (e.name == "satellite") {
overlays["satellite"].bringToBack();
}
if (e.name == "heatmap") { // show heatmap button when it's layer is added.
//document.getElementById("heat").style.display = 'block';
clrHeat.addTo(map);
}
if (e.name == "day/night") {
//console.log("add daynight");
layers["_daynight"].addLayer(L.terminator());
}
if (e.name == "drawing") {
map.addControl(drawControl);
overlays["drawing"].bringToBack();
}
//else { console.log("layer add :",e.name); }
ws.emit("worldmap",{action:"addlayer", name:e.name});
2016-04-01 18:31:07 +08:00
});
map.on('overlayremove', function(e) {
if (e.name == "heatmap") { // hide heatmap button when it's layer is removed.
//document.getElementById("heat").style.display = 'none';
clrHeat.removeFrom(map);
}
if (e.name == "day/night") {
//console.log("del daynight");
layers["_daynight"].clearLayers();
}
if (e.name == "drawing") {
map.removeControl(drawControl);
}
//else console.log("layer del :",e.name);
ws.emit("worldmap",{action:"dellayer", name:e.name});
2016-04-01 18:31:07 +08:00
});
map.on('baselayerchange', function(e) {
//console.log("base layer now :",e.name);
baselayername = e.name;
ws.emit("worldmap",{action:"layer", name:e.name});
2016-04-01 18:31:07 +08:00
});
2016-06-27 18:55:30 +08:00
function showMapCurrentZoom() {
console.log("ZOOM:",map.getZoom(),". CLUSTER:",clusterAt);
for (var l in layers) {
if (layers[l].hasOwnProperty("_zoom")) {
if (map.getZoom() >= clusterAt) {
layers[l].disableClustering();
}
else {
layers[l].enableClustering();
}
}
}
2016-04-01 18:31:07 +08:00
setTimeout( function() {
for (var key in markers) {
if (polygons[key]) {
var vis = layers[markers[key].lay].getVisibleParent(markers[key]);
if ((vis) && (vis.hasOwnProperty("lay"))) {
polygons[key].setStyle({opacity:1});
}
else {
polygons[key].setStyle({opacity:0});
}
polygons[key].redraw();
}
}
},750);
2016-06-27 18:55:30 +08:00
}
map.on('zoomend', function() {
showMapCurrentZoom();
2016-04-01 18:31:07 +08:00
});
//map.on('contextmenu', function(e) {
// ws.emit("worldmap",{action:"rightclick", lat:e.latlng.lat.toFixed(5), lon:e.latlng.lng.toFixed(5)});
2016-04-01 18:31:07 +08:00
//});
var rightmenuMap = L.popup().setContent("<input type='text' id='rinput' onkeydown='if (event.keyCode == 13) addThing();' placeholder='name (,icon, layer)'/>");
var rclk;
var addThing = function() {
var thing = document.getElementById('rinput').value;
console.log(thing);
ws.emit("worldmap",{action:"point", lat:rclk.lat.toFixed(5), lon:rclk.lng.toFixed(5), point:thing});
2016-04-01 18:31:07 +08:00
map.closePopup();
var bits = thing.split(",");
var lay = (bits[2] || "drawing").trim();
var icon = (bits[1] || "circle").trim();
var d = {name:bits[0].trim(),layer:lay,icon:icon,lat:rclk.lat,lon:rclk.lng};
setMarker(d);
map.addLayer(layers[lay]);
}
// allow double right click to zoom out
// single right click opens a message window that sends to the socket.
2016-04-01 18:31:07 +08:00
var rclicked = false;
var rtout = null;
map.on('contextmenu', function(e) {
if (rclicked) {
rclicked = false;
clearTimeout(rtout);
map.zoomOut();
}
else {
rclicked = true;
rtout = setTimeout( function() {
rclicked = false;
rclk = e.latlng;
rightmenuMap.setLatLng(e.latlng);
map.openPopup(rightmenuMap);
setTimeout( function() {
document.getElementById('rinput').focus();
}, 200);
}, 300);
}
});
function onLocationFound(e) {
var radius = e.accuracy;
2016-04-01 18:31:07 +08:00
//L.marker(e.latlng).addTo(map).bindPopup("You are within " + radius + " meters from this point").openPopup();
L.circle(e.latlng, radius, {color:"cyan", weight:4, opacity:0.8, fill:false, clickable:false}).addTo(map);
if (e.hasOwnProperty("heading")) {
var lengthAsDegrees = e.speed * 60 / 110540;
var ya = e.latlng.lat + Math.sin((90-e.heading)/180*Math.PI)*lengthAsDegrees*Math.cos(e.latlng.lng/180*Math.PI);
var xa = e.latlng.lng + Math.cos((90-e.heading)/180*Math.PI)*lengthAsDegrees;
var lla = new L.LatLng(ya,xa);
L.polygon([ e.latlng, lla ], {color:"cyan", weight:3, opacity:0.8, clickable:false}).addTo(map);
}
ws.emit("worldmap",{action:"point", lat:e.latlng.lat.toFixed(5), lon:e.latlng.lng.toFixed(5), point:"self", bearing:e.heading, speed:(e.speed*3.6 || undefined)});
2016-04-01 18:31:07 +08:00
}
function onLocationError(e) { console.log(e.message); }
map.on('locationfound', onLocationFound);
map.on('locationerror', onLocationError);
2016-04-01 18:31:07 +08:00
// Add all the base layer maps
// Use this for OSM online maps
var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
//var osmUrl='http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png';
var osmAttrib='Map data © OpenStreetMap contributors';
var osmg = new L.TileLayer.Grayscale(osmUrl, {attribution:osmAttrib});
2016-04-01 18:31:07 +08:00
basemaps["OSM grey"] = osmg;
var osm = new L.TileLayer(osmUrl, {attribution:osmAttrib});
2016-04-01 18:31:07 +08:00
basemaps["OSM"] = osm;
// Extra Leaflet map layers from http://leaflet-extras.github.io/leaflet-providers/preview/
var Esri_WorldStreetMap = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
});
basemaps["Esri"] = Esri_WorldStreetMap;
var Esri_WorldImagery = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
});
basemaps["Esri Satellite"] = Esri_WorldImagery;
2016-04-01 18:31:07 +08:00
var Esri_WorldShadedRelief = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Source: Esri',
maxNativeZoom: 13
2016-04-01 18:31:07 +08:00
});
basemaps["Esri Terrain"] = Esri_WorldShadedRelief;
var Esri_OceanBasemap = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/Ocean_Basemap/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri',
maxNativeZoom: 10
2016-04-01 18:31:07 +08:00
});
basemaps["Esri Ocean"] = Esri_OceanBasemap;
var OpenMapSurfer_Roads = L.tileLayer('http://korona.geog.uni-heidelberg.de/tiles/roads/x={x}&y={y}&z={z}', {
maxZoom: 18,
2016-04-01 18:31:07 +08:00
attribution: 'Imagery from <a href="http://giscience.uni-hd.de/">GIScience Research Group @ University of Heidelberg</a> &mdash; Map data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
});
basemaps["Mapsurfer"] = OpenMapSurfer_Roads;
2016-07-24 18:57:22 +08:00
// var MapQuestOpen_OSM = L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.{ext}', {
// type: 'map',
// ext: 'jpg',
// attribution: 'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a> &mdash; Map data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
// subdomains: '1234',
// maxNativeZoom: 17
// });
//basemaps["MapQuest OSM"] = MapQuestOpen_OSM;
2016-04-01 18:31:07 +08:00
var Esri_NatGeoWorldMap = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC',
maxNativeZoom: 12
2016-04-01 18:31:07 +08:00
});
basemaps["Nat Geo"] = Esri_NatGeoWorldMap;
var NLS_OS_opendata = L.tileLayer('http://geo.nls.uk/maps/opendata/{z}/{x}/{y}.png', {
attribution: '<a href="http://geo.nls.uk/maps/">National Library of Scotland Historic Maps</a>',
bounds: [[49.6, -12], [61.7, 3]],
minZoom: 1,
maxNativeZoom: 16,
2016-04-01 18:31:07 +08:00
subdomains: '0123'
});
basemaps["UK OS Opendata"] = NLS_OS_opendata;
var NLS_OS_1919_1947 = L.tileLayer( 'http://nls-{s}.tileserver.com/nls/{z}/{x}/{y}.jpg', {
attribution: 'Historical Maps Layer, 1919-1947 from the <a href="http://maps.nls.uk/projects/api/">NLS Maps API</a>',
bounds: [[49.6, -12], [61.7, 3]],
minZoom: 1,
maxZoom: 18,
subdomains: '0123'
});
basemaps["UK OS 1919-47"] = NLS_OS_1919_1947;
2016-04-01 18:31:07 +08:00
//var NLS_OS_1900 = L.tileLayer('http://nls-{s}.tileserver.com/NLS_API/{z}/{x}/{y}.jpg', {
//attribution: '<a href="http://geo.nls.uk/maps/">National Library of Scotland Historic Maps</a>',
//bounds: [[49.6, -12], [61.7, 3]],
//minZoom: 1,
//maxZoom: 18,
//subdomains: '0123'
2016-04-01 18:31:07 +08:00
//});
//basemaps["UK OS 1900"] = NLS_OS_1900;
2016-07-24 18:57:22 +08:00
//var CartoPos = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
// attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>'
//});
2016-04-01 18:31:07 +08:00
//basemaps["CartoDB Light"] = CartoPos;
2016-05-08 18:07:09 +08:00
// Terrain map of US only
2016-04-01 18:31:07 +08:00
var usterrainmap = new L.StamenTileLayer('terrain');
basemaps["US terrain"] = usterrainmap;
// Nice watercolour based maps by Stamen Design
var watermap = new L.StamenTileLayer('watercolor');
basemaps["Watercolor"] = watermap;
2016-04-01 18:31:07 +08:00
// Now add the overlays
// Add the day/night overlay
layers["_daynight"] = new L.LayerGroup();
overlays["day/night"] = layers["_daynight"];
// Add the heatmap layer
var heat = L.heatLayer([], {radius:60, gradient:{0.2:'blue', 0.4:'lime', 0.6:'red', 0.8:'yellow', 1:'white'}});
layers["heat"] = new L.LayerGroup().addLayer(heat);
overlays["heatmap"] = layers["heat"];
// Add the drawing layer for fun...
layers["drawing"] = new L.FeatureGroup();
overlays["drawing"] = layers["drawing"];
var drawControl = new L.Control.Draw({
draw: {
polyline: { shapeOptions: { clickable:false } },
marker: false,
circle: false,
//circle: { shapeOptions: { clickable:false } },
rectangle: { shapeOptions: { clickable:true } },
polygon: { shapeOptions: { clickable:true } },
},
edit: {
featureGroup: layers["drawing"],
remove: true,
edit: false
}
});
map.on('draw:created', function (e) {
var type = e.layerType;
var layer = e.layer;
//console.log(type, layer._latlngs);
//console.log(JSON.stringify(layer.toGeoJSON()));
ws.emit("worldmap",{action:"draw",type:type, points:layer._latlngs});
2016-04-01 18:31:07 +08:00
layers["drawing"].addLayer(layer);
});
// Add the roads overlay
overlays["roads"] = L.tileLayer('http://otile{s}.mqcdn.com/tiles/1.0.0/{type}/{z}/{x}/{y}.{ext}', {
type: 'hyb',
ext: 'png',
attribution: 'Tiles Courtesy of <a href="http://www.mapquest.com/">MapQuest</a> &mdash; Map data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
subdomains: '1234',
opacity: 0.9
});
overlays["rain"] = L.tileLayer('http://{s}.tile.openweathermap.org/map/rain/{z}/{x}/{y}.png', {
maxZoom: 18,
2016-04-01 18:31:07 +08:00
attribution: 'Map data &copy; <a href="http://openweathermap.org">OpenWeatherMap</a>',
opacity: 0.5
});
overlays["pressure"] = L.tileLayer('http://{s}.tile.openweathermap.org/map/pressure_cntr/{z}/{x}/{y}.png', {
maxZoom: 18,
2016-04-01 18:31:07 +08:00
attribution: 'Map data &copy; <a href="http://openweathermap.org">OpenWeatherMap</a>',
opacity: 0.5
});
overlays["weather"] = L.OWM.current({interval:30, minZoom:8, appId:"9ae11d4f6f9bed61f32fc061f715cc71"});
// Add the shipping navigation markers
2016-05-08 18:07:09 +08:00
//var OpenSeaMap = L.tileLayer('http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', {
// attribution: 'Map data: &copy; <a href="http://www.openseamap.org">OpenSeaMap</a> contributors'
//});
2016-04-01 18:31:07 +08:00
//overlays["ship nav"] = OpenSeaMap;
if ( window.localStorage.hasOwnProperty("lastlayer") ) {
if ( basemaps[window.localStorage.getItem("lastlayer")] ) {
baselayername = window.localStorage.getItem("lastlayer");
}
}
basemaps[baselayername].addTo(map);
// Add the layers control widget
var layercontrol = L.control.layers(basemaps, overlays).addTo(map);
// Layer control based on select box rather than radio buttons.
//var layercontrol = L.control.selectLayers(basemaps, overlays).addTo(map);
// Delete a marker (and notify websocket)
var delMarker = function(dname) {
//console.log("Deleting",dname);
if (typeof polygons[dname] != "undefined") {
layers[markers[dname].lay].removeLayer(polygons[dname]);
delete polygons[dname];
}
if (typeof markers[dname] != "undefined") {
var d = {name:dname,type:"DELETE",lat:markers[dname]._latlng.lat,lon:markers[dname]._latlng.lng};
ws.emit("worldmap",{action:"delete", name:dname});
2016-04-01 18:31:07 +08:00
layers[markers[dname].lay].removeLayer(markers[dname]);
delete markers[dname];
}
map.closePopup();
}
// the MAIN add something to map function
function setMarker(data) {
//console.log(typeof data, data);
2016-04-01 18:31:07 +08:00
var ll;
var stay = popped;
var lay = data.layer || "not known";
if (typeof layers[lay] == "undefined") { // add layer if if doesn't exist
//layers[lay] = new L.LayerGroup().addTo(map);
2016-06-27 18:55:30 +08:00
layers[lay] = new L.MarkerClusterGroup({
maxClusterRadius:50,
spiderfyDistanceMultiplier:1.8,
disableClusteringAtZoom:clusterAt,
//zoomToBoundsOnClick:false
}).addTo(map);
2016-04-01 18:31:07 +08:00
overlays[lay] = layers[lay];
layercontrol.addOverlay(layers[lay],lay);
}
if (typeof markers[data.name] != "undefined") { layers[lay].removeLayer(markers[data.name]); }
if (typeof polygons[data.name] != "undefined") { layers[lay].removeLayer(polygons[data.name]); }
if (data.deleted) { // remove markers we are told to
delMarker(data.name);
}
else if (data.hasOwnProperty("line") && Array.isArray(data.line)) {
2016-04-01 18:31:07 +08:00
var col = data.iconColor || "#910000";
var polyln = L.polyline(data.line, {stroke:true, weight:3, color:col, opacity:0.8, clickable:false});
polygons[data.name] = polyln;
layers[lay].addLayer(polyln);
}
else if (data.hasOwnProperty("area") && Array.isArray(data.area)) {
var cola = data.iconColor || "#910000";
var polyarea = L.polygon(data.area, {stroke:true, weight:2, color:cola, fillColor:cola, fillOpacity:0.2, clickable:false});
polygons[data.name] = polyarea;
layers[lay].addLayer(polyarea);
2016-04-01 18:31:07 +08:00
}
2016-05-08 18:07:09 +08:00
else if (data.hasOwnProperty("sdlat") && data.hasOwnProperty("sdlon")) {
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, {color:(data.iconColor || "blue"), weight:2} );
ellipse.on('click', function(e) {
var popup = L.popup()
.setLatLng(new L.LatLng((data.lat*1), (data.lon*1)))
.setContent('<p><b>'+data.name+'</b><br/>lat : '+data.lat+'<br/>lon : '+data.lon+'</p>')
.openOn(map);
});
polygons[data.name] = ellipse;
layers[lay].addLayer(ellipse);
}
2016-04-01 18:31:07 +08:00
else {
//console.log("handling",data.name);
if (typeof data.coordinates == "object") { ll = new L.LatLng(data.coordinates[1],data.coordinates[0]); }
else if (data.hasOwnProperty("lat") && data.hasOwnProperty("lon")) { ll = new L.LatLng((data.lat*1), (data.lon*1)); }
else if (data.hasOwnProperty("latitude") && data.hasOwnProperty("longitude")) { ll = new L.LatLng((data.latitude*1), (data.longitude*1)); }
else { console.log("No location:",data); return; }
2016-04-01 18:31:07 +08:00
var words="<b>"+data.name+"</b><br/>";
// Create the icons... handle ship, earthquake as specials
var marker, mymarker;
var icon, q;
2016-04-01 18:31:07 +08:00
if (data.icon === "ship") {
marker = L.boatMarker(ll, {
title: data.name,
color: (data.iconColor || "blue")
});
marker.setHeading(data.bearing);
q = 'http://www.bing.com/images/search?q='+data.icon+'%20%2B"'+encodeURIComponent(data.name)+'"';
2016-04-01 18:31:07 +08:00
words += '<a href=\''+q+'\' target="_thingpic">Pictures</a><br>';
}
else if (data.icon === "plane") {
data.iconColor = data.iconColor || "black";
if (data.hasOwnProperty("squawk")) { data.iconColor = "red"; }
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="310px" height="310px" viewBox="0 0 310 310" >';
2016-04-01 18:31:07 +08:00
icon += '<g><path d="M134.875,19.74c0.04-22.771,34.363-22.771,34.34,0.642v95.563L303,196.354v35.306l-133.144-43.821v71.424l30.813,24.072v27.923l-47.501-14.764l-47.501,14.764v-27.923l30.491-24.072v-71.424L3,231.66v-35.306l131.875-80.409V19.74z" fill="'+data.iconColor+'"/></g></svg>';
var svgplane = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
2016-04-01 18:31:07 +08:00
className:"planeicon",
iconAnchor: [15, 15],
html:'<img src="'+svgplane+'" style="width:31px; height:30px; -webkit-transform:rotate('+data.bearing+'deg); -moz-transform:rotate('+data.bearing+'deg);" />',
});
marker = L.marker(ll, {title: data.name, icon: myMarker});
q = 'http://www.bing.com/images/search?q='+data.icon+'%20'+encodeURIComponent(data.name);
2016-04-01 18:31:07 +08:00
words += '<a href=\''+q+'\' target="_thingpic">Pictures</a><br>';
}
2016-04-18 23:57:31 +08:00
else if (data.icon === "car") {
data.iconColor = data.iconColor || "black";
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="47px" height="47px" viewBox="0 0 47.032 47.032">';
2016-04-18 23:57:31 +08:00
icon += '<g><path d="M29.395,0H17.636c-3.117,0-5.643,3.467-5.643,6.584v34.804c0,3.116,2.526,5.644,5.643,5.644h11.759 c3.116,0,5.644-2.527,5.644-5.644V6.584C35.037,3.467,32.511,0,29.395,0z M34.05,14.188v11.665l-2.729,0.351v-4.806L34.05,14.188z M32.618,10.773c-1.016,3.9-2.219,8.51-2.219,8.51H16.631l-2.222-8.51C14.41,10.773,23.293,7.755,32.618,10.773z M15.741,21.713 v4.492l-2.73-0.349V14.502L15.741,21.713z M13.011,37.938V27.579l2.73,0.343v8.196L13.011,37.938z M14.568,40.882l2.218-3.336 h13.771l2.219,3.336H14.568z M31.321,35.805v-7.872l2.729-0.355v10.048L31.321,35.805z" fill="'+data.iconColor+'"/></g></svg>';
var svgcar = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
2016-04-18 23:57:31 +08:00
className:"caricon",
iconAnchor: [15, 15],
html:'<img src="'+svgcar+'" style="width:31px; height:30px; -webkit-transform:rotate('+data.bearing+'deg); -moz-transform:rotate('+data.bearing+'deg);" />',
});
marker = L.marker(ll, {title: data.name, icon: myMarker});
}
2016-06-03 16:11:48 +08:00
else if (data.icon === "locate") {
data.iconColor = data.iconColor || "cyan";
icon = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="468px" height="468px" viewBox="0 0 468 468">';
icon += '<polygon points="32 32 104 32 104 0 0 0 0 104 32 104" fill="'+data.iconColor+'"/>';
icon += '<polygon points="468 0 364 0 364 32 436 32 436 104 468 104" fill="'+data.iconColor+'"/>';
icon += '<polygon points="0 468 104 468 104 436 32 436 32 364 0 364" fill="'+data.iconColor+'"/>';
icon += '<polygon points="436 436 364 436 364 468 468 468 468 364 436 364" fill="'+data.iconColor+'"/>';
//icon += '<circle cx="234" cy="234" r="22" fill="'+data.iconColor+'"/>';
icon += '</svg>';
var svglocate = "data:image/svg+xml;base64," + btoa(icon);
myMarker = L.divIcon({
className:"locateicon",
iconAnchor: [16, 16],
html:'<img src="'+svglocate+'" style="width:32px; height:32px;"/>',
});
marker = L.marker(ll, {title:data.name, icon:myMarker});
}
2016-04-01 18:31:07 +08:00
else if (data.icon === "friend") {
marker = L.marker(ll, { icon: L.divIcon({ className: 'circle f', iconSize: [20, 12] }), title: data.name });
}
else if (data.icon === "hostile") {
marker = L.marker(ll, { icon: L.divIcon({ className: 'circle h', iconSize: [16, 16] }), title: data.name });
}
else if (data.icon === "neutral") {
marker = L.marker(ll, { icon: L.divIcon({ className: 'circle n', iconSize: [16, 16] }), title: data.name });
}
else if (data.icon === "unknown") {
marker = L.marker(ll, { icon: L.divIcon({ className: 'circle', iconSize: [16, 16] }), title: data.name });
}
else if (data.icon === "danger") {
console.log("danger will robinson");
marker = L.marker(ll, { icon: L.divIcon({ className: 'up-triangle' }), title: data.name });
}
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 });
}
else {
myMarker = L.VectorMarkers.icon({
2016-04-01 18:31:07 +08:00
icon: data.icon || "circle",
markerColor: (data.iconColor || "#910000"),
prefix: 'fa',
iconColor: 'white'
});
marker = L.marker(ll, {title: data.name, icon: myMarker});
}
// remove icon from list of properties, then add all others to popup
if (data.hasOwnProperty("icon")) { delete data.icon; }
if (data.hasOwnProperty("iconColor")) { delete data.iconColor; }
for (var i in data) {
if (i != "name") {
if (typeof data[i] === "object") {
words += i +" : "+JSON.stringify(data[i])+"<br/>";
} else {
words += i +" : "+data[i]+"<br/>";
}
}
}
if (data.hasOwnProperty("photourl")) {
2016-06-09 05:07:54 +08:00
words += "<img src=\"" + data.photourl + "\" style=\"width:100%; margin-top:10px;\">";
2016-04-01 18:31:07 +08:00
}
marker.bindPopup(words);
marker._popup.dname = data.name;
marker.ts = parseInt(Date.now()/1000); // save time we got this marker
marker.lay = lay; // and the layer it is on
var rightmenuMarker = L.popup().setContent("<b>"+data.name+"</b><br/><button onclick='delMarker(\""+data.name+"\");'>Delete</button>");
marker.on('contextmenu', function(e) {
rightmenuMarker.setLatLng(e.latlng);
map.openPopup(rightmenuMarker);
});
if (heatAll || map.hasLayer(layers[lay])) { heat.addLatLng(ll); }
markers[data.name] = marker;
layers[lay].addLayer(marker);
popped = stay;
if (popped) {
if (popid == data.name) { marker.openPopup(); }
else { popmark.openPopup(); }
}
if ((data.hdg != null) && (data.bearing == null)) { data.bearing = data.hdg; delete data.hdg; }
if (data.bearing != null) { // if there is a heading
if (data.speed != null) { data.length = data.speed * 50; } // and a speed
if (data.length != null) {
if (polygons[data.name] != null) { map.removeLayer(polygons[data.name]); }
var x = ll.lng; // X coordinate
var y = ll.lat * 1; // Y coordinate
var ll1 = ll;
2016-04-01 18:31:07 +08:00
var angle = data.bearing * 1;
var lengthAsDegrees = data.length / 110540; // metres in a degree..ish
var polygon = null;
if (data.accuracy != null) {
data.accuracy = Number(data.accuracy);
var y2 = y + Math.sin((90-angle+data.accuracy)/180*Math.PI)*lengthAsDegrees*Math.cos(y/180*Math.PI);
var x2 = x + Math.cos((90-angle+data.accuracy)/180*Math.PI)*lengthAsDegrees;
var ll2 = new L.LatLng(y2,x2);
var y3 = y + Math.sin((90-angle-data.accuracy)/180*Math.PI)*lengthAsDegrees*Math.cos(y/180*Math.PI);
var x3 = x + Math.cos((90-angle-data.accuracy)/180*Math.PI)*lengthAsDegrees;
var ll3 = new L.LatLng(y3,x3);
polygon = L.polygon([ ll1, ll2, ll3 ], {weight:2, color:'#f30', fillOpacity:0.06, clickable:false});
2016-04-01 18:31:07 +08:00
} else {
var ya = y + Math.sin((90-angle)/180*Math.PI)*lengthAsDegrees*Math.cos(y/180*Math.PI);
var xa = x + Math.cos((90-angle)/180*Math.PI)*lengthAsDegrees;
var lla = new L.LatLng(ya,xa);
polygon = L.polygon([ ll1, lla ], {weight:2, color:'#f30', clickable:false});
2016-04-01 18:31:07 +08:00
}
if (typeof layers[lay].getVisibleParent === 'function') {
var vis = layers[lay].getVisibleParent(marker);
if ((polygon !== null) && (vis !== null) && (!vis.hasOwnProperty("lay"))) {
polygon.setStyle({opacity:0});
}
polygons[data.name] = polygon;
layers[lay].addLayer(polygon);
}
}
}
if (panit) {
map.setView(ll,map.getZoom());
}
}
}
function doCommand(cmd) {
//console.log("COMMAND",cmd);
// ignore server side initial command if client position already saved.
if (cmd.hasOwnProperty("init") && initialposition) {
return;
}
var existsalready = false;
// Add a new base map layer
2016-04-01 18:31:07 +08:00
if (cmd.map && cmd.map.hasOwnProperty("name") && cmd.map.hasOwnProperty("url") && cmd.map.hasOwnProperty("opt")) {
console.log("New map:",cmd.map.name);
if (basemaps.hasOwnProperty(cmd.map.name)) { existsalready = true; }
2016-04-01 18:31:07 +08:00
basemaps[cmd.map.name] = L.tileLayer(cmd.map.url, cmd.map.opt);
if (!existsalready) {
layercontrol.addBaseLayer(basemaps[cmd.map.name],cmd.map.name);
}
}
// Add a new overlay layer
if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("url") && cmd.map.hasOwnProperty("opt")) {
console.log("New overlay:",cmd.map.overlay);
if (overlays.hasOwnProperty(cmd.map.overlay)) { existsalready = true; }
overlays[cmd.map.overlay] = L.tileLayer(cmd.map.url, cmd.map.opt);
if (!existsalready) {
layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay);
}
overlays[cmd.map.overlay].addTo(map);
2016-04-01 18:31:07 +08:00
}
// Swap a base layer
2016-04-01 18:31:07 +08:00
if (cmd.layer && basemaps.hasOwnProperty(cmd.layer)) {
map.removeLayer(basemaps[baselayername]);
baselayername = cmd.layer;
basemaps[baselayername].addTo(map);
}
// Add an overlay
if (cmd.layer && overlays.hasOwnProperty(cmd.layer)) {
overlays[cmd.layer].addTo(map);
2016-04-01 18:31:07 +08:00
}
var clat = map.getCenter().lat;
var clon = map.getCenter().lng;
var czoom = map.getZoom();
if (cmd.hasOwnProperty("lat")) { clat = cmd.lat; }
if (cmd.hasOwnProperty("lon")) { clon = cmd.lon; }
if (cmd.hasOwnProperty("zoom")) { czoom = cmd.zoom; }
if (cmd.hasOwnProperty("cluster")) {
2016-06-27 18:55:30 +08:00
clusterAt = cmd.cluster;
// document.getElementById("setclus").value = cmd.cluster;
// setCluster();
}
if (cmd.hasOwnProperty("maxage")) {
document.getElementById("maxage").value = cmd.maxage;
setMaxAge();
}
2016-04-01 18:31:07 +08:00
map.setView([clat,clon],czoom);
}
</script>
</body>
</html>