From 9bc26db65b0fa56f6661b11c749dd381ff382894 Mon Sep 17 00:00:00 2001 From: "Hansi, dl9rdz" Date: Mon, 25 Jan 2021 19:27:50 +0100 Subject: [PATCH] minor things added --- LICENSE-EasyButton | 10 ++ package.json | 4 +- www/css/easy-button.css | 56 ++++++ www/css/index.css | 53 +++++- www/img/landing.png | Bin 0 -> 578 bytes www/index.html | 15 +- www/js/easy-button.js | 376 ++++++++++++++++++++++++++++++++++++++++ www/js/index.js | 211 ++++++++++++++++++++-- 8 files changed, 699 insertions(+), 26 deletions(-) create mode 100644 LICENSE-EasyButton create mode 100644 www/css/easy-button.css create mode 100644 www/img/landing.png create mode 100644 www/js/easy-button.js diff --git a/LICENSE-EasyButton b/LICENSE-EasyButton new file mode 100644 index 0000000..e2aaa4b --- /dev/null +++ b/LICENSE-EasyButton @@ -0,0 +1,10 @@ +css/easy-button.css +js/easy-button.js + +Copyright (C) 2014 Daniel Montague + +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. diff --git a/package.json b/package.json index b360684..f2c57e5 100644 --- a/package.json +++ b/package.json @@ -15,13 +15,15 @@ "devDependencies": { "cordova-android": "^9.0.0", "cordova-browser": "^6.0.0", + "cordova-plugin-inappbrowser": "^4.1.0", "cordova-plugin-whitelist": "^1.3.4", "de-dl9rdz-rdzwx": "https://github.com/dl9rdz/rdzwx-plugin.git" }, "cordova": { "plugins": { "cordova-plugin-whitelist": {}, - "de-dl9rdz-rdzwx": {} + "de-dl9rdz-rdzwx": {}, + "cordova-plugin-inappbrowser": {} }, "platforms": [ "android" diff --git a/www/css/easy-button.css b/www/css/easy-button.css new file mode 100644 index 0000000..18ce9ac --- /dev/null +++ b/www/css/easy-button.css @@ -0,0 +1,56 @@ +.leaflet-bar button, +.leaflet-bar button:hover { + background-color: #fff; + border: none; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; +} + +.leaflet-bar button { + background-position: 50% 50%; + background-repeat: no-repeat; + overflow: hidden; + display: block; +} + +.leaflet-bar button:hover { + background-color: #f4f4f4; +} + +.leaflet-bar button:first-of-type { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +.leaflet-bar button:last-of-type { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; +} + +.leaflet-bar.disabled, +.leaflet-bar button.disabled { + cursor: default; + pointer-events: none; + opacity: .4; +} + +.easy-button-button .button-state{ + display: block; + width: 100%; + height: 100%; + position: relative; +} + + +.leaflet-touch .leaflet-bar button { + width: 30px; + height: 30px; + line-height: 30px; +} diff --git a/www/css/index.css b/www/css/index.css index ed4e8f5..3832458 100644 --- a/www/css/index.css +++ b/www/css/index.css @@ -33,18 +33,63 @@ body { padding:0px; /* Padding to avoid the "unsafe" areas behind notches in the screen */ padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px); - text-transform:uppercase; width:100%; } -html, body, #map { +html, body { height: 100%; width: 100cv; } -#info { - height: 20%; +#all { + height: 100%; width: 100cv; } +#map { + height: 100%; + width: 100cv; +} + +.sondeTooltip { + color: #333; + font-size: 11px; + font-weight: bold; +} +.text-speed { + color: #008cba; +} + +.fitbutton { + font-size: 1.5em; +} +.target { + font-size: 1.5em; +} +.infobutton { + font-size: 1.5em; + font-family: 'Courier New', monospace; +} + +.leaflet-center { + position: relative !important; + left: 0; + right: 0; + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; +} + +.leaflet-center .leaflet-control { + bottom: 0; +} + +.leaflet-control-container .leaflet-control-bottomcenter { + position: absolute; + bottom: 0; + left: 0; + right: 0; +} + /* Portrait layout (default) */ .app { diff --git a/www/img/landing.png b/www/img/landing.png new file mode 100644 index 0000000000000000000000000000000000000000..ee6bd11b9e45ae64ff3465f1bb557bcaab85519e GIT binary patch literal 578 zcmV-I0=@l-P)Ji`0UMidv|sFCalJeF80nh(1e<#1z^X6f6@+ zA$Xs_M#Za$|3C!`uh(WStjq4s%-$3!a$uUBne+SZ?BO?O*^CVwJ8@{ Qr2qf`07*qoM6N<$g2RvdIsgCw literal 0 HcmV?d00001 diff --git a/www/index.html b/www/index.html index 744bbeb..cd6d25c 100644 --- a/www/index.html +++ b/www/index.html @@ -36,13 +36,22 @@ + + + + + Hello World -
-
- +
+
+ +
diff --git a/www/js/easy-button.js b/www/js/easy-button.js new file mode 100644 index 0000000..2863fd5 --- /dev/null +++ b/www/js/easy-button.js @@ -0,0 +1,376 @@ +(function(){ + +// This is for grouping buttons into a bar +// takes an array of `L.easyButton`s and +// then the usual `.addTo(map)` +L.Control.EasyBar = L.Control.extend({ + + options: { + position: 'topleft', // part of leaflet's defaults + id: null, // an id to tag the Bar with + leafletClasses: true // use leaflet classes? + }, + + + initialize: function(buttons, options){ + + if(options){ + L.Util.setOptions( this, options ); + } + + this._buildContainer(); + this._buttons = []; + + for(var i = 0; i < buttons.length; i++){ + buttons[i]._bar = this; + buttons[i]._container = buttons[i].button; + this._buttons.push(buttons[i]); + this.container.appendChild(buttons[i].button); + } + + }, + + + _buildContainer: function(){ + this._container = this.container = L.DomUtil.create('div', ''); + this.options.leafletClasses && L.DomUtil.addClass(this.container, 'leaflet-bar easy-button-container leaflet-control'); + this.options.id && (this.container.id = this.options.id); + }, + + + enable: function(){ + L.DomUtil.addClass(this.container, 'enabled'); + L.DomUtil.removeClass(this.container, 'disabled'); + this.container.setAttribute('aria-hidden', 'false'); + return this; + }, + + + disable: function(){ + L.DomUtil.addClass(this.container, 'disabled'); + L.DomUtil.removeClass(this.container, 'enabled'); + this.container.setAttribute('aria-hidden', 'true'); + return this; + }, + + + onAdd: function () { + return this.container; + }, + + addTo: function (map) { + this._map = map; + + for(var i = 0; i < this._buttons.length; i++){ + this._buttons[i]._map = map; + } + + var container = this._container = this.onAdd(map), + pos = this.getPosition(), + corner = map._controlCorners[pos]; + + L.DomUtil.addClass(container, 'leaflet-control'); + + if (pos.indexOf('bottom') !== -1) { + corner.insertBefore(container, corner.firstChild); + } else { + corner.appendChild(container); + } + + return this; + } + +}); + +L.easyBar = function(){ + var args = [L.Control.EasyBar]; + for(var i = 0; i < arguments.length; i++){ + args.push( arguments[i] ); + } + return new (Function.prototype.bind.apply(L.Control.EasyBar, args)); +}; + +// L.EasyButton is the actual buttons +// can be called without being grouped into a bar +L.Control.EasyButton = L.Control.extend({ + + options: { + position: 'topleft', // part of leaflet's defaults + + id: null, // an id to tag the button with + + type: 'replace', // [(replace|animate)] + // replace swaps out elements + // animate changes classes with all elements inserted + + states: [], // state names look like this + // { + // stateName: 'untracked', + // onClick: function(){ handle_nav_manually(); }; + // title: 'click to make inactive', + // icon: 'fa-circle', // wrapped with + // } + + leafletClasses: true, // use leaflet styles for the button + tagName: 'button', + }, + + + + initialize: function(icon, onClick, title, id){ + + // clear the states manually + this.options.states = []; + + // add id to options + if(id != null){ + this.options.id = id; + } + + // storage between state functions + this.storage = {}; + + // is the last item an object? + if( typeof arguments[arguments.length-1] === 'object' ){ + + // if so, it should be the options + L.Util.setOptions( this, arguments[arguments.length-1] ); + } + + // if there aren't any states in options + // use the early params + if( this.options.states.length === 0 && + typeof icon === 'string' && + typeof onClick === 'function'){ + + // turn the options object into a state + this.options.states.push({ + icon: icon, + onClick: onClick, + title: typeof title === 'string' ? title : '' + }); + } + + // curate and move user's states into + // the _states for internal use + this._states = []; + + for(var i = 0; i < this.options.states.length; i++){ + this._states.push( new State(this.options.states[i], this) ); + } + + this._buildButton(); + + this._activateState(this._states[0]); + + }, + + _buildButton: function(){ + + this.button = L.DomUtil.create(this.options.tagName, ''); + + if (this.options.tagName === 'button') { + this.button.setAttribute('type', 'button'); + } + + if (this.options.id ){ + this.button.id = this.options.id; + } + + if (this.options.leafletClasses){ + L.DomUtil.addClass(this.button, 'easy-button-button leaflet-bar-part leaflet-interactive'); + } + + // don't let double clicks and mousedown get to the map + L.DomEvent.addListener(this.button, 'dblclick', L.DomEvent.stop); + L.DomEvent.addListener(this.button, 'mousedown', L.DomEvent.stop); + L.DomEvent.addListener(this.button, 'mouseup', L.DomEvent.stop); + + // take care of normal clicks + L.DomEvent.addListener(this.button,'click', function(e){ + L.DomEvent.stop(e); + this._currentState.onClick(this, this._map ? this._map : null ); + this._map && this._map.getContainer().focus(); + }, this); + + // prep the contents of the control + if(this.options.type == 'replace'){ + this.button.appendChild(this._currentState.icon); + } else { + for(var i=0;i"']/) ){ + + // if so, the user should have put in html + // so move forward as such + tmpIcon = ambiguousIconString; + + // then it wasn't html, so + // it's a class list, figure out what kind + } else { + ambiguousIconString = ambiguousIconString.replace(/(^\s*|\s*$)/g,''); + tmpIcon = L.DomUtil.create('span', ''); + + if( ambiguousIconString.indexOf('fa-') === 0 ){ + L.DomUtil.addClass(tmpIcon, 'fa ' + ambiguousIconString) + } else if ( ambiguousIconString.indexOf('glyphicon-') === 0 ) { + L.DomUtil.addClass(tmpIcon, 'glyphicon ' + ambiguousIconString) + } else { + L.DomUtil.addClass(tmpIcon, /*rollwithit*/ ambiguousIconString) + } + + // make this a string so that it's easy to set innerHTML below + tmpIcon = tmpIcon.outerHTML; + } + + return tmpIcon; +} + +})(); diff --git a/www/js/index.js b/www/js/index.js index e0495e8..a26e8d3 100644 --- a/www/js/index.js +++ b/www/js/index.js @@ -23,9 +23,30 @@ document.addEventListener('deviceready', onDeviceReady, false); // map from sondeid to marker (and path) var markers = {}; -var mypos; var ready = 0; var map = null; +var lastObj = { obj: null, pred: null, land: null }; +//var mypos = {lat: 48.56, lon: 13.43}; +var mypos = {lat: 48.1, lon: 13.1}; + +var ballonIcon, landIcon; +var infobox = null; + +var checkMark = "✅"; +var crossMark = "❌"; + +// add "bottom center" to leaflet +(function (L) { + L.Map.prototype._initControlPos = function(_initControlPos) { + return function() { + _initControlPos.apply(this, arguments); // original function + this._controlCorners['bottomcenter'] = L.DomUtil.create('div', 'leaflet-bottom leaflet-center', + L.DomUtil.create('div', 'leaflet-control-bottomcenter', this._controlContainer) + ); + }; + } (L.Map.prototype._initControlPos); +}(L, this, document)); + function onDeviceReady() { // Cordova is now initialized. Have fun! @@ -68,19 +89,164 @@ function onDeviceReady() { var baseMapControl = new L.control.layers(baseMaps, {}, { collapsed: true, position: 'topright' } ).addTo(map); baseMaps["Openstreetmap"].addTo(map); - //map.addEventListener('baselayerchange', function(){ map.fire('click'); }); + // not working.......... map.addEventListener('baselayerchange', baseMapControl.collapse() ); L.control.scale({metric: true, imperial: false, position: "bottomright"}).addTo(map); - - //L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { - // attribution: '© OpenStreetMap contributors', - //}).addTo(map); + // prediction + L.easyButton('', function(btn, map) { + getPrediction(); + }).addTo(map); map.locate({setView: true, maxZoom: 16}); - RdzWx.start("testarg", callBack); + var Infobox = L.Control.extend({ + options: { position: 'bottomcenter' }, + + onAdd: function(map) { + var infoContainer = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control'); + var infoBody = L.DomUtil.create('div', 'leaflet-popup-content-wrapper'); + infoContainer.appendChild(infoBody); + infoBody.setAttribute('style', 'max-width: 100vw'); + var infoContent = L.DomUtil.create('div', 'leaflet-popup-content'); + infoBody.appendChild(infoContent); + var infoCloseButton = L.DomUtil.create('a', 'leaflet-popup-close-button'); + infoContainer.appendChild(infoCloseButton); + infoCloseButton.innerHTML = 'x'; + infoCloseButton.setAttribute('style', 'cursor: pointer'); + this._infoContainer = infoContainer; + this._infoBody = infoBody; + this._infoContentContainer = infoContent; + this._infoCloseButton = infoCloseButton; + + infoContent.innerHTML = 'This is the inner content'; + this._hideContent(); + + L.DomEvent.disableClickPropagation(infoContainer); + L.DomEvent.on(infoCloseButton, 'click', L.DomEvent.stop); + L.DomEvent.on(infoCloseButton, 'click', this._hideContent, this); + + return infoContainer; + }, + toggle: function() { + if(this._contentShown == false) { this._showContent(); } else { this._hideContent(); } + }, + setContent: function(content) { + this._infoContent = content; + if(this._infoContentContainer) { this._infoContentContainer.innerHTML = content; } + }, + _hideContent: function(ev) { + this._infoBody.style.display = 'none'; + this._infoCloseButton.style.display = 'none'; + this._contentShown = false; + }, + _showContent: function(ev) { + this._infoBody.style.display = ''; + this._infoCloseButton.style.display = ''; + this._contentShown = true; + }, + }); + infobox = new Infobox(); + infobox.addTo(map); + + // button to show/hide info box on bottom + L.easyButton('I', function(btn, map) { + infobox.toggle(); + }).addTo(map); + + // fit map to enclosing rectangle of (last pos, own pos) + L.easyButton('', function(btn, map) { + // last item + if(lastObj.obj == null) return; + // self position + if(mypos == null) return; + var items = [ [lastObj.obj.lat, lastObj.obj.lon], [mypos.lat, mypos.lon] ]; + if(lastObj.land) { items.push( lastObj.land.getLatLng() ); } + b = L.latLngBounds(items); + map.fitBounds(b); + }).addTo(map); + + ttgoStatus = L.easyButton( { + ttgourl: "http://192.168.42.1", + states: [{ stateName: 'offline', + icon: '' + crossMark + '', + onClick: function(btn, map) { btn.state('online'); } + }, + { stateName: 'online', + icon: '' + checkMark + '', + onClick: function(btn, map) { cordova.InAppBrowser.open(btn.ttgourl, '_blank', "location=yes"); } + } + ], + position: "topright" + }); + ttgoStatus.state('offline'); + ttgoStatus.addTo(map); + + // '', ) + + ballonIcon = L.icon({ + iconUrl: "img/ballon.png", + iconSize: [17,22], + iconAnchor: [9,22], + popupAnchor: [0,-28] + }); + landingIcon = L.icon({ + iconUrl: "img/landing.png", + iconSize: [24,24], + iconAnchor: [12,12], + popupAnchor: [0,0] + }); ready = 1; + RdzWx.start("testarg", callBack); + + // just for testing + update( {id: "A1234567", lat: 48, lon: 13, alt: 10000, vs: 10, hs: 30} ); +} + +function formatParams(params) { + return '?' + Object.keys(params).map( function(key) { + return key+"="+encodeURIComponent(params[key]) + }).join('&'); +} + +function getPrediction() { + TAWHIRI = 'http://predict.cusf.co.uk/api/v1'; + if(lastObj.obj == null) { + alert("no object available"); + return; + } + var tParams = { + "launch_latitude": lastObj.obj.lat, + "launch_longitude": lastObj.obj.lon, + "launch_altitude": lastObj.obj.alt, + "launch_datetime": new Date().toISOString().split('.')[0] + 'Z', + "ascent_rate": 5, + "descent_rate": 3, + "burst_altitude": lastObj.obj.alt+2, + "profile": "standard_profile", + } + const xhr = new XMLHttpRequest(); + const url = TAWHIRI + formatParams(tParams); + xhr.onreadystatechange = function() { + if(xhr.readyState === 4) { + console.log(xhr.response); + var pred = JSON.parse(xhr.response); + var traj = pred.prediction[1]; // 0 is ascent, 1 is descent... + traj = traj.trajectory; + var latlons = []; + traj.forEach( p => latlons.push( [p.latitude, p.longitude] ) ); + //alert("path: "+JSON.stringify(traj)); + poly = L.polyline(latlons, { opacity: 0.5, color: '#EE0000', dashArray: '8, 6'} ); + poly.addTo(map); + if( lastObj.pred ) { lastObj.pred.remove(map); } + lastObj.pred = poly; + if( lastObj.land ) { lastObj.land.remove(map); } + lastObj.land = new L.marker(latlons.slice(-1)[0], {icon: landingIcon}); + lastObj.land.addTo(map); + } + } + xhr.open('GET', url, true); + xhr.send(null); } function callBack(arg) { @@ -92,7 +258,7 @@ function callBack(arg) { return; } console.log("callback: "+JSON.stringify(arg)); - if(obj.id) { + if(obj.id || obj.msgtype) { update(obj); } } @@ -103,32 +269,41 @@ function update(obj) { console.log("not ready"); return; } + if(obj.msgtype) { + if(obj.msgtype == "ttgostatus") { + ttgoStatus.ttgourl = 'http://' + obj.ip; + ttgoStatus.state(obj.state) + } + // TODO: add GPS messages + return; + } + lastObj.obj = obj; var pos = new L.LatLng(obj.lat, obj.lon); var marker; + var tooltip; if(markers[obj.id]) { marker = markers[obj.id]; if(pos.equals(marker.getLatLng())) { console.log("update: position unchanged"); } else { marker.path.addLatLng(pos); console.log("update: appending new position"); } + tooltip = marker.tt; } else { console.log("creating new marker"); - var myIcon = L.icon({ - //iconUrl: "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png", - iconUrl: "img/ballon.png", - iconSize: [17,22], - iconAnchor: [9,22], - popupAnchor: [0,-28], - }); - marker = new L.marker(pos, {icon: myIcon}); + marker = new L.marker(pos, {icon: ballonIcon}); poly = L.polyline(pos, { opacity: 0.5, color: '#3388ff'} ); marker.path = poly; //marker.on('clock', function() { showMarkerInfoWindow( obj.id, pos) } ); markers[obj.id] = marker; marker.addTo(map); poly.addTo(map); + tooltip = L.tooltip({ direction: 'right', permanent: true, className: 'sondeTooltip', offset: [10,0], interactive: false, opacity: 0.6 }); + marker.bindTooltip(tooltip); + marker.tt = tooltip; } - //var icon = new Icon(); - //marker.set + var tt = '
' + obj.id + '
' + obj.alt + 'm '+ obj.vs +'m/s ' + (obj.hs*3.6).toFixed(1) + 'km/h
'; + tooltip.setContent(tt); + marker.setLatLng(pos); marker.update(); // necessary? } +