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

Google Maps und Tracking

Screenshot der aktiven Appzoom

Screenshot der aktiven App
und die erzeugten GPX-Daten (473,5 kByte) 24.04.2016 07:51

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) 24.04.2016 08:36
{
  "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 (550 bytes) 24.04.2016 08:19
<!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" />
    <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) 24.04.2016 08:19
  [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) 24.04.2016 09:14
/*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();
});

Autor: , veröffentlicht: , letzte Änderung:

Kontakt

Copyright / License of sources

Copyright (c) 2007-2017, Udo Schmal <udo.schmal@t-online.de>

Permission to use, copy, modify, and/or distribute the software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Service Infos

CMS Info
UDOs Webserver

0.3.1.24

All in one Webserver

Udo Schmal

Sa, 21 Okt 2017 00:30:10
Development Info
Lazarus LCL 1.9.0.0

Free Pascal FPC 3.1.1

OS:Win64, CPU:x86_64
Hardware Info
Precision WorkStation T3500

Intel(R) Xeon(R) CPU W3530 @ 2.80GHz

x86_64, 1 physical CPU(s), 4 Core(s), 8 logical CPU(s), 2800 MHz