update 1.0.2 - fix 0/0 pos, enhanced prediction

upa v1.0.2
Hansi, dl9rdz 2021-01-29 13:20:12 +01:00
rodzic 9a49b48f4c
commit 1bfc9f8369
9 zmienionych plików z 913 dodań i 51 usunięć

Wyświetl plik

@ -0,0 +1,12 @@
www/js/leaflet.contextmenu.js
www/css/leaflet.contextmenu.css
The MIT License (MIT)
Copyright (c) 2017 adam.ratcliffe@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Wyświetl plik

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<widget id="de.dl9rdz" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" <widget id="de.dl9rdz" version="1.0.2" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"
xmlns:android="schemas.android.com/apk/res/android"> xmlns:android="schemas.android.com/apk/res/android">
<name>rdzSondyGO</name> <name>rdzSondyGO</name>
<description> <description>

Wyświetl plik

@ -1,7 +1,7 @@
{ {
"name": "de.dl9rdz", "name": "de.dl9rdz",
"displayName": "rdzwx-go", "displayName": "rdzwx-go",
"version": "0.0.1", "version": "1.0.1",
"description": "A sample Apache Cordova application that uses rdzwx-plugin.", "description": "A sample Apache Cordova application that uses rdzwx-plugin.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@ -22,11 +22,12 @@
"cordova": { "cordova": {
"plugins": { "plugins": {
"cordova-plugin-whitelist": {}, "cordova-plugin-whitelist": {},
"de-dl9rdz-rdzwx": {}, "cordova-plugin-inappbrowser": {},
"cordova-plugin-inappbrowser": {} "de-dl9rdz-rdzwx": {}
}, },
"platforms": [ "platforms": [
"android" "android",
"browser"
] ]
} }
} }

Wyświetl plik

@ -49,6 +49,25 @@ html, body {
width: 100cv; width: 100cv;
} }
#toolbar {
background: rgba(255, 255, 255, 1);
opacity: .6;
width: 100vw;
height: 600px;
height: 100vh;
position: absolute;
left: -100vw;
z-index: 2000;
transition: .6s left;
padding: 20px;
box-sizing: border-box;
}
#toolbar.open {
left: 0;
opacity: 0.9;
}
.sondeTooltip { .sondeTooltip {
color: #333; color: #333;
font-size: 11px; font-size: 11px;
@ -88,6 +107,34 @@ html, body {
float: right; float: right;
} }
.tawhiridiv {
background-color: #ecfedf;
padding: 1px 8px;
margin: 0px 0px !important;
}
.tawhiridiv td {
padding: 0px;
position: relative;
}
.tawhiridiv table {
border-spacing: 0;
}
.tawhiricontent {
clear: both;
font-size: 11pt;
}
.tawhiricontent input {
font-size: 11pt;
width: 5em;
}
.tawhiricontent input[type=checkbox] {
-webkit-transform: scale(1.5) !important;
}
.tawhirismall {
margin: 0pt !important;
font-size: 10pt;
}
.infocontent p { .infocontent p {
display: inline-block; display: inline-block;
} }
@ -144,6 +191,29 @@ html, body {
} }
} }
.ballon-menu .leaflet-popup-tip {
background: rgba(0,0,0,0) !important;
box-shadow: none !important;
width: 0px;
height: 0px;
}
.ballon-drop {
background:#f8f8f8;
border: 1px solid #ccc;
border-radius:3px;
}
.ballon-drop a {
color:#333;
display: block;
padding: 5px 10px;
text-decoration:none;
}
.ballon-drop a:hover {
background:#333;
color:#fff;
}
.pop-header { .pop-header {
display: flex; display: flex;
align-items:center; align-items:center;

Wyświetl plik

@ -0,0 +1,54 @@
.leaflet-contextmenu {
display: none;
box-shadow: 0 1px 7px rgba(0,0,0,0.4);
-webkit-border-radius: 4px;
border-radius: 4px;
padding: 4px 0;
background-color: #fff;
cursor: default;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.leaflet-contextmenu a.leaflet-contextmenu-item {
display: block;
color: #222;
font-size: 12px;
line-height: 20px;
text-decoration: none;
padding: 0 12px;
border-top: 1px solid transparent;
border-bottom: 1px solid transparent;
cursor: default;
outline: none;
}
.leaflet-contextmenu a.leaflet-contextmenu-item-disabled {
opacity: 0.5;
}
.leaflet-contextmenu a.leaflet-contextmenu-item.over {
background-color: #f4f4f4;
border-top: 1px solid #f0f0f0;
border-bottom: 1px solid #f0f0f0;
}
.leaflet-contextmenu a.leaflet-contextmenu-item-disabled.over {
background-color: inherit;
border-top: 1px solid transparent;
border-bottom: 1px solid transparent;
}
.leaflet-contextmenu-icon {
margin: 2px 8px 0 0;
width: 16px;
height: 16px;
float: left;
border: 0;
}
.leaflet-contextmenu-separator {
border-bottom: 1px solid #ccc;
margin: 5px 0;
}

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 776 B

Wyświetl plik

@ -36,8 +36,10 @@
<meta name="color-scheme" content="light dark"> <meta name="color-scheme" content="light dark">
<link rel="stylesheet" href="css/index.css"> <link rel="stylesheet" href="css/index.css">
<link rel="stylesheet" href="css/leaflet.css"> <link rel="stylesheet" href="css/leaflet.css">
<link rel="stylesheet" href="css/leaflet.contextmenu.css">
<script src="cordova.js"></script> <script src="cordova.js"></script>
<script src="js/leaflet.js"></script> <script src="js/leaflet.js"></script>
<script src="js/leaflet.contextmenu.js"></script>
<link rel="stylesheet" href="css/easy-button.css"> <link rel="stylesheet" href="css/easy-button.css">
<!-- <!--
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-easybutton@2/src/easy-button.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-easybutton@2/src/easy-button.css">
@ -49,8 +51,17 @@
</head> </head>
<body> <body>
<div id="all"> <div id="all">
<div id="toolbar">
<a id="toolbarclose" class="leaflet-popup-close-button" style="cursor: pointer; float: right;">x</a>
<h2>rdzSondyGO v1.0.1</h2>
<p>Copyright &copy; 2021 Hansi Reiser, dl9rdz</p>
<p>see <a href="https://github.com/dl9rdz/rdzwx-go">https://github.com/dl9rdz/rdzwx-go</a> for details</p>
<p>Apache License Version 2.0</p>
<hr>
<div id="toolbaritems">
</div>
</div>
<div id='map'></div> <div id='map'></div>
<!-- <div id='info'></div> -->
</div> </div>
<script src="js/index.js"></script> <script src="js/index.js"></script>
</body> </body>

Wyświetl plik

@ -25,12 +25,14 @@ document.addEventListener('deviceready', onDeviceReady, false);
var markers = {}; var markers = {};
var ready = 0; var ready = 0;
var map = null; var map = null;
var lastObj = { obj: null, pred: null, land: null }; /////var lastObj = { obj: null, marker: null, /*no longer used: */pred: null, land: null };
var lastMarker = null;
var mypos = {lat: 48.56, lon: 13.43}; var mypos = {lat: 48.56, lon: 13.43};
//var mypos = {lat: 48.1, lon: 13.1}; //var mypos = {lat: 48.1, lon: 13.1};
var myposMarker = null; var myposMarker = null;
var ballonIcon, landIcon; var ballonIcon, landIcon, burstIcon;
var infobox = null; var infobox = null;
//var checkMark = "&#9989;"; //var checkMark = "&#9989;";
@ -78,8 +80,7 @@ function onDeviceReady() {
}); });
var hybrid = new L.layerGroup([sat, Stamen_TonerHybrid]); var hybrid = new L.layerGroup([sat, Stamen_TonerHybrid]);
map = L.map('map', { layers: [osm] } ).setView([48,13],12); map = L.map('map', { layers: [osm], contextmenu: true, zoomControl: false} ).setView([48,13],12);
var baseMaps = { var baseMaps = {
"Openstreetmap": osm, "Openstreetmap": osm,
"Landscape": tfland, "Landscape": tfland,
@ -99,13 +100,74 @@ function onDeviceReady() {
L.control.scale({metric: true, imperial: false, position: "bottomright"}).addTo(map); L.control.scale({metric: true, imperial: false, position: "bottomright"}).addTo(map);
// prediction // main menu
L.easyButton('<span class="target">&target;</span>', function(btn, map) { L.easyButton('<span class="target">&equiv;</span>', function(btn, map) {
getPrediction(); toolbar = L.DomUtil.get("toolbar");
L.DomUtil.addClass(toolbar, "open");
toolbarclose = L.DomUtil.get("toolbarclose");
L.DomEvent.on(toolbarclose, 'click', function(e) {L.DomUtil.removeClass(toolbar, "open")});
}).addTo(map); }).addTo(map);
new L.Control.Zoom({position: "topleft" }).addTo(map);
// prediction
L.easyButton('<span id="targetbtn" class="target">&target;</span>', function(btn, map) {
getPrediction();
}).addTo(map);
t = L.DomUtil.get("targetbtn");
if(t) { L.DomEvent.on(t, 'contextmenu', function(e) { tawhiriCtl.toggle(); } ); }
map.locate({setView: true, maxZoom: 16}); map.locate({setView: true, maxZoom: 16});
var TawhiriCtl = L.Control.extend({
options: { position: 'bottomcenter' },
onAdd: function(map) {
var tawhiriContainer = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control');
var tawhiriBody = L.DomUtil.create('div', 'leaflet-popup-content-wrapper');
tawhiriContainer.appendChild(tawhiriBody);
var tawhiriContent = L.DomUtil.create('div', 'leaflet-popup-content tawhiridiv');
tawhiriBody.appendChild(tawhiriContent);
var infoCloseButton = L.DomUtil.create('a', 'leaflet-popup-close-button');
tawhiriContainer.appendChild(infoCloseButton);
infoCloseButton.innerHTML = 'x';
infoCloseButton.setAttribute('style', 'cursor: pointer');
var infoContent = L.DomUtil.create('div', 'tawhiricontent');
infoContent.innerHTML = '<h3>Tawhiri prediction parameter</h3><form><table>' +
'<tr><td>Ascent rate:</td><td><input type="number" size="5" value="5.0" step="any" id="tawhiri-ascent"></input></td><td>m/s</td></tr>' +
'<tr><td>Burst alt:</td><td><input type="number" size="5" value="35000" step="any" id="tawhiri-burst"></input></td><td>m</td></tr>' +
'<tr><td>Sea-level descent rate:</td><td><input type="number" size="5" value="5.0" step="any" id="tawhiri-descent"></input></td><td>m/s</td></tr>' +
'<tr><td>Use current v<sub>v</sub>:</td><td><input type="checkbox" checked="yes" id="tawhiri-current"></input></td><td></td><tr>' +
'<tr><td colspan="3"><p class="tawhirismall">If checked: On ascent: ascent rate := v<sub>v</sub><br>' +
'On descent: descent rate := estimate_sea_level(v<sub>v</sub>)</p></td></tr>';
'</table></form>'+
tawhiriContent.appendChild(infoContent);
this._tawhiriBody = tawhiriBody;
this._infoCloseButton = infoCloseButton;
this._showContent();
L.DomEvent.disableClickPropagation(tawhiriContainer);
L.DomEvent.on(infoCloseButton, 'click', L.DomEvent.stop);
L.DomEvent.on(infoCloseButton, 'click', this._hideContent, this);
return tawhiriContainer;
},
toggle: function() {
if(this._contentShown==false) { this._showContent(); } else { this._hideContent(); }
},
_hideContent: function(ev) {
this._tawhiriBody.style.display = 'none';
this._infoCloseButton.style.display = 'none';
this._contentShown = false;
},
_showContent: function(ev) {
this._tawhiriBody.style.display = '';
this._infoCloseButton.style.display = '';
this._contentShown = true;
},
})
tawhiriCtl = new TawhiriCtl();
tawhiriCtl.addTo(map);
var Infobox = L.Control.extend({ var Infobox = L.Control.extend({
options: { position: 'bottomcenter' }, options: { position: 'bottomcenter' },
@ -154,12 +216,10 @@ function onDeviceReady() {
if(!this._infoContentContainer) return; if(!this._infoContentContainer) return;
if(obj.type == null) obj.type = "RS41"; // TODO fix in plugin if(obj.type == null) obj.type = "RS41"; // TODO fix in plugin
distance = ""; distance = "";
if(obj.validPos) { distance = L.latLng(obj).distanceTo(L.latLng(mypos))
distance = L.latLng(obj).distanceTo(L.latLng(mypos)) if(distance>9999) { distance = distance.toFixed(0); }
if(distance>9999) { distance = distance.toFixed(0); } else { distance = distance.toFixed(1); }
else { distance = distance.toFixed(1); } distance = "d=" + distance + "m";
distance = "d=" + distance + "m";
}
sym = "<span class=\"lifenessinfo\">&#x2B24; </span>"; sym = "<span class=\"lifenessinfo\">&#x2B24; </span>";
l1 = "<table class=\"infotable\"><tr><td class=\"infotd\">" + sym + obj.type + "</td><td class=\"infotdr\">" + obj.ser + "</td></tr></table>"; l1 = "<table class=\"infotable\"><tr><td class=\"infotd\">" + sym + obj.type + "</td><td class=\"infotdr\">" + obj.ser + "</td></tr></table>";
l2 = "<table class=\"infotable\"><tr><td class=\"infotd\">" + (1*obj.freq).toFixed(3) + " MHz </td><td class=\"infotdr\" style=\”font-size:0.9em;\">" + (0.001*obj.afc).toFixed(2) + " kHz</td></tr></table>"; l2 = "<table class=\"infotable\"><tr><td class=\"infotd\">" + (1*obj.freq).toFixed(3) + " MHz </td><td class=\"infotdr\" style=\”font-size:0.9em;\">" + (0.001*obj.afc).toFixed(2) + " kHz</td></tr></table>";
@ -195,11 +255,11 @@ function onDeviceReady() {
// fit map to enclosing rectangle of (last pos, own pos) // fit map to enclosing rectangle of (last pos, own pos)
L.easyButton('<span class="fitbutton">&#9635;</span>', function(btn, map) { L.easyButton('<span class="fitbutton">&#9635;</span>', function(btn, map) {
// last item // last item
if(lastObj.obj == null) return; if(lastMarker == null) return;
// self position // self position
if(mypos == null) return; if(mypos == null) return;
var items = [ [lastObj.obj.lat, lastObj.obj.lon], [mypos.lat, mypos.lon] ]; var items = [ [lastMarker.obj.lat, lastMarker.obj.lon], [mypos.lat, mypos.lon] ];
if(lastObj.land) { items.push( lastObj.land.getLatLng() ); } if(lastMarker.land) { items.push( lastMarker.land.getLatLng() ); }
b = L.latLngBounds(items); b = L.latLngBounds(items);
map.fitBounds(b); map.fitBounds(b);
}).addTo(map); }).addTo(map);
@ -234,12 +294,18 @@ function onDeviceReady() {
iconAnchor: [12,12], iconAnchor: [12,12],
popupAnchor: [0,0] popupAnchor: [0,0]
}); });
burstIcon = L.icon({
iconUrl: "img/pop-marker.png",
iconSize: [16,16],
iconAnchor: [8,8],
popupAnchor: [0,0]
});
ready = 1; ready = 1;
RdzWx.start("testarg", callBack); RdzWx.start("testarg", callBack);
setInterval(periodicStatusCheck, 1000); setInterval(periodicStatusCheck, 1000);
// just for testing // just for testing
update( {res: 0, validId: 1, validPos: 1, id: "A1234567", lat: 48, lon: 13, alt: 10000, vs: 10, hs: 30, rssi: -90, rxStat: "||||||||||||....", type: "RS41", freq: "400.000", afc: "+1.2", ser: "A1234567"} ); update( {res: 0, validId: 1, validPos: 127, id: "A1234567", lat: 48, lon: 13, alt: 10000, vs: 10, hs: 30, rssi: -90, rxStat: "||||||||||||....", type: "RS41", freq: "400.000", afc: "+1.2", ser: "A1234567"} );
updateMypos(mypos); updateMypos(mypos);
} }
@ -277,33 +343,50 @@ function calc_drag(drag,alt){
return drag; return drag;
} }
function getPrediction() { function removePrediction(marker) {
if(marker.pred) { marker.pred.remove(map); }
if(marker.land) { marker.land.remove(map); }
if(marker.burst) { marker.burst.remove(map); }
}
function getPrediction(refobj) {
TAWHIRI = 'http://predict.cusf.co.uk/api/v1'; TAWHIRI = 'http://predict.cusf.co.uk/api/v1';
if(lastObj.obj == null) { if(refobj == null) { refobj = lastMarker; }
if(refobj == null) {
alert("no object available"); alert("no object available");
return; return;
} }
// lookup parameters from form
var burst = document.getElementById("tawhiri-burst").value;
if(burst) burst= parseInt(burst); else burst=35000;
if(refobj.obj.alt > burst) burst = refobj.obj.alt;
var asc = document.getElementById("tawhiri-ascent").value;
if(asc) asc=parseFloat(asc); else asc=5.0;
var desc = document.getElementById("tawhiri-descent").value;
if(desc) desc=parseFloat(desc); else desc=5.0;
var usecurrent = document.getElementById("tawhiri-current").checked;
var tParams = { var tParams = {
"launch_latitude": lastObj.obj.lat, "launch_latitude": refobj.obj.lat,
"launch_longitude": lastObj.obj.lon, "launch_longitude": refobj.obj.lon,
"launch_altitude": lastObj.obj.alt, "launch_altitude": refobj.obj.alt,
"launch_datetime": new Date().toISOString().split('.')[0] + 'Z', "launch_datetime": new Date().toISOString().split('.')[0] + 'Z',
"ascent_rate": 5, "ascent_rate": asc,
"descent_rate": 5, "descent_rate": desc,
"burst_altitude": lastObj.obj.alt+2, "burst_altitude": refobj.obj.alt+2,
"profile": "standard_profile", "profile": "standard_profile",
} }
var vs = lastObj.obj.vs; var vs = refobj.obj.vs;
if( markers[lastObj.obj.id] && markers[lastObj.obj.id].vsavg ) { if( refobj.vsavg ) {
vs = markers[lastObj.obj.id].vsavg; vs = refobj.vsavg;
if(vs*lastObj.obj.vs < 0) vs=lastObj.obj.vs; if(vs*refobj.obj.vs < 0) vs=refobj.obj.vs;
} }
if(vs > 0) { if(vs > 0) {
// still climbing up // still climbing up
tParams["ascent_rate"] = vs; tParams["ascent_rate"] = usecurrent ? vs : asc;
tParams["burst_altitude"] = 35000; tParams["burst_altitude"] = burst;
} else { } else {
tParams["descent_rate"] = calc_drag( -vs, lastObj.obj.alt ); tParams["descent_rate"] = usecurrent ? calc_drag( -vs, refobj.obj.alt ) : desc;
} }
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
const url = TAWHIRI + formatParams(tParams); const url = TAWHIRI + formatParams(tParams);
@ -322,11 +405,17 @@ function getPrediction() {
//alert("path: "+JSON.stringify(traj)); //alert("path: "+JSON.stringify(traj));
poly = L.polyline(latlons, { opacity: 0.5, color: '#EE0000', dashArray: '8, 6'} ); poly = L.polyline(latlons, { opacity: 0.5, color: '#EE0000', dashArray: '8, 6'} );
poly.addTo(map); poly.addTo(map);
if( lastObj.pred ) { lastObj.pred.remove(map); } if( refobj.pred ) { refobj.pred.remove(map); }
lastObj.pred = poly; refobj.pred = poly;
if( lastObj.land ) { lastObj.land.remove(map); } if( refobj.land ) { refobj.land.remove(map); }
lastObj.land = new L.marker(latlons.slice(-1)[0], {icon: landingIcon}); refobj.land = new L.marker(latlons.slice(-1)[0], {icon: landingIcon});
lastObj.land.addTo(map); refobj.land.addTo(map);
if( refobj.burst ) { refobj.burst.remove(map); }
if( vs>0 ) { // still climbing, so add burst mark
var b = traj0.slice(-1)[0];
refobj.burst = new L.marker( [b.latitude, b.longitude], {icon: burstIcon});
refobj.burst.addTo(map);
}
var lastpt = traj1.splice(-1)[0]; var lastpt = traj1.splice(-1)[0];
lastpt.datetime = new Date(lastpt.datetime).toISOString().split(".")[0] + "Z"; lastpt.datetime = new Date(lastpt.datetime).toISOString().split(".")[0] + "Z";
var popup = '<div class="pop-header"><img src="img/landing.png"><h4> Landing Point </h4></div>' + var popup = '<div class="pop-header"><img src="img/landing.png"><h4> Landing Point </h4></div>' +
@ -337,7 +426,7 @@ function getPrediction() {
'</br>Burst: ' + tParams["burst_altitude"] + ' m'+ '</br>Burst: ' + tParams["burst_altitude"] + ' m'+
'</br>Desc. Rate: ' + tParams["descent_rate"].toFixed(2) + ' m/s</p>' + '</br>Desc. Rate: ' + tParams["descent_rate"].toFixed(2) + ' m/s</p>' +
''; '';
lastObj.land.bindPopup(popup); refobj.land.bindPopup(popup);
} }
} }
xhr.open('GET', url, true); xhr.open('GET', url, true);
@ -403,6 +492,10 @@ function update(obj) {
} }
// position update // position update
if( ((obj.validPos&0x03) != 0x03) || ((obj.validPos&0x80)!=0) ) { // latitude and longitude are invalid
console.log("invalid position, ignoring");
return;
}
console.log("Pos update: "+JSON.stringify(obj)); console.log("Pos update: "+JSON.stringify(obj));
infobox.setContent(obj); infobox.setContent(obj);
infobox.setStatus(obj.res); infobox.setStatus(obj.res);
@ -413,7 +506,6 @@ function update(obj) {
console.log("update with no valid pos"); console.log("update with no valid pos");
return; return;
} }
lastObj.obj = obj;
console.log("Good update!"); console.log("Good update!");
var pos = new L.LatLng(obj.lat, obj.lon); var pos = new L.LatLng(obj.lat, obj.lon);
var marker; var marker;
@ -426,7 +518,29 @@ function update(obj) {
marker.vsavg = 0.9 * marker.vsavg + 0.1 * obj.vs; marker.vsavg = 0.9 * marker.vsavg + 0.1 * obj.vs;
} else { } else {
console.log("creating new marker"); console.log("creating new marker");
marker = new L.marker(pos, {icon: ballonIcon}); marker = new L.marker(pos, {icon: ballonIcon,
contextmenu: true,
contextmenuItems: [{
/*
text: "Show info",
callback: function(e) { alert("not available yet"); }
}, {
*/
text: "Make prediction",
callback: function(e) { getPrediction(marker); }
}, {
text: "Configure prediction",
callback: function(e) { tawhiriCtl.toggle(); }
}, {
text: "Remove prediction",
callback: function(e) { removePrediction(marker); }
}, {
separator: true
}, {
text: "Delete item",
callback: function(e) { removePrediction(marker); map.removeLayer(marker); if(marker==lastMarker) lastMarker=null; }
}]
});
poly = L.polyline(pos, { opacity: 0.5, color: '#3388ff'} ); poly = L.polyline(pos, { opacity: 0.5, color: '#3388ff'} );
marker.path = poly; marker.path = poly;
//marker.on('clock', function() { showMarkerInfoWindow( obj.id, pos) } ); //marker.on('clock', function() { showMarkerInfoWindow( obj.id, pos) } );
@ -437,11 +551,19 @@ function update(obj) {
marker.bindTooltip(tooltip); marker.bindTooltip(tooltip);
marker.tt = tooltip; marker.tt = tooltip;
marker.vsavg = obj.vs; marker.vsavg = obj.vs;
} }
var tt = '<div class="tooltip-container">' + obj.id + '<div class="text-speed tooltip-container">' + obj.alt + 'm '+ obj.vs +'m/s ' + (obj.hs*3.6).toFixed(1) + 'km/h </div></div>'; lastMarker = marker;
tooltip.setContent(tt); lastMarker.obj = obj;
var tt = '<div class="tooltip-container">' + obj.id + '<div class="text-speed tooltip-container">' + obj.alt + 'm '+ obj.vs +'m/s ' + (obj.hs*3.6).toFixed(1) + 'km/h </div></div>';
tooltip.setContent(tt);
marker.setLatLng(pos); marker.setLatLng(pos);
marker.update(); // necessary? marker.update(); // necessary?
} }
function createButton(label, container) {
var btn = L.DomUtil.create("button", "", container);
btn.setAttribute("type", "button");
btn.innerHTML = label;
return btn;
}

Wyświetl plik

@ -0,0 +1,592 @@
/*
Leaflet.contextmenu, a context menu for Leaflet.
(c) 2015, Adam Ratcliffe, GeoSmart Maps Limited
@preserve
*/
(function(factory) {
// Packaging/modules magic dance
var L;
if (typeof define === 'function' && define.amd) {
// AMD
define(['leaflet'], factory);
} else if (typeof module === 'object' && typeof module.exports === 'object') {
// Node/CommonJS
L = require('leaflet');
module.exports = factory(L);
} else {
// Browser globals
if (typeof window.L === 'undefined') {
throw new Error('Leaflet must be loaded first');
}
factory(window.L);
}
})(function(L) {
L.Map.mergeOptions({
contextmenuItems: []
});
L.Map.ContextMenu = L.Handler.extend({
_touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
statics: {
BASE_CLS: 'leaflet-contextmenu'
},
initialize: function (map) {
L.Handler.prototype.initialize.call(this, map);
this._items = [];
this._visible = false;
var container = this._container = L.DomUtil.create('div', L.Map.ContextMenu.BASE_CLS, map._container);
container.style.zIndex = 10000;
container.style.position = 'absolute';
if (map.options.contextmenuWidth) {
container.style.width = map.options.contextmenuWidth + 'px';
}
this._createItems();
L.DomEvent
.on(container, 'click', L.DomEvent.stop)
.on(container, 'mousedown', L.DomEvent.stop)
.on(container, 'dblclick', L.DomEvent.stop)
.on(container, 'contextmenu', L.DomEvent.stop);
},
addHooks: function () {
var container = this._map.getContainer();
L.DomEvent
.on(container, 'mouseleave', this._hide, this)
.on(document, 'keydown', this._onKeyDown, this);
if (L.Browser.touch) {
L.DomEvent.on(document, this._touchstart, this._hide, this);
}
this._map.on({
contextmenu: this._show,
mousedown: this._hide,
zoomstart: this._hide
}, this);
},
removeHooks: function () {
var container = this._map.getContainer();
L.DomEvent
.off(container, 'mouseleave', this._hide, this)
.off(document, 'keydown', this._onKeyDown, this);
if (L.Browser.touch) {
L.DomEvent.off(document, this._touchstart, this._hide, this);
}
this._map.off({
contextmenu: this._show,
mousedown: this._hide,
zoomstart: this._hide
}, this);
},
showAt: function (point, data) {
if (point instanceof L.LatLng) {
point = this._map.latLngToContainerPoint(point);
}
this._showAtPoint(point, data);
},
hide: function () {
this._hide();
},
addItem: function (options) {
return this.insertItem(options);
},
insertItem: function (options, index) {
index = index !== undefined ? index: this._items.length;
var item = this._createItem(this._container, options, index);
this._items.push(item);
this._sizeChanged = true;
this._map.fire('contextmenu.additem', {
contextmenu: this,
el: item.el,
index: index
});
return item.el;
},
removeItem: function (item) {
var container = this._container;
if (!isNaN(item)) {
item = container.children[item];
}
if (item) {
this._removeItem(L.Util.stamp(item));
this._sizeChanged = true;
this._map.fire('contextmenu.removeitem', {
contextmenu: this,
el: item
});
return item;
}
return null;
},
removeAllItems: function () {
var items = this._container.children,
item;
while (items.length) {
item = items[0];
this._removeItem(L.Util.stamp(item));
}
return items;
},
hideAllItems: function () {
var item, i, l;
for (i = 0, l = this._items.length; i < l; i++) {
item = this._items[i];
item.el.style.display = 'none';
}
},
showAllItems: function () {
var item, i, l;
for (i = 0, l = this._items.length; i < l; i++) {
item = this._items[i];
item.el.style.display = '';
}
},
setDisabled: function (item, disabled) {
var container = this._container,
itemCls = L.Map.ContextMenu.BASE_CLS + '-item';
if (!isNaN(item)) {
item = container.children[item];
}
if (item && L.DomUtil.hasClass(item, itemCls)) {
if (disabled) {
L.DomUtil.addClass(item, itemCls + '-disabled');
this._map.fire('contextmenu.disableitem', {
contextmenu: this,
el: item
});
} else {
L.DomUtil.removeClass(item, itemCls + '-disabled');
this._map.fire('contextmenu.enableitem', {
contextmenu: this,
el: item
});
}
}
},
isVisible: function () {
return this._visible;
},
_createItems: function () {
var itemOptions = this._map.options.contextmenuItems,
item,
i, l;
for (i = 0, l = itemOptions.length; i < l; i++) {
this._items.push(this._createItem(this._container, itemOptions[i]));
}
},
_createItem: function (container, options, index) {
if (options.separator || options === '-') {
return this._createSeparator(container, index);
}
var itemCls = L.Map.ContextMenu.BASE_CLS + '-item',
cls = options.disabled ? (itemCls + ' ' + itemCls + '-disabled') : itemCls,
el = this._insertElementAt('a', cls, container, index),
callback = this._createEventHandler(el, options.callback, options.context, options.hideOnSelect),
icon = this._getIcon(options),
iconCls = this._getIconCls(options),
html = '';
if (icon) {
html = '<img class="' + L.Map.ContextMenu.BASE_CLS + '-icon" src="' + icon + '"/>';
} else if (iconCls) {
html = '<span class="' + L.Map.ContextMenu.BASE_CLS + '-icon ' + iconCls + '"></span>';
}
el.innerHTML = html + options.text;
el.href = '#';
L.DomEvent
.on(el, 'mouseover', this._onItemMouseOver, this)
.on(el, 'mouseout', this._onItemMouseOut, this)
.on(el, 'mousedown', L.DomEvent.stopPropagation)
.on(el, 'click', callback);
if (L.Browser.touch) {
L.DomEvent.on(el, this._touchstart, L.DomEvent.stopPropagation);
}
// Devices without a mouse fire "mouseover" on tap, but never “mouseout"
if (!L.Browser.pointer) {
L.DomEvent.on(el, 'click', this._onItemMouseOut, this);
}
return {
id: L.Util.stamp(el),
el: el,
callback: callback
};
},
_removeItem: function (id) {
var item,
el,
i, l, callback;
for (i = 0, l = this._items.length; i < l; i++) {
item = this._items[i];
if (item.id === id) {
el = item.el;
callback = item.callback;
if (callback) {
L.DomEvent
.off(el, 'mouseover', this._onItemMouseOver, this)
.off(el, 'mouseover', this._onItemMouseOut, this)
.off(el, 'mousedown', L.DomEvent.stopPropagation)
.off(el, 'click', callback);
if (L.Browser.touch) {
L.DomEvent.off(el, this._touchstart, L.DomEvent.stopPropagation);
}
if (!L.Browser.pointer) {
L.DomEvent.on(el, 'click', this._onItemMouseOut, this);
}
}
this._container.removeChild(el);
this._items.splice(i, 1);
return item;
}
}
return null;
},
_createSeparator: function (container, index) {
var el = this._insertElementAt('div', L.Map.ContextMenu.BASE_CLS + '-separator', container, index);
return {
id: L.Util.stamp(el),
el: el
};
},
_createEventHandler: function (el, func, context, hideOnSelect) {
var me = this,
map = this._map,
disabledCls = L.Map.ContextMenu.BASE_CLS + '-item-disabled',
hideOnSelect = (hideOnSelect !== undefined) ? hideOnSelect : true;
return function (e) {
if (L.DomUtil.hasClass(el, disabledCls)) {
return;
}
var map = me._map,
containerPoint = me._showLocation.containerPoint,
layerPoint = map.containerPointToLayerPoint(containerPoint),
latlng = map.layerPointToLatLng(layerPoint),
relatedTarget = me._showLocation.relatedTarget,
data = {
containerPoint: containerPoint,
layerPoint: layerPoint,
latlng: latlng,
relatedTarget: relatedTarget
};
if (hideOnSelect) {
me._hide();
}
if (func) {
func.call(context || map, data);
}
me._map.fire('contextmenu.select', {
contextmenu: me,
el: el
});
};
},
_insertElementAt: function (tagName, className, container, index) {
var refEl,
el = document.createElement(tagName);
el.className = className;
if (index !== undefined) {
refEl = container.children[index];
}
if (refEl) {
container.insertBefore(el, refEl);
} else {
container.appendChild(el);
}
return el;
},
_show: function (e) {
this._showAtPoint(e.containerPoint, e);
},
_showAtPoint: function (pt, data) {
if (this._items.length) {
var map = this._map,
event = L.extend(data || {}, {contextmenu: this});
this._showLocation = {
containerPoint: pt
};
if (data && data.relatedTarget){
this._showLocation.relatedTarget = data.relatedTarget;
}
this._setPosition(pt);
if (!this._visible) {
this._container.style.display = 'block';
this._visible = true;
}
this._map.fire('contextmenu.show', event);
}
},
_hide: function () {
if (this._visible) {
this._visible = false;
this._container.style.display = 'none';
this._map.fire('contextmenu.hide', {contextmenu: this});
}
},
_getIcon: function (options) {
return L.Browser.retina && options.retinaIcon || options.icon;
},
_getIconCls: function (options) {
return L.Browser.retina && options.retinaIconCls || options.iconCls;
},
_setPosition: function (pt) {
var mapSize = this._map.getSize(),
container = this._container,
containerSize = this._getElementSize(container),
anchor;
if (this._map.options.contextmenuAnchor) {
anchor = L.point(this._map.options.contextmenuAnchor);
pt = pt.add(anchor);
}
container._leaflet_pos = pt;
if (pt.x + containerSize.x > mapSize.x) {
container.style.left = 'auto';
container.style.right = Math.min(Math.max(mapSize.x - pt.x, 0), mapSize.x - containerSize.x - 1) + 'px';
} else {
container.style.left = Math.max(pt.x, 0) + 'px';
container.style.right = 'auto';
}
if (pt.y + containerSize.y > mapSize.y) {
container.style.top = 'auto';
container.style.bottom = Math.min(Math.max(mapSize.y - pt.y, 0), mapSize.y - containerSize.y - 1) + 'px';
} else {
container.style.top = Math.max(pt.y, 0) + 'px';
container.style.bottom = 'auto';
}
},
_getElementSize: function (el) {
var size = this._size,
initialDisplay = el.style.display;
if (!size || this._sizeChanged) {
size = {};
el.style.left = '-999999px';
el.style.right = 'auto';
el.style.display = 'block';
size.x = el.offsetWidth;
size.y = el.offsetHeight;
el.style.left = 'auto';
el.style.display = initialDisplay;
this._sizeChanged = false;
}
return size;
},
_onKeyDown: function (e) {
var key = e.keyCode;
// If ESC pressed and context menu is visible hide it
if (key === 27) {
this._hide();
}
},
_onItemMouseOver: function (e) {
L.DomUtil.addClass(e.target || e.srcElement, 'over');
},
_onItemMouseOut: function (e) {
L.DomUtil.removeClass(e.target || e.srcElement, 'over');
}
});
L.Map.addInitHook('addHandler', 'contextmenu', L.Map.ContextMenu);
L.Mixin.ContextMenu = {
bindContextMenu: function (options) {
L.setOptions(this, options);
this._initContextMenu();
return this;
},
unbindContextMenu: function (){
this.off('contextmenu', this._showContextMenu, this);
return this;
},
addContextMenuItem: function (item) {
this.options.contextmenuItems.push(item);
},
removeContextMenuItemWithIndex: function (index) {
var items = [];
for (var i = 0; i < this.options.contextmenuItems.length; i++) {
if (this.options.contextmenuItems[i].index == index){
items.push(i);
}
}
var elem = items.pop();
while (elem !== undefined) {
this.options.contextmenuItems.splice(elem,1);
elem = items.pop();
}
},
replaceContextMenuItem: function (item) {
this.removeContextMenuItemWithIndex(item.index);
this.addContextMenuItem(item);
},
_initContextMenu: function () {
this._items = [];
this.on('contextmenu', this._showContextMenu, this);
},
_showContextMenu: function (e) {
var itemOptions,
data, pt, i, l;
if (this._map.contextmenu) {
data = L.extend({relatedTarget: this}, e);
pt = this._map.mouseEventToContainerPoint(e.originalEvent);
if (!this.options.contextmenuInheritItems) {
this._map.contextmenu.hideAllItems();
}
for (i = 0, l = this.options.contextmenuItems.length; i < l; i++) {
itemOptions = this.options.contextmenuItems[i];
this._items.push(this._map.contextmenu.insertItem(itemOptions, itemOptions.index));
}
this._map.once('contextmenu.hide', this._hideContextMenu, this);
this._map.contextmenu.showAt(pt, data);
}
},
_hideContextMenu: function () {
var i, l;
for (i = 0, l = this._items.length; i < l; i++) {
this._map.contextmenu.removeItem(this._items[i]);
}
this._items.length = 0;
if (!this.options.contextmenuInheritItems) {
this._map.contextmenu.showAllItems();
}
}
};
var classes = [L.Marker, L.Path],
defaultOptions = {
contextmenu: false,
contextmenuItems: [],
contextmenuInheritItems: true
},
cls, i, l;
for (i = 0, l = classes.length; i < l; i++) {
cls = classes[i];
// L.Class should probably provide an empty options hash, as it does not test
// for it here and add if needed
if (!cls.prototype.options) {
cls.prototype.options = defaultOptions;
} else {
cls.mergeOptions(defaultOptions);
}
cls.addInitHook(function () {
if (this.options.contextmenu) {
this._initContextMenu();
}
});
cls.include(L.Mixin.ContextMenu);
}
return L.Map.ContextMenu;
});