Position and distance tracker with the GeoLocation API and Google Maps API

In Chapter 2, Practical Dart, we learned about the dart:js library. We saw that we can proxy JavaScript objects using the JsObject class in Dart. We're going to use the JsObject class a lot right now because this app will use the Google Maps API, which is a JavaScript API that we'll control from Dart.

Note

There's a Dart wrapper (https://pub.dartlang.org/packages/google_maps) for the Google Maps API, which you would probably prefer in a real-world app, but for demonstration purposes, we'll write everything by ourselves.

First, include the Google Maps API before including any Dart script:

<script src="http://maps.../api/js?libraries=geometry"></script>
<script type="application/dart" src="main.dart"></script>

Note the ?libraries=geometry parameter in the URL. This tells Google to include an additional library in the API, which we need in order to calculate the distance between two GPS locations.

We'll initialize Google Map and assign Dart proxies to JavaScript objects:

import 'dart:html';
import 'dart:js';
import 'dart:async';

void main() {
  double totalDistance = 0.0;

  // My last position as a JavaScript LatLng object.
  JsObject lastPos;
  JsObject mapsApi = context['google']['maps'];

  // Reference to an object that we'll instantiate multiple times.
  JsObject jsLatLng = mapsApi['LatLng'];
  // We'll keep listener subscription in a variable
  // because we want to be able to unsubscribe.
  StreamSubscription<Geoposition> locSubscr = null;
  // Proxy object to computeDistanceBetween().
  JsFunction jsDistance =
      mapsApi['geometry']['spherical']['computeDistanceBetween'];
  
  HtmlElement distanceElm, distanceFromLastElm;
  HtmlElement statusElm, mapCanvas;
  ButtonElement btnStart, btnStop;

  /* Assign HTML elements to Dart variables. */
  
  // Convert Dart Map into a JavaScript object.
  JsObject mapOptions = new JsObject.jsify({
    "zoom": 2,
      "center": new JsObject.jsify({ 'lat': 20.0, 'lng': -30.0}),
      "mapTypeId": mapsApi['MapTypeId']['ROADMAP']
  });
  
  // Init Google Map.
  var map = new JsObject(mapsApi['Map'], [mapCanvas, mapOptions]);
  
  // Method called after receiving the first coordinates.
  void init(Coordinates c) {
    lastPos = new JsObject(jsLatLng, [c.latitude, c.longitude]);
    
    // Call two methods on JsObject proxy.
    map.callMethod('setCenter', [lastPos]);
    map.callMethod('setZoom', [16]);
    
    // Draw a circle to the map that marks my initial position
    // by calling JavaScript object constructor.
    new JsObject(mapsApi['Circle'], [new JsObject.jsify({
      'strokeColor': '#FF0000', 'strokeOpacity': 0.8,
      'strokeWeight': 2, 'fillColor': '#FF0000',
      'fillOpacity': 0.8, 'map': map,
      'center': lastPos, 'radius': 10
    })]);
  }

  /* … */
}

Now when we want to know our current position, we can use:

window.navigator.geolocation.getCurrentPosition()

This code returns a Future<Geoposition> object. We can also listen to every change in position by subscribing to the watchPosition() stream:

window.navigator.geolocation.watchPosition()

Unfortunately, getCurrentPosition() doesn't work properly in every browser, so we have to be able to handle everything in watchPosition() alone.

Note

Currently, there's a bug in Dartium that causes the GeoLocation API to throw an error when used, so in Dart SDK 1.9, the only way to run this example is to compile it to JavaScript with dart2js.

We also add two buttons, start and stop, that bind and unbind listeners, respectively:

void main() {

  /* … */

  void updateStatus() {
    statusElm.text = (locSubscr == null ? 'Stopped' : 'Running'),
  }
  updateStatus();
  
  // Bind event listeners.
  void bindListeners() {
    // Get current location.
    window.navigator.geolocation.getCurrentPosition().then((p) {
      print(p.coords.latitude);
      print(p.coords.longitude);
      
      init(p.coords);
    }, onError: (PositionError e) => print(e.toString()));
    
    // Listen to every change in position.
    // We want to keep reference to the subscription to be
    // able to cancel the subscription later.
    locSubscr = window.navigator.geolocation
        .watchPosition().listen((Geoposition pos) {
      if (lastPos == null) init(pos.coords);
      
      // Create an instance of LatLng JavaScript object.
      var currPos = new JsObject(
          jsLatLng, [pos.coords.latitude, pos.coords.longitude]);
      // Center map to my current location.
      map.callMethod('setCenter', [currPos]);
      
      var path = [lastPos, currPos];
      
      // Create a line and draw it to the map.
      var lineOptions = new JsObject.jsify({
        'strokeColor': '#0000FF',
        'strokeOpacity': 1.0,
        'strokeWeight': 3,
        'map': map
      });
      var line = new JsObject(mapsApi['Polyline'], [lineOptions]);
      line.callMethod('setPath', [new JsObject.jsify(path)]);
      
      // Convert the returned variable to double.
      // jsDistance is our [jsFunction] proxy.
      var dist = double.parse(jsDistance.apply(path).toString());
      distanceFromLastElm.text = dist.toString();
      
      totalDistance += dist;
      // Keep this position for the next event fired.
      lastPos = currPos;
      
      distanceElm.text = totalDistance.toString();
    });
  }
  
  void cancelListeners(e) {
    locSubscr.cancel();
    locSubscr = null;
    updateStatus();
  }
  btnStart.onClick.listen((Event e) {
    if (locSubscr == null) {
      bindListeners();
      updateStatus();
    }
  });
  
  btnStop.onClick.listen(cancelListeners);
}

The final app with a little CSS looks like this on iOS in Mobile Safari:

Position and distance tracker with the GeoLocation API and Google Maps API

As you can see, the GPS location is quite inaccurate, so for the real usage, we would have to use a filter (for example, the Kalman filter) to reduce the position inaccuracy.

Calling pure JavaScript API from Dart isn't that hard, but it requires a little more surrounding code. We also saw that JsObject and JsFunction are sometimes replaceable. You can choose whether you want to use JsObject.callMethod or JsFunction.apply.

We also used JsObject.jsify() from the dart:js library that takes List or Map objects as parameters and converts them into JsObject, which is basically a wrapped JavaScript object.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.42.128