From f14c7fb0818104f622b4b381e0a71a53575081b2 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Wed, 19 Sep 2018 20:50:06 +0100 Subject: [PATCH] bump to 1.4.2 --- CHANGELOG.md | 4 +- README.md | 7 +- package-lock.json | 2 +- package.json | 2 +- worldmap.js | 2 - worldmap/index.html | 81 ++++-- worldmap/leaflet/nvg.js | 530 +++++++++++++++++++++++++++++++++++++ worldmap/worldmap.appcache | 3 +- 8 files changed, 602 insertions(+), 29 deletions(-) create mode 100644 worldmap/leaflet/nvg.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 707c1c2..9cde9ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ### Change Log for Node-RED Worldmap + - v1.4.2 - add NVG layer capability + - v1.4.1 - let `msg.payload.popup` set the popup contents. - v1.4.0 - only send to specific _ sessionid if specified. - v1.3.7 - rescale NATO symbols (less variation, not so small) - v1.3.6 - setting `msg.payload.draggable = true` will allow a marker to be moved and create a move event on the input node. @@ -7,7 +9,7 @@ - v1.3.4 - Add ISS icon - v1.3.3 - Bugfix for inline satellite icon - v1.3.2 - Bugfix for inline svg icons - - v1.3.1 - Allow `msg.payload.popup = true` to auto open the info popup. + - v1.3.1 - Allow `msg.payload.popped = true` to auto open the info popup. - v1.3.0 - Add initial 3D page (worldmap/index3d.html), Add ability to add KML, GPX and TOPOJSON overlay layers and optional zoom to fit. Change all http: links to https: - v1.2.4 - Let weblink also specify target page. eg `msg.payload.weblink = {name:"BBC News", url:"news.bbc.co.uk", target:"_new"}` - v1.2.3 - Add higher maxZoom values for some layers diff --git a/README.md b/README.md index 112cbb6..f3fa30c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ map web page for plotting "things" on. ### Updates +- v1.4.2 - add NVG layer capability +- v1.4.1 - let `msg.payload.popup` set the popup contents. - v1.4.0 - only send to specific _ sessionid if specified. - v1.3.7 - rescale NATO symbols (less variation, not so small) - v1.3.6 - setting `msg.payload.draggable = true` will allow a marker to be moved and create a move event on the input node. @@ -16,7 +18,7 @@ map web page for plotting "things" on. - v1.3.4 - Add ISS icon - v1.3.3 - Bugfix for inline satellite icon - v1.3.2 - Bugfix for inline svg icons -- v1.3.1 - Allow `msg.payload.popup = true` to auto open the info popup. +- v1.3.1 - Allow `msg.payload.popped = true` to auto open the info popup. - v1.3.0 - Add initial 3D page (worldmap/index3d.html), Add ability to add KML, GPX and TOPOJSON overlay layers and optional zoom to fit. see [CHANGELOG](https://github.com/dceejay/RedMap/blob/master/CHANGELOG.md) for full list. @@ -59,7 +61,8 @@ Optional properties include - **weblink** : adds a link to an external page for more information. Either set a url as a *string*, or an *object* like `{name:"BBC News", url:"news.bbc.co.uk", target:"_new"}` - **addtoheatmap** : set to false to exclude point from contributing to heatmap layer. (default true) - **intensity** : set to a value of 0.1 - 1.0 to set the intensity of the point on heatmap layer. (default 1.0) - - **popup** : set to true to automatically open the popup info box. + - **popped** : set to true to automatically open the popup info box. + - **popup** : html to fill the popup if you don't want the automatic default of the properties list. Any other `msg.payload` properties will be added to the icon popup text box. diff --git a/package-lock.json b/package-lock.json index c7faa3e..9969ed5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "node-red-contrib-web-worldmap", - "version": "1.3.6", + "version": "1.4.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0ca55c7..08006da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red-contrib-web-worldmap", - "version": "1.4.0", + "version": "1.4.2", "description": "A Node-RED node to provide a web page of a world map for plotting things on.", "dependencies": { "cgi": "0.3.1", diff --git a/worldmap.js b/worldmap.js index c7a5c00..064217e 100644 --- a/worldmap.js +++ b/worldmap.js @@ -72,14 +72,12 @@ module.exports = function(RED) { node.on('input', function(msg) { if (msg.hasOwnProperty("_sessionid")) { if (clients.hasOwnProperty(msg._sessionid)) { - console.log("DING",msg._sessionid); clients[msg._sessionid].write(JSON.stringify(msg.payload)); } } else { for (var c in clients) { if (clients.hasOwnProperty(c)) { - console.log("DONG",c); clients[c].write(JSON.stringify(msg.payload)); } } diff --git a/worldmap/index.html b/worldmap/index.html index 7f1ed3d..66bfd1e 100644 --- a/worldmap/index.html +++ b/worldmap/index.html @@ -47,6 +47,7 @@ + @@ -129,20 +130,20 @@ var layercontrol; var ws; var iconSz = { - "Team/Crew": 5, - "Squad": 10, - "Section": 15, - "Platoon/detachment": 20, - "Company/battery/troop": 25, + "Team/Crew": 17.5, + "Squad": 20, + "Section": 22.5, + "Platoon/detachment": 25, + "Company/battery/troop": 27.5, "Battalion/squadron": 30, - "Regiment/group": 35, - "Brigade": 40, - "Division": 45, - "Corps/MEF": 50, - "Army": 55, - "Army Group/front": 60, - "Region/Theater": 65, - "Command": 70 + "Regiment/group": 32.5, + "Brigade": 35, + "Division": 37.5, + "Corps/MEF": 40, + "Army": 45, + "Army Group/front": 50, + "Region/Theater": 50, + "Command": 50 }; // Create the socket @@ -1018,12 +1019,12 @@ function setMarker(data) { // Now that we have a symbol we can ask for the echelon and set the symbol size var opts = data.options || {}; opts.size = opts.size || iconSz[myMarker.getProperties().echelon] || 30; - opts.size = (opts.size - 30) / 2 + 30; opts.size = opts.size * (opts.scale || 1); myMarker = myMarker.setOptions(opts); var myicon = L.icon({ - iconUrl: myMarker.toDataURL(), - iconAnchor: [myMarker.getAnchor().x, myMarker.getAnchor().y], + iconUrl: myMarker.toDataURL(), + iconAnchor: [myMarker.getAnchor().x, myMarker.getAnchor().y], + className: "natoicon", }); marker = L.marker(ll, { title:data.name, icon:myicon, draggable:drag }); } @@ -1097,10 +1098,10 @@ function setMarker(data) { } popped = stay; - if (data.hasOwnProperty("popup") && (data.popup == true) ) { + if (data.hasOwnProperty("popped") && (data.popped == true) ) { popped = true; popid = data.name; - delete data.popup; + delete data.popped; } // Add any remaining properties to the info box @@ -1113,6 +1114,7 @@ function setMarker(data) { } } } + words = data.popup || words; marker.bindPopup(words, {keepInView:true, minWidth:348}); marker._popup.dname = data.name; marker.lay = lay; // and the layer it is on @@ -1176,7 +1178,7 @@ function setMarker(data) { } function doCommand(cmd) { - //console.log("COMMAND",cmd); + // console.log("COMMAND",cmd); // ignore server side initial command if client position already saved. // if (cmd.hasOwnProperty("init") && initialposition) { // return; @@ -1238,8 +1240,6 @@ function doCommand(cmd) { existsalready = true; } var opt = cmd.map.opt || {style:function(feature) {return {color:feature.properties.color||feature.properties.roofColor}}}; - //console.log("OPT",opt); - opt.onEachFeature = function (f, l) { l.bindPopup('
'+JSON.stringify(f.properties,null,' ').replace(/[\{\}"]/g,'')+'
'); } @@ -1250,6 +1250,45 @@ function doCommand(cmd) { map.addLayer(overlays[cmd.map.overlay]); if (cmd.map.hasOwnProperty("fit")) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); } } + // Add a new NVG XML overlay layer + if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("nvg") ) { + if (overlays.hasOwnProperty(cmd.map.overlay)) { + map.removeLayer(overlays[cmd.map.overlay]); + existsalready = true; + } + var parser = new NVG(cmd.map.nvg); + var geoj = parser.toGeoJSON(); + + overlays[cmd.map.overlay] = L.geoJson(geoj,{ + style: function(feature) { + var st = { stroke:true, color:"black", weight:2, fill:true }; + if (feature.hasOwnProperty("properties") && feature.properties.hasOwnProperty('style')) { + if (feature.properties.style.hasOwnProperty('stroke')) { + st.color = feature.properties.style.stroke; + } + if (feature.properties.style.hasOwnProperty('stroke-width')) { + st.weight = feature.properties.style["stroke-width"]; + } + if (feature.properties.style.hasOwnProperty('stroke-opacity')) { + st.opacity = feature.properties.style["stroke-opacity"]; + } + if (feature.properties.style.hasOwnProperty('fill')) { + if (feature.properties.style.fill == "none") { st.fill = false; } + else { st.fillColor = feature.properties.style.fill; } + } + if (feature.properties.style.hasOwnProperty('fill-opacity')) { + st.fillOpacity = feature.properties.style["fill-opacity"]; + } + } + return st; + } + }); + if (!existsalready) { + layercontrol.addOverlay(overlays[cmd.map.overlay],cmd.map.overlay); + } + map.addLayer(overlays[cmd.map.overlay]); + if (cmd.map.hasOwnProperty("fit")) { map.fitBounds(overlays[cmd.map.overlay].getBounds()); } + } // Add a new KML overlay layer if (cmd.map && cmd.map.hasOwnProperty("overlay") && cmd.map.hasOwnProperty("kml") ) { if (overlays.hasOwnProperty(cmd.map.overlay)) { diff --git a/worldmap/leaflet/nvg.js b/worldmap/leaflet/nvg.js new file mode 100644 index 0000000..8efa52a --- /dev/null +++ b/worldmap/leaflet/nvg.js @@ -0,0 +1,530 @@ +'use strict'; + +var NVG = class { + constructor(input) { + this.document = 'nvg'; + this.items = []; + this.version = '2.0.2' + if (Array.isArray(input)){ + this.items = input; + } + if (typeof input == 'object' && !Array.isArray(input)){ + this.items.push(input); + } + if (typeof input == 'string'){ + //do stuff with input object + try { + input = JSON.parse(input); + for (var key in input){ + this[key] = input[key]; + } + }catch (e) { + //So parse as JSON failed, try to parse it as xml + this.parseXML(input); + } + } + } + getItem(uri){ //returns item from uri + if(typeof uri == 'undefined')return this.items; + + function getItemByURI(uri, items){ + for (var i = 0; i< items.length; i++){ + if(items[i].uri == uri) return items[i]; + if(items[i].hasOwnProperty('items')){ + var result = getItemByURI(uri, items[i].items); + if(result) return result; + } + } + return false; + } + return getItemByURI(uri, this.items); + } + getItems(){ //returns all items + return this.items; + } + parseXML(xml){ + //parse XML string to JSON + function tagAttributes(nodes, current){ + for (var i = 0; i < nodes.length; i++){ + var node = nodes[i]; + var nodeName = node.nodeName.split(':'); + if(nodeName[0] == 'dc' || nodeName[0] == 'dcterms'){ + nodeName = nodeName[0]; + }else{ + nodeName = nodeName[1]; + } + if(node.nodeType == 1 && nodeName){ + nodeName = nodeName.toLowerCase(); + switch (nodeName) { + case 'begin': + current[nodeName] = node.textContent; + break; + case 'end': + current[nodeName] = node.textContent; + break; + case 'dc': + case 'dcterms': + current[node.nodeName] = node.textContent; + break; + case 'content': + current[nodeName] = node.textContent; + break; + case 'exclude': + if (!current.hasOwnProperty(nodeName)){ + current.exclusion = []; + } + tagAttributes(node.childNodes, current.exclusion); + break; + case 'extendeddata': + if (!current.hasOwnProperty(nodeName)){ + current[nodeName] = {}; + current[nodeName].simpledata = []; + } + nodeAttibutes(node, current[nodeName]); + parseSubNodes(node.childNodes, current[nodeName]); + break; + case 'extension': + console.log('TODO tagAttributes: ' + nodeName); + // TODO How to handle extended data + current[nodeName] = [];//this is for node + break; + case 'metadata': + current[nodeName] = {}; + tagAttributes(node.childNodes, current[nodeName]); + break; + case 'textinfo': + current[nodeName] = node.textContent; + break; + case 'timespan': + current[nodeName] = {}; + tagAttributes(node.childNodes, current[nodeName]); + break; + case 'timestamp': + current[nodeName] = node.textContent; + break; + case 'simplefield': + if (!current.hasOwnProperty(nodeName)){ + current[nodeName] = []; + } + var field = {}; + nodeAttibutes(node, field); + current[nodeName].push(field); + break; + case 'arcband-ring': + case 'circular-ring': + case 'elliptic-ring': + case 'linear-ring': + case 'rect-ring': + var exclude = {}; + exclude.ring = nodeName.replace('-',''); + nodeAttibutes(node, exclude); + current.push(exclude); + break; + default: + //Debug logging, remove later + if(['arc','arcband','arrow','circle','composite','content-item','corridor','ellipse','g','multipoint','orbit','point','polygon','polyline','rect','text'].lastIndexOf(nodeName) == -1){ + console.log('TODO tagAttributes default: ' + nodeName); + } + } + } + } + } + function nodeAttibutes(node, current){ + Array.prototype.slice.call(node.attributes).forEach(function(attr) { + if (attr.name == 'modifiers' || attr.name == 'style') { + current[attr.name] = {}; + var attr_list = attr.value.trim().split(';'); + for (var j = 0; j < attr_list.length; j++){ + if(attr_list[j]){ + var s = attr_list[j].split(':'); + if(s[0] && s[1])current[attr.name][s[0].trim()] = isNaN(Number(s[1].trim()))?s[1].trim():Number(s[1].trim()); + } + } + return; + } + if (attr.name == 'points') { + current[attr.name] = []; + var attr_list = attr.value.trim().split(' '); + for (var j = 0; j < attr_list.length; j++){ + if(attr_list[j]){ + var s = attr_list[j].split(','); + if(s[0] && s[1])current[attr.name].push([Number(s[0]),Number(s[1])]); + } + } + return; + } + current[attr.name] = isNaN(Number(attr.value))?attr.value:Number(attr.value); + }); + } + function parseSubNodes(nodes, current){ + for (var i = 0; i < nodes.length; i++){ + var node = nodes[i]; + if(node.nodeType == 1){ + var nodeName = node.nodeName.split(':')[1] || node.nodeName; + nodeName = nodeName.toLowerCase(); + var item = {}; + if (['extendeddata','extension','metadata','schema','section','simpledata','simplefield'].lastIndexOf(nodeName) != -1){ + + switch (nodeName) { + case 'extendeddata': + current[nodeName] = item; + nodeAttibutes(node, item); + tagAttributes(node.childNodes, item); + break; + case 'extension': + console.log('TODO parsesubnodes: ' + nodeName) + // TODO How to handle extended data + current[nodeName] = [];//this is for root level + break; + case 'metadata': + console.log('TODO parsesubnodes: ' + nodeName) + // TODO How to handle metadata data + current[nodeName] = item; + break; + case 'schema': + if (!current.hasOwnProperty(nodeName)){ + current[nodeName] = []; + } + current[nodeName].push(item); + nodeAttibutes(node, item); + tagAttributes(node.childNodes, item); + break; + case 'section': + if (!current.hasOwnProperty('simpledatasection')){ + current.simpledatasection = []; + } + current.simpledatasection.push(item); + nodeAttibutes(node, item); + item.simpledata = []; + parseSubNodes(node.childNodes, item); + break; + case 'simpledata': + nodeAttibutes(node, item); + item.value = node.textContent; + current.simpledata.push(item); + tagAttributes(node.childNodes, item); + break; + case 'simplefield': + current[nodeName] = item; + nodeAttibutes(node, item); + tagAttributes(node.childNodes, item); + break; + default: + console.log('TODO parsesubnodes default: ' + nodeName) + } + }else{ //This is all drawables + nodeAttibutes(node, item); + item.drawable = nodeName; + + if(node.childNodes.length){ + tagAttributes(node.childNodes, item); + } + if(item.drawable == 'g' || item.drawable == 'composite'){ + item.items = []; + parseSubNodes(node.childNodes, item); + } + if(item.drawable == 'a'){ //This is for handling the old A element + parseSubNodes(node.childNodes, current); + }else{ // otherwise just add featuers + current.items.push(item); + } + } + } + } + } + + var xml = (new DOMParser()).parseFromString(xml , "text/xml"); + if(xml.firstChild.nodeName == 'nvg' || xml.firstChild.nodeName.split(':')[1] == 'nvg'){//check that we actually are parsing NVG but ignore namespace + this.version = xml.firstChild.getAttribute('version'); + this.items = []; + var nodes = xml.firstChild.childNodes; + parseSubNodes(nodes, this); + } + } + toGeoJSON(){ + function bearing(p1,p2){ + var l1 = p1[0] * (Math.PI/180); + var l2 = p2[0] * (Math.PI/180); + var f1 = p1[1] * (Math.PI/180); + var f2 = p2[1] * (Math.PI/180); + var y = Math.sin(l2-l1)*Math.cos(f2); + var x = Math.cos(f1)*Math.sin(f2)-Math.sin(f1)*Math.cos(f2)*Math.cos(l2-l1); + return Math.atan2(y,x)/(Math.PI/180); + } + function distBearing(point, dist, bearing){ + var angularDist = dist/6371e3; + var bearing = bearing * (Math.PI/180); + var lng = point[0] * (Math.PI/180); + var lat = point[1] * (Math.PI/180); + var lat2 = Math.asin(Math.sin(lat)*Math.cos(angularDist)+Math.cos(lat)*Math.sin(angularDist)*Math.cos(bearing)); + var lng2 = (lng+Math.atan2(Math.sin(bearing)*Math.sin(angularDist)*Math.cos(lat),Math.cos(angularDist)-Math.sin(lat)*Math.sin(lat2))); + lat2 = lat2/(Math.PI/180); + lng2 = ((lng2/(Math.PI/180))+540)%360-180; + return [lng2,lat2]; + } + function exclusions(exclusion){ + var exclude = []; + switch (exclusion.ring) { + case 'arcbandring': + var startangle = exclusion.startangle; + var endangle = exclusion.endangle; + if(startangle > endangle) endangle += 360; + for (var j = startangle; j <= endangle; j+=2){ + exclude.push(distBearing([exclusion.cx,exclusion.cy], exclusion.minr, j)); + } + for (var j = endangle; j >= startangle; j-=2){ + exclude.push(distBearing([exclusion.cx,exclusion.cy], exclusion.maxr, j)); + } + exclude.push(distBearing([exclusion.cx,exclusion.cy], exclusion.minr, startangle)); + break; + case 'ellipticalring': + for (var j = 360; j >= 0; j-=2){ + var radius = exclusion.ry * exclusion.rx / Math.sqrt(Math.pow(exclusion.rx * Math.cos(j * (Math.PI/180)),2) + Math.pow(exclusion.ry * Math.sin(j * (Math.PI/180)),2)); + exclude.push(distBearing([exclusion.cx,exclusion.cy], radius, j-(exclusion.rotation||0))); + } + break; + case 'linearring': + exclude = exclusion.points; + exclude.push(exclusion.points[0]); + break; + case 'rectangularring': + var diagonalRadius = Math.sqrt(Math.pow(exclusion.rx,2)+Math.pow(exclusion.rx,2)); + var angle; + angle = ((Math.PI/2)-Math.atan2(exclusion.ry, exclusion.rx)) / (Math.PI/180); + exclude.push(distBearing([exclusion.cx,exclusion.cy], diagonalRadius, exclusion.rotation?angle-exclusion.rotation:angle)); + angle = ((Math.PI/2)-Math.atan2(-exclusion.ry, exclusion.rx)) / (Math.PI/180); + exclude.push(distBearing([exclusion.cx,exclusion.cy], diagonalRadius, exclusion.rotation?angle-exclusion.rotation:angle)); + angle = ((Math.PI/2)-Math.atan2(-exclusion.ry, -exclusion.rx)) / (Math.PI/180); + exclude.push(distBearing([exclusion.cx,exclusion.cy], diagonalRadius, exclusion.rotation?angle-exclusion.rotation:angle)); + angle = ((Math.PI/2)-Math.atan2(exclusion.ry, -exclusion.rx)) / (Math.PI/180); + exclude.push(distBearing([exclusion.cx,exclusion.cy], diagonalRadius, exclusion.rotation?angle-exclusion.rotation:angle)); + angle = ((Math.PI/2)-Math.atan2(exclusion.ry, exclusion.rx)) / (Math.PI/180); + exclude.push(distBearing([exclusion.cx,exclusion.cy], diagonalRadius, exclusion.rotation?angle-exclusion.rotation:angle)); + break; + default: + console.log('TODO parse item default: ' + exclusion.ring) + } + return exclude; + } + function items2features(items, geometrycollection){ + var features = []; + for (var i = 0; i < items.length; i++){ + var item = items[i]; + var feature = { "type": "Feature", "properties" : {}}; + for (var key in item){ + if(key == 'uri'){ + feature.id = item.uri; + }else{ + feature.properties[key] = item[key]; + } + } + switch (item.drawable) { + case 'arc': + feature.geometry = {"type": "LineString"}; + feature.geometry.coordinates = []; + var startangle = item.startangle; + var endangle = item.endangle; + if(startangle > endangle) endangle += 360; + for (var j = startangle; j <= endangle; j+=2){ + var radius = item.ry * item.rx / Math.sqrt(Math.pow(item.rx * Math.cos(j * (Math.PI/180)),2) + Math.pow(item.ry * Math.sin(j * (Math.PI/180)),2)); + feature.geometry.coordinates.push(distBearing([item.cx,item.cy], radius, item.rotation?j-item.rotation:j)); + } + break; + case 'arcband': + feature.geometry = {"type": "Polygon"}; + feature.geometry.coordinates = [[]]; + var startangle = item.startangle; + var endangle = item.endangle; + if(startangle > endangle) endangle += 360; + for (var j = startangle; j <= endangle; j+=2){ + feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], item.minr, j)); + } + for (var j = endangle; j >= startangle; j-=2){ + feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], item.maxr, j)); + } + feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], item.minr, startangle)); + break; + case 'arrow': + var direction; + feature.geometry = {"type": "LineString"}; + feature.geometry.coordinates = []; + direction = (bearing(item.points[0],item.points[1]) +360) % 360; + feature.geometry.coordinates.push(distBearing(item.points[0], item.width/2, direction-90)); + for (var j = 1; j < item.points.length-1; j++){ + var direction1 = (bearing(item.points[j], item.points[j-1]) +360) % 360; + var direction2 = (bearing(item.points[j], item.points[j+1]) +360) % 360; + var factor = 1/Math.sin(((direction2-direction1)/2)*(Math.PI/180)); + feature.geometry.coordinates.push(distBearing(item.points[j], (item.width/2)*factor , ((direction1+direction2)/2))); + } + direction = (bearing(item.points[item.points.length-1],item.points[item.points.length-2]) + 180) % 360; + + //Arrowhead + var point = distBearing(item.points[item.points.length-1], item.width, direction+180); + feature.geometry.coordinates.push(distBearing(point, item.width/2, direction-90)) + feature.geometry.coordinates.push(distBearing(point, item.width, direction-90)) + feature.geometry.coordinates.push(item.points[item.points.length-1]); + feature.geometry.coordinates.push(distBearing(point, item.width, direction+90)) + feature.geometry.coordinates.push(distBearing(point, item.width/2, direction+90)) + + for (var j = item.points.length-2; j > 0; j--){ + var direction1 = (bearing(item.points[j], item.points[j-1]) +360) % 360; + var direction2 = (bearing(item.points[j], item.points[j+1]) +360) % 360; + var factor = 1/Math.sin(((direction2-direction1)/2)*(Math.PI/180)); + feature.geometry.coordinates.push(distBearing(item.points[j], -(item.width/2)*factor, ((direction1+direction2)/2))); + } + + direction = (bearing(item.points[0],item.points[1]) +360) % 360; + feature.geometry.coordinates.push(distBearing(item.points[0], item.width/2, direction+90)); + break; + case 'circle': + feature.geometry = {"type": "Polygon"}; + feature.geometry.coordinates = [[]]; + for (var j = 360; j >= 0; j-=5){ + feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], item.r, j)); + } + break; + case 'composite': + //Flatten composites at the moment + var subfeatures = items2features(item.items, true); + for (key in subfeatures){ + subfeatures[key].properties.parent = {};//feature.properties; + if(item.uri)subfeatures[key].properties.parent.uri = item.uri; + features.push(subfeatures[key]); + } + break; + case 'corridor': + var direction; + feature.geometry = {"type": "Polygon"}; + feature.geometry.coordinates = [[]]; + direction = (bearing(item.points[0],item.points[1]) +360) % 360; + feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction-90)); + for (var j = 1; j < item.points.length-1; j++){ + var direction1 = (bearing(item.points[j], item.points[j-1]) +360) % 360; + var direction2 = (bearing(item.points[j], item.points[j+1]) +360) % 360; + var factor = 1/Math.sin(((direction2-direction1)/2)*(Math.PI/180)); + feature.geometry.coordinates[0].push(distBearing(item.points[j], (item.width/2)*factor, ((direction1+direction2)/2))); + } + + direction = (bearing(item.points[item.points.length-1],item.points[item.points.length-2]) + 180) % 360; + feature.geometry.coordinates[0].push(distBearing(item.points[item.points.length-1], item.width/2, direction-90)); + feature.geometry.coordinates[0].push(distBearing(item.points[item.points.length-1], item.width/2, direction+90)); + + for (var j = item.points.length-2; j > 0; j--){ + var direction1 = (bearing(item.points[j], item.points[j-1]) +360) % 360; + var direction2 = (bearing(item.points[j], item.points[j+1]) +360) % 360; + var factor = 1/Math.sin(((direction2-direction1)/2)*(Math.PI/180)); + feature.geometry.coordinates[0].push(distBearing(item.points[j], -(item.width/2)*factor, ((direction1+direction2)/2))); + } + + direction = (bearing(item.points[0],item.points[1]) +360) % 360; + feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction+90)); + feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction-90));//Close line + + break; + case 'ellipse': + feature.geometry = {"type": "Polygon"}; + feature.geometry.coordinates = [[]]; + for (var j = 360; j >= 0; j-=2){ + var radius = item.ry * item.rx / Math.sqrt(Math.pow(item.rx * Math.cos(j * (Math.PI/180)),2) + Math.pow(item.ry * Math.sin(j * (Math.PI/180)),2)); + feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], radius, j-(item.rotation||0))); + } + break; + case 'g': + //Flatten groups + var subfeatures = items2features(item.items, true); + for (key in subfeatures){ + subfeatures[key].properties.parent = {};//feature.properties; + if(item.uri)subfeatures[key].properties.parent.uri = item.uri; + features.push(subfeatures[key]); + } + break; + case 'multipoint': + feature.geometry = {"type": "MultiPoint"}; + feature.geometry.coordinates = item.points; + delete feature.properties.points; + break; + case 'orbit': + var direction; + feature.geometry = {"type": "Polygon"}; + feature.geometry.coordinates = [[]]; + direction = (Math.atan2(item.points[1][0] - item.points[0][0], item.points[1][1] - item.points[0][1]) - (Math.PI/2)) / (Math.PI/180); + feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction)); + for (var j = 0; j <= 180; j+=2){ + feature.geometry.coordinates[0].push(distBearing(item.points[1], item.width/2, direction + j )); + } + + direction = (Math.atan2(item.points[0][0] - item.points[1][0], item.points[0][1] - item.points[1][1]) - (Math.PI/2)) / (Math.PI/180); + feature.geometry.coordinates[0].push(distBearing(item.points[item.points.length-1], item.width/2, direction)); + for (var j = 0; j <= 180; j+=2){ + feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction + j )); + } + + direction = (Math.atan2(item.points[1][0] - item.points[0][0], item.points[1][1] - item.points[0][1]) - (Math.PI/2)) / (Math.PI/180); + feature.geometry.coordinates[0].push(distBearing(item.points[0], item.width/2, direction)); + break; + break; + case 'point': + feature.geometry = {"type": "Point"}; + feature.geometry.coordinates = [item.x, item.y]; + break; + case 'polygon': + feature.geometry = {"type": "Polygon"}; + feature.geometry.coordinates = [item.points]; + feature.geometry.coordinates[0].push(item.points[0]) + delete feature.properties.points; + break; + case 'polyline': + feature.geometry = {"type": "LineString"}; + feature.geometry.coordinates = item.points; + delete feature.properties.points; + break; + case 'rect': + feature.geometry = {"type": "Polygon"}; + feature.geometry.coordinates = [[]]; + var diagonalRadius = Math.sqrt(Math.pow(item.rx,2)+Math.pow(item.rx,2)); + var angle; + angle = ((Math.PI/2)-Math.atan2(item.ry, item.rx)) / (Math.PI/180); + feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], diagonalRadius, item.rotation?angle-item.rotation:angle)); + angle = ((Math.PI/2)-Math.atan2(-item.ry, item.rx)) / (Math.PI/180); + feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], diagonalRadius, item.rotation?angle-item.rotation:angle)); + angle = ((Math.PI/2)-Math.atan2(-item.ry, -item.rx)) / (Math.PI/180); + feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], diagonalRadius, item.rotation?angle-item.rotation:angle)); + angle = ((Math.PI/2)-Math.atan2(item.ry, -item.rx)) / (Math.PI/180); + feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], diagonalRadius, item.rotation?angle-item.rotation:angle)); + angle = ((Math.PI/2)-Math.atan2(item.ry, item.rx)) / (Math.PI/180); + feature.geometry.coordinates[0].push(distBearing([item.cx,item.cy], diagonalRadius, item.rotation?angle-item.rotation:angle)); + break; + case 'text': + feature.geometry = {"type": "Point"}; + feature.geometry.coordinates = [item.x, item.y]; + break; + default: + console.log('TODO parse item default: ' + item.drawable) + } + + if(item.hasOwnProperty('exclusion')){ + for (var e = 0; e < item.exclusion.length; e++){ + feature.geometry.coordinates.push(exclusions(item.exclusion[e])); + } + } + if(feature.geometry){ + features.push(feature); + } + } + return features; + } + var geoJSON = {}; + geoJSON.type = 'FeatureCollection'; + for (var key in this){ + if(key == 'items'){ + geoJSON.features = items2features(this.items, false); + }else{ + geoJSON[key] = this[key]; + } + } + return geoJSON; + } + toXML(){ + //parse this to NVG XML + } +}; diff --git a/worldmap/worldmap.appcache b/worldmap/worldmap.appcache index 208be52..2c7c9ed 100644 --- a/worldmap/worldmap.appcache +++ b/worldmap/worldmap.appcache @@ -1,5 +1,5 @@ CACHE MANIFEST -# date: Sept 10th 2018 - v1.4.0 +# date: Sept 10th 2018 - v1.4.2 CACHE: index.html @@ -40,6 +40,7 @@ leaflet/leaflet.measurecontrol.css leaflet/leaflet.measurecontrol.js leaflet/leaflet.select-layers.min.js leaflet/milsymbol.js +leaflet/nvg.js leaflet/OSMBuildings-Leaflet.js leaflet/sockjs.min.js leaflet/tile.stamen.js