Merge pull request #2768 from halemmerich/gpstrek
gpstrek - Adds zoomable map view of loaded routemaster
|
@ -14,3 +14,5 @@
|
|||
Averaging for smoothing compass headings
|
||||
Save state if route or waypoint has been chosen
|
||||
0.09: Workaround a minifier issue allowing to install gpstrek with minification enabled
|
||||
0.10: Adds map view of loaded route
|
||||
Automatically search for new waypoint if moving away from current target
|
|
@ -6,11 +6,18 @@ This app is inspired by and uses code from "GPS Navigation" and "Navigation comp
|
|||
|
||||
## Usage
|
||||
|
||||
Tapping or button to switch to the next information display, swipe right for the menu.
|
||||
Swipe left and right to change between map, info and menu. Swipe up and down in map and info display to change the displayed information.
|
||||
|
||||
Choose either a route or a waypoint as basis for the display.
|
||||
### Menu
|
||||
Choose either a route or a waypoint as basis for the display and configure settings to your liking.
|
||||
|
||||
After this selection and availability of a GPS fix the compass will show a checkered flag for your destination and a green dot for possibly available waypoints on the way.
|
||||
### Map
|
||||
The map shows the loaded route. Current waypoint is a green dot with a circle around it that denotes how close you need to be to autoselect the next waypoint as target.
|
||||
The map is centered on the currently selected waypoint and the position will be shown relative to the waypoint. If the position is farther away than can be shown on the display the icon turns red and shows the approximate direction on the border of the map.
|
||||
Secondary map mode is an overview map. Tap the overview to enter scrolling mode and move it around. Tap again to get back to normal function navigation.
|
||||
|
||||
### Info
|
||||
After selecting a target and availability of a GPS fix the compass will show a checkered flag for your destination and a green dot for possibly available waypoints on the way.
|
||||
Waypoints are shown with name if available and distance to waypoint.
|
||||
|
||||
As long as no GPS signal is available the compass shows the heading from the build in magnetometer. When a GPS fix becomes available, the compass display shows the GPS course. This can be differentiated by the display of bubble levels on top and sides of the compass.
|
||||
|
@ -18,13 +25,9 @@ If they are on display, the source is the magnetometer and you should keep the b
|
|||
|
||||
### Route
|
||||
|
||||
Routes can be created from .gpx files containing "trkpt" elements with this script: [createRoute.sh](createRoute.sh)
|
||||
|
||||
The resulting file needs to be uploaded to the watch and will be shown in the file selection menu.
|
||||
|
||||
The route can be mirrored to switch start and destination.
|
||||
|
||||
If the GPS position is closer than 30m to the next waypoint, the route is automatically advanced to the next waypoint.
|
||||
If the GPS position is close enough to the next waypoint, the route is automatically advanced to the next waypoint.
|
||||
|
||||
### Waypoints
|
||||
|
||||
|
|
1212
apps/gpstrek/app.js
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0"?>
|
||||
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common" xmlns="http://www.topografix.com/GPX/1/1" xmlns:osmand="https://osmand.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:_="http://www.topografix.com/GPX/1/1" xmlns:DEFAULT="http://www.topografix.com/GPX/1/1" version="1.0" extension-element-prefixes="exslt">
|
||||
<xsl:output omit-xml-declaration="no" indent="yes"/>
|
||||
|
||||
<xsl:template match="/">
|
||||
<xsl:for-each select="//_:trkpt">
|
||||
<xsl:choose>
|
||||
<xsl:when test="_:name and _:ele">
|
||||
<xsl:text>D</xsl:text>
|
||||
</xsl:when>
|
||||
<xsl:when test="_:ele and not(_:name)">
|
||||
<xsl:text>C</xsl:text>
|
||||
</xsl:when>
|
||||
<xsl:when test="not(_:ele) and _:name">
|
||||
<xsl:text>B</xsl:text>
|
||||
</xsl:when>
|
||||
<xsl:otherwise>
|
||||
<xsl:text>A</xsl:text>
|
||||
</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
<xsl:call-template name="value-of-template">
|
||||
<xsl:with-param name="select" select="format-number(@lat,"+00.0000000;-00.0000000")"/>
|
||||
</xsl:call-template>
|
||||
<xsl:call-template name="value-of-template">
|
||||
<xsl:with-param name="select" select="format-number(@lon,"+000.0000000;-000.0000000")"/>
|
||||
</xsl:call-template>
|
||||
<xsl:choose>
|
||||
<xsl:when test="_:ele">
|
||||
<xsl:call-template name="value-of-template">
|
||||
<xsl:with-param name="select" select="format-number(_:ele,"+00000;-00000")"/>
|
||||
</xsl:call-template>
|
||||
</xsl:when>
|
||||
</xsl:choose>
|
||||
<xsl:choose>
|
||||
<xsl:when test="_:name">
|
||||
<xsl:call-template name="value-of-template">
|
||||
<xsl:with-param name="select" select="format-number(string-length(_:name),"00")"/>
|
||||
</xsl:call-template>
|
||||
<xsl:call-template name="value-of-template">
|
||||
<xsl:with-param name="select" select="_:name"/>
|
||||
</xsl:call-template>
|
||||
</xsl:when>
|
||||
</xsl:choose>
|
||||
<xsl:value-of select="' '"/>
|
||||
</xsl:for-each>
|
||||
</xsl:template>
|
||||
<xsl:template name="value-of-template">
|
||||
<xsl:param name="select"/>
|
||||
<xsl:value-of select="$select"/>
|
||||
<xsl:for-each select="exslt:node-set($select)[position()>1]">
|
||||
<xsl:value-of select="' '"/>
|
||||
<xsl:value-of select="."/>
|
||||
</xsl:for-each>
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>
|
|
@ -1,14 +0,0 @@
|
|||
#!/bin/bash
|
||||
[ -z "$1" ] && echo Give gpx file name
|
||||
|
||||
|
||||
xmlstarlet select -t -m '//_:trkpt' \
|
||||
--if '_:name and _:ele' -o D \
|
||||
--elif '_:ele and not(_:name)' -o C \
|
||||
--elif 'not(_:ele) and _:name' -o B \
|
||||
--else -o A -b \
|
||||
-v 'format-number(@lat,"+00.0000000;-00.0000000")' \
|
||||
-v 'format-number(@lon,"+000.0000000;-000.0000000")' \
|
||||
--if '_:ele' -v 'format-number(_:ele,"+00000;-00000")' -b \
|
||||
--if _:name -v 'format-number(string-length(_:name),"00")' -v '_:name' -b \
|
||||
-n "$1" | iconv -f utf8 -t iso8859-1 > "$(basename "$1" | sed -e "s|.gpx||").trf"
|
|
@ -0,0 +1,93 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<link rel="stylesheet" href="../../css/spectre-exp.min.css">
|
||||
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||
<style>
|
||||
.badgeerror[data-badge]::after { background-color: red; }
|
||||
</style>
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
|
||||
<script>
|
||||
var converted;
|
||||
var filename;
|
||||
|
||||
function handleOnload(readerEvent){
|
||||
var content = readerEvent.target.result;
|
||||
|
||||
var xsltProcessor = new XSLTProcessor();
|
||||
|
||||
var myXMLHTTPRequest = new XMLHttpRequest();
|
||||
myXMLHTTPRequest.open("GET", "convert.xsl", false);
|
||||
myXMLHTTPRequest.send(null);
|
||||
|
||||
var xslRef = myXMLHTTPRequest.responseXML;
|
||||
|
||||
xsltProcessor.importStylesheet(xslRef);
|
||||
|
||||
var parser = new DOMParser();
|
||||
var doc = parser.parseFromString(content, "text/xml");
|
||||
|
||||
var fragment = xsltProcessor.transformToFragment(doc, document);
|
||||
|
||||
var serializer = new XMLSerializer();
|
||||
converted = serializer.serializeToString(fragment);
|
||||
|
||||
document.getElementById('upload').disabled = false;
|
||||
}
|
||||
|
||||
function handleFileChange(event){
|
||||
document.getElementById('error').innerHTML = "";
|
||||
var file = event.target.files[0];
|
||||
if (!file) {
|
||||
document.getElementById('error').innerHTML = "No valid file selected";
|
||||
return;
|
||||
}
|
||||
|
||||
filename = file.name.replace(/\.gpx/,".trf");
|
||||
document.getElementById('fileName').value = filename;
|
||||
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = handleOnload;
|
||||
|
||||
reader.readAsText(file,'UTF-8');
|
||||
}
|
||||
|
||||
function handleClick(event){
|
||||
if (converted){
|
||||
Util.showModal("Saving...");
|
||||
var name = document.getElementById('fileName').value;
|
||||
if (!name || name.length == 0)
|
||||
name = document.getElementById('fileName').placeholder;
|
||||
if (name){
|
||||
Util.writeStorage(name, converted, function() {
|
||||
Util.hideModal();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
document.getElementById('status').innerHTML += "error" + "</br>";
|
||||
document.getElementById('error').innerHTML = "File could not be converted correctly.";
|
||||
}
|
||||
}
|
||||
function onInit() {}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Upload tracks to your watch</h1>
|
||||
<p>Allows uploading a GPX format track file to your watch. Converts tracks containing "trkpt"-nodes into the GPS Trekking format.</p>
|
||||
<form>
|
||||
<input class="form-input" type="file" id="fileSelector"><br/>
|
||||
<label class="form-label" for="fileName">Filename on watch (must end with .trf an be shorter than 28 characters total)</label>
|
||||
<input class="form-input" placeholder="route.trf" id="fileName" pattern="^.{0,24}\.trf$"><br/>
|
||||
<button class="btn btn-primary" type="button" id="upload">Upload</button><br/>
|
||||
</form>
|
||||
<div id="error"></div>
|
||||
<div id="status"></div>
|
||||
<script>
|
||||
document.getElementById('upload').disabled = true;
|
||||
document.getElementById('fileSelector').addEventListener('change', handleFileChange);
|
||||
document.getElementById('upload').addEventListener('click', handleClick);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,14 +1,15 @@
|
|||
{
|
||||
"id": "gpstrek",
|
||||
"name": "GPS Trekking",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],
|
||||
"screenshots": [{"url":"screenInit.png"},{"url":"screenMenu.png"},{"url":"screenMap.png"},{"url":"screenLost.png"},{"url":"screenOverview.png"},{"url":"screenOverviewScroll.png"},{"url":"screenSlices.png"},{"url":"screenSlices2.png"},{"url":"screenSlices3.png"}],
|
||||
"tags": "tool,outdoors,gps",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"dependencies" : { "waypoints":"type" },
|
||||
"interface" : "interface.html",
|
||||
"storage": [
|
||||
{"name":"gpstrek.app.js","url":"app.js"},
|
||||
{"name":"gpstrek.wid.js","url":"widget.js"},
|
||||
|
|
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 3.9 KiB |
|
@ -23,10 +23,12 @@ if (!state) {
|
|||
state = {};
|
||||
initState();
|
||||
}
|
||||
state.started = false;
|
||||
let bgChanged = false;
|
||||
|
||||
function saveState(){
|
||||
state.saved = Date.now();
|
||||
if (state.route) delete state.route.indexToOffset;
|
||||
STORAGE.writeJSON("gpstrek.state.json", state);
|
||||
}
|
||||
|
||||
|
@ -144,6 +146,9 @@ function start(bg){
|
|||
Bangle.setHRMPower(1, "gpstrek");
|
||||
Bangle.setCompassPower(1, "gpstrek");
|
||||
Bangle.setBarometerPower(1, "gpstrek");
|
||||
|
||||
state.started = true;
|
||||
|
||||
if (bg){
|
||||
if (!state.active) bgChanged = true;
|
||||
state.active = true;
|
||||
|
@ -153,6 +158,7 @@ function start(bg){
|
|||
}
|
||||
|
||||
function stop(bg){
|
||||
state.started = true;
|
||||
if (bg){
|
||||
if (state.active) bgChanged = true;
|
||||
state.active = false;
|
||||
|
@ -177,12 +183,12 @@ if (state.active){
|
|||
start(false);
|
||||
}
|
||||
|
||||
WIDGETS["gpstrek"]={
|
||||
WIDGETS.gpstrek={
|
||||
area:"tl",
|
||||
width:state.active?24:0,
|
||||
resetState: initState,
|
||||
getState: function() {
|
||||
if (state.saved && Date.now() - state.saved > 60000 || !state){
|
||||
if (!state.started && state.saved && Date.now() - state.saved > 60000 || !state){
|
||||
initState();
|
||||
}
|
||||
return state;
|
||||
|
|