Web-App, Firefox OS App alles nur ein wenig JavaScript

Google Maps und Tracking

Screenshot der aktiven App
Screenshot der aktiven App
zoom

Screenshot der aktiven App
und die erzeugten GPX-Daten (473,5 kByte) 30.12.2018 21:29

Ein paar wenige Dateien und schon ist die Web-App fertig, oder?

Diese Web-App stellt folgende Funtionen zur Verfügung:

  • Google-Maps Kartenansicht mit Umschlatmöglichkeit zur Satelitenbild-Ansicht
  • Zentrierung der Karte zum aktuellen Standort
  • Aufnehmen der Koordinaten in eine Liste, zur Tracking-Anzeige in der Karte und zum Download als GPS-Datei im GPX Format
  • Löschen / Zurücksetzen der aufgenommenen Werte
  • Anzeige der Aktuellen Werte Longitude, Latitude, Altitude, Speed, Accuracy und Anzahl der eingehenden Werte im Verhätnis zu den aufgenommenen Werten
  • ungenaue als auch doppelte Werte werden ignoriert und nicht in die Werteliste aufgenommen

Es ist noch eine Funktion zur Anzeige aufgenommener Tracks bzw. von anderer Seite zur Verfügung gestelleter GPX-Dateinen angedacht, des Weiteren einige Eingabefelder zur Beschreibung der GPX-Datei. (ist noch in Arbeit)

Die komplette Definition der App wird in einer manifest Datei abgelegt, zu beachten sind hier besonders die Rechte, je nach Typ stehen auch unter Umständen nicht alle Möglichkeiten zur Verfügung. Unter Firefox OS kann man z.B. nicht beim Typ Web auf die SD-Card zugreifen, also ist später unter umständen ein anderer Weg einzuplanen, in diesem Fall der Download. Die meisten Punkte erklären sich hoffentlich von alleine.

manifest.webapp (1,02 kByte) 22.03.2020 22:35
{
  "version": "0.3.0",
  "name": "Gocher Maps Web App",
  "description": "Firefox OS Maps Web App",
  "launch_path": "/index.htm",
  "icons": {
    "16": "/img/icons/icon16x16.png",
    "48": "/img/icons/icon48x48.png",
    "60": "/img/icons/icon60x60.png",
    "128": "/img/icons/icon128x128.png",
    "512": "/img/icons/icon512x512.png"
  },
  "developer": {
    "name": "Udo Schmal",
    "url": "http://www.gocher.me"
  },
  "type": "web",
  "permissions": {
    "geolocation": {
      "description": "Needed for the app to get positions from the device."
    },
    "device-storage:sdcard": { "access": "readwrite" }
  },
  "installs_allowed_from": [
    "*"
  ],
  "locales": {
    "en": {
      "description": "Firefox OS Maps Web App",
      "developer": {
        "name": "Udo Schmal",
        "url": "http://www.gocher.me"
      }
    },
    "de": {
      "description": "Firefox OS Maps Web App",
      "developer": {
        "name": "Udo Schmal",
        "url": "http://www.gocher.me"
      }
    }
  },
  "default_locale": "en"
}

In der index.html der eigentlichen Seite werden in dieser App lediglich die benötigten JavaScripts und Stylesheets eingebunden.

index.htm HTML (804 Bytes) 06.05.2021 22:54
<!DOCTYPE html >
<html>
  <head>
    <meta charset="utf-8" />
    <title>Gocher Maps Web App</title>
    <meta name="description" content="Firefox OS Maps Web App" />
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width, minimum-scale=1, maximum-scale=1" />
    <link rel="stylesheet" href="app.css" />
    <meta http-equiv="Content-Security-Policy" content="default-src 'self' https://maps.googleapis.com/; script-src 'self'  https://maps.googleapis.com/; style-src 'self' https://maps.googleapis.com/; img-src 'self' data:  https://maps.googleapis.com/ " />
    <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3"></script>
    <script type="text/javascript" src="app.js" defer></script>
  </head>
  <body role="application"></body>
</html>

Ein paar wenige definitionen für die Gestaltung, den gößten Teil der Datei stellen die eingebetteten Bilddaten dar.

app.css StyleSheet (7,94 kByte) 22.03.2020 22:35
  [role="toolbar"] {height:4rem; width:100%; position:fixed; bottom:0; left:0; z-index:100; background:rgba(0,0,0, 0.85);}
  [role="toolbar"] ul {float:left; list-style:none; padding:0; margin:0;}
  [role="toolbar"] ul:last-child {float:right;}
  [role="toolbar"] li {float:left;}
  [role="toolbar"] button {width:5.5rem; height:4rem; border:none; font-size:0; background:transparent no-repeat 50% 50% / 3rem auto; padding:0; border-radius:0;}
  [role="toolbar"] button:active, [role="toolbar"] button.active {background-color:#008aaa;}
  [role="toolbar"] .pack-icon-mark {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NDA1RDQxQUE3MjA2ODExODhDNkE3NjAxQjM4MUZDNSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDOUMyRkJBN0REODYxMUUxQjVDNEE4QjczREVCRENENCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDOUMyRkJBNkREODYxMUUxQjVDNEE4QjczREVCRENENCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjk4MjBGQzc5QjkyMDY4MTE4OEM2QjcwQTAwNDE1NjQ1IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjg0MDVENDFBQTcyMDY4MTE4OEM2QTc2MDFCMzgxRkM1Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+hFqHHQAAAQFJREFUeNpi/P//P8NAAiaGAQajDhh1AAsFermAWB2IeYH4FhC/IMsUUDYkAvMBcTkQZwIxOxCvBOLv/xHgDxAvBWJeIs2DY2IVykEtegjE/P9xg02kOoDaacAXiGMHOhFOA2KDgXQADxDvBeJwJPNBCVaYntlQCIhXAPF9ID4MxEeA+DO1syExQA6KvYH410AVRKCQ2EbtNHAfmtr1gDgQiA/gUHcFiDOoURAhlwOggkgUi5oSIP6FVCacBmIxQmYzEtkgAcXjQyB+BMTyeNRpQ+MbpG4NEP+hZV2ADVyF4tHqeNQBw69F9AGIK4D4E7UdwDjaMRl1wKgDBtoBAAEGAJMNVePSORzwAAAAAElFTkSuQmCC);}
  [role="toolbar"] .pack-icon-share {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NDA1RDQxQUE3MjA2ODExODhDNkE3NjAxQjM4MUZDNSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDOUM5NjE4Q0REODYxMUUxQjVDNEE4QjczREVCRENENCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDOUM5NjE4QkREODYxMUUxQjVDNEE4QjczREVCRENENCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjk4MjBGQzc5QjkyMDY4MTE4OEM2QjcwQTAwNDE1NjQ1IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjg0MDVENDFBQTcyMDY4MTE4OEM2QTc2MDFCMzgxRkM1Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+14Nt4AAAAWxJREFUeNpi/P//P8NAAsZRB4w6YDA7QAuItwCxAIlm/gPiBUBcQokD2IG4B4jFyfCUG9QRQgMVBcuA2IvYkGNC47NRwQHoPrIDYl5iHACK8w4apLNWID4KxKKEHLCVzDgnBugC8V5sjkB2gCCNcxzIEfvQHcFCoaEqQBwOxIZALAf1kCIQ8wDxGagadST1OlBHOAHxa0iKAeYCKP4AxEuR+PgwFxBPBeK//8kDm2FmkRMCoBS9B4jNKAi5f5REwSwky/8C8XUgfoVsKBowQSsTLgJxCiLTkhYFFtAgBKktBWIBIqLrMFLQXwBiEWR5UkMgAYjvQovbezjU9AOxIxAboImDfO4CxG/wlYSEgDEBy0FADIgViLGcnDSwhIDl2EAVEJ8H4i/YJFnISICkgsOkVEaEwHdqF49MFDqIqi2it0DMCsQ7sVSpRJsHTWxE1y3IaWAuECcDsTOFngIVSItGW8WjDhh1ALEAIMAA+H41QW+KcxcAAAAASUVORK5CYII=);}
  [role="toolbar"] .pack-icon-move {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NDA1RDQxQUE3MjA2ODExODhDNkE3NjAxQjM4MUZDNSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDOUM5NjE4OEREODYxMUUxQjVDNEE4QjczREVCRENENCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDOUM5NjE4N0REODYxMUUxQjVDNEE4QjczREVCRENENCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjk4MjBGQzc5QjkyMDY4MTE4OEM2QjcwQTAwNDE1NjQ1IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjg0MDVENDFBQTcyMDY4MTE4OEM2QTc2MDFCMzgxRkM1Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+eL5wKQAAAVlJREFUeNpi/P//P8NAAiaGAQajDhgWDtAaSAf4AfF5IPYg2wRQNiQT6wLxx/8Q8AGI1ckxh1zLRYH4zn9UcBuIBUg1i9woCAXic0D8HMp/BI2KSHpGAQhvgfp+KblmDJlsKAjEXiSa7Q7E/NRwgAYQnwRiWxId4ADEx4FYBZ8iRgK1ISh/r4D65AQQH0STDwFiZSC+BsSb0eTsgdgCiN8DcTgQ7yY1EXoC8Z//1AG/gdgFmz0seHy/E4h7gLgcyn8BxE/R1KhAQ+cdEN9Hk5MGYgmQH4G4E4j3kpsNY4H4OxC3k5gNQeq/AXE4PvNZiEhMi4H4NhCbkZgIn0AT7ll8iliINOwEFJMCpg6J9gALmfq8oYlMDikxpkET6VZ61AUmQPwVLauB+Hr0qo5BOAKI/0EtB9GB9GwPwHAL1AG15JpBqQNARXkGlCbLDMbRntGoA0a8AwACDAA8jrVLWIU9BAAAAABJRU5ErkJggg==);}
  [role="toolbar"] .pack-icon-delete {background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NDA1RDQxQUE3MjA2ODExODhDNkE3NjAxQjM4MUZDNSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDOUMyRkJBM0REODYxMUUxQjVDNEE4QjczREVCRENENCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDOUMyRkJBMkREODYxMUUxQjVDNEE4QjczREVCRENENCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjk4MjBGQzc5QjkyMDY4MTE4OEM2QjcwQTAwNDE1NjQ1IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjg0MDVENDFBQTcyMDY4MTE4OEM2QTc2MDFCMzgxRkM1Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+PB4wWgAAANFJREFUeNpi/P//P8NAAsZRBww1B7ACsSMQuwCxJhBzAPEPIL4OxHuAeD8Q/6amA2SB2AyImaB8OSDuAeIdQHwASZ0HEDsAcQkQP4KK/QPiU0D8GK8LQA7AgQOA+Od/ygBIvx8eO/CGwB0gVqZCNIPMUSUnCt5RMa0JDdlcsBuJ7YrGJ1bMlRIHIEsyovGJFWPEZwETwwCDUQeMOmDUAaMOGHXAkHJAGgViVKkNya7yKQmBkxRafojSEOACYlNoc5xU8BvqgR+jPaNRB+ADAAEGAKG8wdkMfRTuAAAAAElFTkSuQmCC);}

  html, body {width:100%; height:100%;}
  html, body, #gmap {margin:0; padding:0; font-family:sans-serif;}
  #gmap {position: absolute; top:0; left:0; width:100%; bottom:66px;}
  #geoButton {position:absolute; bottom:15px; right:15px; width:40px; height:40px;}
  #status {position:absolute; top:5px; right:5px; width:132px; height:130px; padding-left:5px; font-size:9pt; line-height:150%; color:white; font-weight:bold; background-color:black; overflow:hidden; opacity:0.7;}

Die eigentliche Arbeit wird vom JavaScript ausgeführt, ich habe mich hier bemüht den Code kurz zu halten und keine weiteren Bibliotheken einzubinden um das Projekt überschaubar zu halten.

app.js JavaScript (9,34 kByte) 22.03.2020 22:34
/*jslint indent: 2, white: true, browser: true, devel: true */
/*global navigator,google,Blob,URL */
'use strict';
function App() {
  this.body = null; // html body element
  this.gmap = null; // html div wrapper for google map
  this.stat = null; // html div for status output
  this.btnMark = null; // btml button for start / stop tracking
  this.btnCenter = null; // html button for start / stop center actual position
  this.wakeLock = null; // geolocation wakelock
  this.watchID = null; // geolocation watch id
  this.counter = 0; // geolocation watch position counter
  this.map = null; // google map
  this.marker = null; // google marker
  this.poly = null; // google maps polyline to display track
  this.lastPos = {}; // last position to detect changes
  this.marks = []; // marks cache send to map
  this.track = []; // full track
  this.gps = null; // xml-File
}
App.prototype = {
  // start tracking
  start: function () {
    function formatNum (s, n) {
      s = String(s);
      while (s.length < n) {
        s = "0" + s;
      }
      return s;
    }
    function formatDeg(n) {
      var deg = Math.floor(n), minutes, seconds, cents;
      n = (n - Math.floor(n)) * 60;
      minutes = Math.floor(n);
      n = (n - Math.floor(n)) * 60;
      seconds = Math.floor(n);
      n = (n - Math.floor(n)) * 100;
      cents = Math.floor(n);
      return deg + "°" + formatNum(minutes, 2) + "'" + formatNum(seconds, 2) + "." + formatNum(cents, 2) + '"';
    }
    var self = this;
    this.btnMark.className = "pack-icon-mark active";
    this.stat.innerHTML = 'get it ...';
    this.poly.setMap(this.map);
    this.lastPos = {'lat': 0, 'lon': 0};
    this.wakeLock = navigator.requestWakeLock('gps');
    this.watchID = navigator.geolocation.watchPosition(
      function (pos) {
        var lat, lon, alt, speed, posMark, latlng, path;
        ++self.counter;
        lat = pos.coords.latitude;
        lon = pos.coords.longitude;
        alt = pos.coords.altitude;
        speed = pos.coords.speed;
        if (((self.lastPos.lat !== lat) || (self.lastPos.lon !== lon)) && (pos.coords.accuracy < 32)) {
          self.marks.push({'lat': lat, 'lon': lon});
          self.lastPos = {'lat': lat, 'lon': lon, 'alt': alt, 'speed': speed, 'accuracy': pos.coords.accuracy};
          self.track.push({'lat': lat, 'lon': lon, 'alt': alt, 'ts': pos.timestamp});
        }
        if (!document.hidden && (self.marks.length > 0)) {
          path = self.poly.getPath();
          while (self.marks.length > 0) {
            posMark = self.marks.shift();
            lat = posMark.lat;
            lon = posMark.lon;
            latlng = new google.maps.LatLng(lat, lon);
            path.push(latlng);
          }
          if (latlng) {
            self.marker.setPosition(latlng);
            self.marker.setVisible(true);
            if (self.btnCenter.className === "pack-icon-move active") {
              self.map.panTo(latlng);
            }
          }
        }
        self.stat.innerHTML = "lat: " + ((self.lastPos.lat === null) ? "no lat" : formatDeg(Math.abs(self.lastPos.lat)) + (self.lastPos.lat < 0 ? "S" : "N")) + "<br />" +
                              "lon: " + ((self.lastPos.lon === null) ? "no lon" : formatDeg(Math.abs(self.lastPos.lon)) + (self.lastPos.lon < 0 ? "W" : "E")) + "<br />" +
                              // Referenzelipsoid WGS84 + Quasigeoid height (47.5)
                              "alt: " + ((self.lastPos.alt === null) ? "no alt" : Math.round(self.lastPos.alt - 47.5) + "m NN") + "<br />" +
                              "speed: " + ((self.lastPos.speed !== null && ! isNaN(self.lastPos.speed)) ? (self.lastPos.speed * 3.6).toFixed(0) + "km/h" : "no speed") + "<br />" +
                              "accuracy: ±" + self.lastPos.accuracy + "m" + "<br />" +
                              "count: " + self.track.length + "/"+ self.counter;
      },
      function (error) {
        //self.btnMark.className = "pack-icon-mark";
        ++self.counter;
        self.stat.innerHTML = 'error' + '<br />' + 'try to get it ...';
      },
      {
        enableHighAccuracy: true,
        maximumAge: 3000,
        timeout: 3000
      }
    );
    navigator.vibrate(200);
  },
  // stop tracking
  stop: function () {
    this.btnMark.className = "pack-icon-mark";
    navigator.geolocation.clearWatch(this.watchID);
    this.wakeLock.unlock();
    this.marker.setVisible(false);
  },
  // clear tracking history
  clear: function () {
    var path = this.poly.getPath();
    path.clear();
    this.track = [];
    this.marks = [];
    this.counter = 0;
  },
  // save track
  save: function () {
    var self = this, dateStr = new Date().toISOString(), gpx = [], blob, sdcard, request;
    gpx.push('<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n');
    gpx.push('<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="www.gocher.me">\n');
    gpx.push('<metadata><link href="http://www.gocher.me"><text>gocher.me</text></link>\n');
    gpx.push('<time>' + dateStr + '</time></metadata>\n');
    gpx.push('<trk>\n');
    gpx.push('  <trkseg>\n');
    this.track.forEach(
      function (pos, i) {
        gpx.push('    <trkpt lat="' + pos.lat + '" lon="' + pos.lon + '">' +
                    ((pos.alt !== undefined) ? ('<ele>' + pos.alt + '</ele>') : '') +
                    '<time>' + new Date(pos.ts).toISOString() + '</time>' +
                 '</trkpt>\n');
      });
    gpx.push('  </trkseg>\n');
    gpx.push('</trk>\n');
    gpx.push('</gpx>\n');
    blob = new Blob(gpx, {'type': 'application/gpx+xml'});
    sdcard = navigator.getDeviceStorage("sdcard");
    request = sdcard.addNamed(blob, 'tracks/' + dateStr.replace('/', '-').replace(':', '-') + '.gpx');
    request.onsuccess = function () {
      var name = this.result;
      alert('File "' + name + '" successfully wrote on the sdcard storage area');
    };
    // An error typically occur if a file with the same name already exist
    request.onerror = function () {
      if (this.error.name === 'SecurityError') {
        // fallback download
        blob.name = dateStr.replace('/', '-').replace(':', '-') + '.gpx';
        self.stat.appendChild(document.createElement('br'));
        var elem = document.createElement('a'),
            gpxUrl = URL.createObjectURL(blob);
        elem.setAttribute('href', gpxUrl);
        elem.setAttribute('download', blob.name);
        self.stat.appendChild(elem);
        elem.appendChild(document.createTextNode('download'));
      } else {
        alert('Unable to write the file: ' + this.error.name);
      }
    };
  },
  // display toolbar
  addToolbar: function () {
    function addButton(className, caption, ul) {
      var li, btn;
      li = document.createElement('li');
      ul.appendChild(li);
      btn = document.createElement('button');
      li.appendChild(btn);
      btn.className = className;
      btn.appendChild(document.createTextNode(caption));
      return btn;
    }
    var self = this, toolbar, ul, btnDelete, btnShare;
    toolbar = document.createElement('div');
    toolbar.setAttribute('role', "toolbar");
    this.body.appendChild(toolbar);
    ul = document.createElement('ul');
    toolbar.appendChild(ul);
    btnDelete = addButton("pack-icon-delete", "Delete", ul);
    btnDelete.onclick = function () {
      self.clear();
    };
    ul = document.createElement('ul');
    toolbar.appendChild(ul);
    this.btnMark = addButton("pack-icon-mark", "Mark", ul);
    this.btnMark.onclick = function () {
      if (self.btnMark.className === "pack-icon-mark") {
        self.start();
      } else {
        self.stop();
      }
    };
    this.btnCenter = addButton("pack-icon-move", "Move", ul);
    this.btnCenter.onclick = function () {
      if (self.btnCenter.className === "pack-icon-move") {
        self.btnCenter.className = "pack-icon-move active";
      } else {
        self.btnCenter.className = "pack-icon-move";
      }
    };
    btnShare = addButton("pack-icon-share", "Share", ul);
    btnShare.onclick = function () {
      self.save();
    };
  },
  // initialize app
  init: function () {
    this.body = document.getElementsByTagName('body')[0];
    this.gmap = document.createElement('div');
    this.gmap.setAttribute('id', 'gmap');
    this.body.appendChild(this.gmap);

    this.stat = document.createElement("div");
    this.stat.setAttribute('id', 'status');
    this.body.appendChild(this.stat);
    this.addToolbar();
    // Google Maps
    this.map = new google.maps.Map(
      this.gmap, {
        zoom: 17,
        zoomControl: false,
        streetViewControl: false,
        scrollwheel: false,
        mapTypeControl: true,
        keyboardShortcuts: false,
        mapMaker: false,
        noClear: true,
        overviewMapControl: false,
        rotateControl: false,
        disableDefaultUI: true,
        center: new google.maps.LatLng(51.528710, 6.289250),
        mapTypeId: google.maps.MapTypeId.TERRAIN
      }
    );
    var symbol = {
      path: 'M0 0 a4 4 0 1 1 0 0.0001 z',
      fillColor: 'red',
      fillOpacity: 0.6,
      scale: 1,
      strokeColor: 'black',
      strokeWeight: 1
    };
    this.marker = new google.maps.Marker({
      position: new google.maps.LatLng(0, 0),
      map: this.map,
      icon: symbol
    });
    this.poly = new google.maps.Polyline({
      strokeColor: '#FF0000',
      strokeOpacity: 0.5,
      strokeWeight: 3
    });
    this.start();
  }
};

window.addEventListener('DOMContentLoaded', function() {
  var app = new App();
  app.init();
});

Kontakt

Udo Schmal
Udo Schmal

Udo Schmal
Softwareentwickler
Ellerndiek 26
24837 Schleswig
Schleswig-Holstein
Germany




+49 4621 9785538
+49 1575 0663676
+49 4621 9785539
SMS
WhatsApp

Google Maps Profile
Instagram Profile
vCard 2.1, vCard 3.0, vCard 4.0

Service Infos

CMS Info

Product Name:
UDOs Webserver
Version:
0.5.1.68
Description:
All in one Webserver
Copyright:
Udo Schmal
Compilation:
Sat, 9. Mar 2024 07:54:53

Development Info

Compiler:
Free Pascal FPC 3.3.1
compiled for:
OS:Linux, CPU:x86_64

System Info

OS:
Ubuntu 22.04.4 LTS (Jammy Jellyfish)

Hardware Info

Model:
Hewlett-Packard HP Pavilion dm4 Notebook PC
CPU Name:
Intel(R) Core(TM) i5-2430M CPU @ 2.40GHz
CPU Type:
x86_64, 1 physical CPU(s), 2 Core(s), 4 logical CPU(s),  MHz