Merge pull request #331 from renaudgweb/master

QTH Locator app
master
Gordon Williams 2020-04-21 09:09:49 +01:00 committed by GitHub
commit c24547746e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 295 additions and 0 deletions

View File

@ -1400,5 +1400,29 @@
{"name":"rclock.app.js","url":"rclock.app.js"},
{"name":"rclock.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "hamloc",
"name": "QTH Locator / Maidenhead Locator System",
"shortName": "QTH Locator",
"icon": "app.png",
"version":"0.01",
"description": "Convert your current GPS location to the Maidenhead locator system used by HAM amateur radio operators",
"tags": "tool,outdoors,gps",
"storage": [
{"name":"hamloc.app.js","url":"app.js"},
{"name":"hamloc.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "osmpoi",
"name": "POI Compass",
"icon": "app.png",
"version":"0.01",
"description": "Uploads all the points of interest in an area onto your watch, same as Beer Compass with more p.o.i.",
"tags": "tool,outdoors,gps",
"custom": "osmpoi.html",
"storage": [
{"name":"osmpoi.app.js"},
{"name":"osmpoi.img"}
]
}
]

1
apps/hamloc/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

14
apps/hamloc/README.md Normal file
View File

@ -0,0 +1,14 @@
# QTH Locator
Convert your current GPS location to the [Maidenhead](https://en.wikipedia.org/wiki/Maidenhead_Locator_System) locator system used by HAM amateur radio operators.
## Description
A Maidenhead locator compresses latitude and longitude into a short string of characters, which is similar in concept to the World Geographic Reference System or GEOREF. This position information is presented in a limited level of precision to limit the number of characters needed for its transmission using voice, Morse code, or any other operating mode.
The chosen coding uses alternating pairs of letters and digits, like so:
* BL11bh
##
* support Paul Brewer KI6CQ HamGridSquare.js
* support Chris Veness 2002-2012 LatLon library

1
apps/hamloc/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4ACwVeAAM6nQECJsYqDFYItCAgQ0DFrxWE2ZhBxOJA4ICBGwhbbxGtAYOz2etnWt2YwBnQCBGgWtxBjXJQQpBLgQABEQIDBMAgwE2YYBwRcUxOtDwgABGgYvGTQQLCMSRNGEQaODF4SQDHgYwUFIlfAgY2DYQQGDsouCGAQSEGBouEKANl1onEwQvGA4QACA4IwQaIISFFwIwDXwI2ExL2BB4YABJgwwKnQAkRpzYDAAbuBA4oABe4aaDD44uGxCNEQwStEdwgAFR4IHGF4iRBxJeLBwIdCcwgvJxLwFFIRgLBo4dBcwj2CF45xIKIxeLAww4FAAuINIYbKMAteso8FAwovPCQllQQtlF4gLFUYwvDsq/HfIShEDhCQDIgIAFnQHGBBITRnWIXwdfAAYGFSYblBNI5KBsobEDo4GCdxyFDr2tR4+tDQrwNF5YlEnQvJaZIvKr4RIEoovaSAIvyXArbBF6OJR6mCXAgvBDwIlFd5IfBd6tfSQQRGCgeIBJE6DIIAFDoIGGF4OCAgIAEnQHGBBITRnWIF4P+V4ZMCWxBoBRw5PBNI7IGnQuCSAIfE1oSB1oADF4WIXxAvHsoIFH4IvEdIJWEDg4vICRTuJSAwABxAcJF5A5C1ovKRwhgCwSQGF6CpEXxBeGMA59JZIIvGA4hhBLw+JF4xRFSBBNDFIhHFe4VfLxhgCAEguIMAJSB1oABSA4HEB4RwBAgQXHss6LxKRDPQTxBsovIRIdexCOGRpwwIr5gFAwbuEAoheBFyQwFRIrwDLwZuBdwgTBC4IuRYYowGRAq+BAoeCAoRABFyIABD4IsCGAgpFGoguBd4eIFyRiEFoIwCEoKJDRwYqCCAJcUGJICBK4JgDKgOtOIQtce4s6AAI2EBAgteAAizBGgYECwQsiAD4"))

21
apps/hamloc/app.js Normal file
View File

@ -0,0 +1,21 @@
latLonToGridSquare=function(o,a){var t,e,n,s,l,i,r,h,M,f=-100,g=0,u="ABCDEFGHIJKLMNOPQRSTUVWX",d=u.toLowerCase();function N(o){return"number"==typeof o?o:"string"==typeof o?parseFloat(o):"function"==typeof o?parseFloat(o()):void E.showMessage("can't convert \ninput: "+o)}return"object"==typeof o?2===o.length?(f=N(o[0]),g=N(o[1])):"lat"in o&&"lon"in o?(f=N(o.lat),g=N(o.lon)):"latitude"in o&&"longitude"in o?(f=N(o.latitude),g=N(o.longitude)):E.showMessage("can't convert \nobject "+o):(f=N(o),g=N(a)),isNaN(f)&&E.showMessage("lat is NaN"),isNaN(g)&&E.showMessage("lon is NaN"),90===Math.abs(f)&&E.showMessage("grid invalid \nat N/S"),90<Math.abs(f)&&E.showMessage("invalid lat: \n"+f),180<Math.abs(g)&&E.showMessage("invalid lon: \n"+g),t=f+90,e=g+180,n=u[Math.floor(t/10)],s=u[Math.floor(e/20)],l=""+Math.floor(t%10),i=""+Math.floor(e/2%10),h=60*(t-Math.floor(t)),M=60*(e-2*Math.floor(e/2)),r=d[Math.floor(h/2.5)],s+n+i+l+d[Math.floor(M/5)]+r};
Bangle.setGPSPower(1);
var fix;
Bangle.removeAllListeners();
Bangle.on('GPS',function(f) {
fix=f;
g.clear();
g.setFontAlign(0,0);
if (!f.fix) {
g.setFont("6x8",3);
g.drawString("Waiting for",120,70);
g.drawString("GPS Fix",120,110);
g.setFont("6x8",2);
g.drawString(f.satellites+" satellites",120,170);
} else {
g.setFont("6x8",5);
var maidenhead = latLonToGridSquare(fix.lat,fix.lon);
g.drawString(maidenhead,120,120);
}
});

BIN
apps/hamloc/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

1
apps/osmpoi/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

5
apps/osmpoi/README.md Normal file
View File

@ -0,0 +1,5 @@
# Points Of Interest Compass
## Description
Uploads all the points of interest in an area onto your watch, same as Beer Compass with more p.o.i.

BIN
apps/osmpoi/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

228
apps/osmpoi/osmpoi.html Normal file
View File

@ -0,0 +1,228 @@
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.3/dist/leaflet.css" />
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<style>
body {
padding: 0;
margin: 0;
}
html, body, #map {
height: 100%;
width: 100%;
}
#map { z-index: 1; }
#controls {
padding: 10px;
margin: 10px;
border: 1px solid black;
position:absolute;
right:0px;top:0px;
background-color: rgb(255, 255, 255);
z-index: 100;
}
</style>
</head>
<body>
<div id="map">
</div>
<div id="controls">
<select id="amenity">
<option value="drinking_water">Drinking Water</option>
<option value="bicycle_parking">Bicycle Park</option>
<option value="post_box">Post Box</option>
<option value="bar">Bars</option>
<option value="bbq">Barbecue</option>
<option value="cafe">Café</option>
<option value="fast_food">Fast food</option>
<option value="restaurant">restaurant</option>
<option value="bicycle_repair_station">Bicycle Repair</option>
<option value="bicycle_rental">Bicycle rental</option>
<option value="bus_station">Bus station</option>
<option value="atm">Bank</option>
<option value="pharmacy">Pharmacy</option>
<option value="interner_cafe">Cybercafé</option>
</select>
<p>Click <button id="poi" class="btn btn-primary">Find P.O.I.</button></p>
<p>If ok, Click <button id="upload" class="btn btn-primary">Upload</button></p>
</div>
<script src="../../lib/customize.js"></script>
<script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://unpkg.com/osmtogeojson@2.2.12/osmtogeojson.js"></script>
<script>
/*
https://overpass-turbo.eu/ to check queries.
Run:
node
({{bbox}});
out;
Over the area you're interested in, click on the POI and find out
what you need to search for, eg.
node
[amenity=pub]
({{bbox}});
out;
*/
var map = L.map('map').locate({setView: true, maxZoom: 16});
var features = [];
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data &copy; <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors</a>'
}).addTo(map);
function buildOverpassApiUrl(map, overpassQuery) {
var bounds = map.getBounds().getSouth() + ',' + map.getBounds().getWest() + ',' + map.getBounds().getNorth() + ',' + map.getBounds().getEast();
var nodeQuery = 'node[' + overpassQuery + '](' + bounds + ');';
var wayQuery = 'way[' + overpassQuery + '](' + bounds + ');';
var relationQuery = 'relation[' + overpassQuery + '](' + bounds + ');';
var query = '?data=[out:json][timeout:15];(' + nodeQuery + wayQuery + relationQuery + ');out body geom;';
var baseUrl = 'https://overpass-api.de/api/interpreter';
var resultUrl = baseUrl + query;
return resultUrl;
}
document.getElementById("poi").addEventListener("click", function() {
//var overpassApiUrl = buildOverpassApiUrl(map, "highway=bus_stop");
var poi = document.getElementById("amenity").options[document.getElementById("amenity").selectedIndex].value;
var overpassApiUrl = buildOverpassApiUrl(map, "amenity="+poi);
var req = new XMLHttpRequest();
req.responseType = 'json';
req.onreadystatechange = function() {
console.log("XMLHttpRequest", this.readyState);
if (this.readyState == 4) {
var osmDataAsJson = req.response;
features = [];
if (osmDataAsJson.elements) {
osmDataAsJson.elements.forEach(function(el) {
if (el.type!="node") return;
features.push({lat:el.lat, lon:el.lon,name:el.tags.name});
});
}
var resultAsGeojson = osmtogeojson(osmDataAsJson);
var resultLayer = L.geoJson(resultAsGeojson, {
style: function (feature) {
return {color: "#ff0000"};
},
filter: function (feature, layer) {
var isPolygon = (feature.geometry) && (feature.geometry.type !== undefined) && (feature.geometry.type === "Polygon");
if (isPolygon) {
feature.geometry.type = "Point";
var polygonCenter = L.latLngBounds(feature.geometry.coordinates[0]).getCenter();
feature.geometry.coordinates = [ polygonCenter.lat, polygonCenter.lng ];
}
return true;
},
onEachFeature: function (feature, layer) {
var popupContent = "";
popupContent = popupContent + "<dt>@id</dt><dd>" + feature.properties.type + "/" + feature.properties.id + "</dd>";
var keys = Object.keys(feature.properties.tags);
keys.forEach(function (key) {
popupContent = popupContent + "<dt>" + key + "</dt><dd>" + feature.properties.tags[key] + "</dd>";
});
popupContent = popupContent + "</dl>"
layer.bindPopup(popupContent);
}
}).addTo(map);
}
};
req.open("GET", overpassApiUrl, true);
req.send();
});
document.getElementById("upload").addEventListener("click", function() {
var app = `var features = ${JSON.stringify(features)};
var img_nofix = require("heatshrink").decompress(atob("mEwxH+AH4A0PgYurg9kr0rGM4nBg9dsgADmUHGUYtHAAddGIJcgFpIxEMTsAlYtMAAZiaLh4AFmQwXLiSTaLiosBnMymUrGCYTBAAgvPCgjwaMh4raF/4v/F/4vUg4vulZgDgAAIF8EyEQUAh0cAAkVisHGA4+HF6gVBiwwFjkONo0HAAMOAAIvTnIhEiovMFQQADNgYvQroUDg4uGjj9EF448DF6a+HAAS+EF5CQCF6SMIeAQvFXYYwHF59eLpSOBF4hgKGAIvPskAFxKOFF80VM4KOFSBs5F6EWRY4xBF43+F5UyF6DuEizZBKwIuHSBQvXdIwvKMYsHlYvQW4IuPYAYRBMggvRgIvCFxwwCTYZlEg4vPlcHjkVFx6WJF6YuXMoTwCF58yVgIvXY4YvQrqqDGDAvvPYIvPsguaYQYv/F7owBF/4vfg4AGTIIAHF7gA/AH4AwA="));
var img_fix = require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AG2JnYv/FzovBGFgvuFwIvCGFQvuFwQvDGFAvuFwYvEGEwvuFwgvFGEgvuFwovGGEQvuFwwvHGEAvuFw4vIGDwvuFxAvJGDgvuFxIvKGDQvuFxQvLGDAvuFxYvMGCwvuFxgvNGCgvuFxovOGCQvuFxwvPGCAvuFx4vQGBwtOCQYvbFRwAJGCwqTGh4uMFS40LEcIA/AH4A/ACQA="));
// https://github.com/Leaflet/Leaflet/blob/master/src/geo/projection/Projection.SphericalMercator.js
function project(latlong) {
var d = Math.PI / 180,
max = 85.0511287798,
R = 6378137, // earth radius in m
lat = Math.max(Math.min(max, latlong.lat), -max),
sin = Math.sin(lat * d);
return {x:R * latlong.lon * d,
y:R * Math.log((1 + sin) / (1 - sin)) / 2};
}
features.forEach(function(f) {
f.p = project(f);
});
var fix = {fix:false,satellites:0};
var nearest = undefined;
var nearestangle = 0;
var nearestdist = 0;
Bangle.on('GPS', function(f) {
fix = f;
fix.p = project(fix);
nearest = undefined;
nearestangle = 0;
nearestdist = 5000; // 5km
features.forEach(function(f) {
var dx = f.p.x - fix.p.x;
var dy = f.p.y - fix.p.y;
var d = Math.sqrt(dx*dx + dy*dy);
if (d<nearestdist) {
nearestdist = d;
nearest = f;
}
});
if (nearest) {
var dx = nearest.p.x - fix.p.x;
var dy = nearest.p.y - fix.p.y;
nearestangle = Math.atan2(dy,dx);
}
});
Bangle.on('mag', function(m) {
if (!Bangle.isLCDOn()) return;
var headingrad = m.heading*Math.PI/180; // in radians
if (!isFinite(headingrad)) headingrad=0;
if (nearest)
g.drawImage(img_fix,120,120,{
rotate: (Math.PI/2)+headingrad-nearestangle,
scale:3,
});
else
g.drawImage(img_nofix,120,120,{
rotate: headingrad,
scale:2,
});
g.clearRect(60,0,180,24);
g.setFontAlign(0,0);
g.setFont("6x8");
if (fix.fix) {
g.drawString(nearest ? nearest.name : "---",120,4);
g.setFont("6x8",2);
g.drawString(nearest ? Math.round(nearestdist)+"m" : "---",120,16);
} else {
g.drawString(fix.satellites+" satellites",120,4);
}
});
Bangle.setCompassPower(1);
Bangle.setGPSPower(1);
g.clear();`;
var icon = `require("heatshrink").decompress(atob("mEwxH+AH4A0PgYurg9kr0rGM4nBg9dsgADmUHGUYtHAAddGIJcgFpIxEMTsAlYtMAAZiaLh4AFmQwXLiSTaLiosBnMymUrGCYTBAAgvPCgjwaMh4raF/4v/F/4vUg4vulZgDgAAIF8EyEQUAh0cAAkVisHGA4+HF6gVBiwwFjkONo0HAAMOAAIvTnIhEiovMFQQADNgYvQroUDg4uGjj9EF448DF6a+HAAS+EF5CQCF6SMIeAQvFXYYwHF59eLpSOBF4hgKGAIvPskAFxKOFF80VM4KOFSBs5F6EWRY4xBF43+F5UyF6DuEizZBKwIuHSBQvXdIwvKMYsHlYvQW4IuPYAYRBMggvRgIvCFxwwCTYZlEg4vPlcHjkVFx6WJF6YuXMoTwCF58yVgIvXY4YvQrqqDGDAvvPYIvPsguaYQYv/F7owBF/4vfg4AGTIIAHF7gA/AH4AwA="))`;
sendCustomizedApp({
storage:[
{name:"osmpoi.app.js", content:app},
{name:"osmpoi.img", content:icon, evaluate:true},
]
});
});
</script>
</body>
</html>