Overview

  • Development tips (or: Be Smart)
  • Simple improvements (or: Be Savvy)
  • Advanced features (or: Be Awesome)

Be smart

Tools for maps development.

Maybe this is overkill

Static Maps

The Static Maps API may be all you need.

http://maps.googleapis.com/maps/api/staticmap
           ?center=-37.819527,145.007102&zoom=17
           &markers=-37.819582,145.007062
           &size=1200x300&sensor=false

But I'm going on a beer crawl!

But I'm going on a beer crawl!

Static Maps support many visual elements

http://maps.googleapis.com/maps/api/staticmap?
      center=-37.818429,145.008931&zoom=15&size=600x300
      &markers=color:yellow|label:S|-37.819495,145.007077
      &markers=color:red|label:G|-37.816582,145.012043
      &markers=color:blue|label:R|-37.816531,145.013309
      &path=color:0x00800080|weight:8
          |-37.81943,145.00708|-37.82003,145.01274|-37.81655,145.01337|-37.81645,145.01209
      &sensor=false

My smartphone has too many pixels!

My smartphone has too many pixels!

Add the scale parameter.

http://maps.googleapis.com/maps/api/staticmap?
      center=-37.818429,145.008931&zoom=15&size=600x300
      &markers=color:yellow|label:S|-37.819495,145.007077
      &markers=color:red|label:G|-37.816582,145.012043
      &markers=color:blue|label:R|-37.816531,145.013309
      &path=color:0x00800080|weight:8
          |-37.81943,145.00708|-37.82003,145.01274|-37.81655,145.01337|-37.81645,145.01209
      &scale=2
      &sensor=false

But I really want Street View

But I really want Street View

The Street View Image API allows static views of SV Imagery.

http://maps.googleapis.com/maps/api/streetview
           ?size=600x500&location=-37.816537,145.012565
           &heading=-174.47667368404316
           &pitch=-3.7044103524331756
           &fov=70
           &sensor=false
      

Brewery data from my server.

HTTP Performance

My maps application needs to fetch a bunch of data.

var xhr = new XMLHttpRequest();
for (var i = 0; i < breweries.length; ++i) {
  breweryId = breweries[i];
  xhr.open('GET', breweryId + '.xml', false);
  xhr.send();
  myData.xml = xhr.responseXML;
}

HTTP Performance

Loading the breweries data one-by-one can be problematic.

HTTP Performance

Better to have a handler that can bundle responses:

xhr = new XMLHttpRequest();
breweryIds = [];
for (var i = 0; i < breweries.length; ++i) {
  breweryIds.push(breweries[i]);
}
idString = breweryIds.join(',');
xhr.open('GET', 'breweryHandler?ids=' + idString, false);
xhr.send();
process(xhr.responseXML);

HTTP Performance

We can load everything with one request.

HTTP Performance

Keep in mind:

  • URL length
  • Size of responses
  • Processing on the server side

Closure Compiler

  • Catches syntax and semantic errors
  • Provides types for stricter programming guarantees
  • Optionally obfuscates code for smaller downloads

Closure Compiler Example

function MyApp(one, two) {
  this.values_ = {
    un: one,
    deux: too,
  };
}
window['app'] = MyApp();
broken.js:5: ERROR - Parse error. Internet Explorer has a non-standard intepretation of trailing commas.
Arrays will have the wrong length and objects will not parse at all.
  };
   ^
1 error(s), 0 warning(s)

Closure Compiler Example

function MyApp(one, two) {
  this.values_ = {
    un: one,
    deux: too
  };
}
window['app'] = MyApp('a');
broken.js:4: ERROR - variable too is undeclared
    deux: too
         ^
broken.js:2: WARNING - dangerous use of the global this object
  this.values_ = {
  ^
1 error(s), 0 warning(s)

Closure Compiler Example

/**
 * @param {string} one The first parameter.
 * @param {number} two The second parameter.
 * @constructor */
function MyApp(one, two) {
  this.values_ = {un: one, deux: two};
}
window['app'] = MyApp('a');
broken.js:7: WARNING - Constructor function (new:MyApp, string, number): undefined should be
called with the "new" keyword
window['app'] = MyApp('a');
                ^
broken.js:7: WARNING - Function MyApp: called with 1 argument(s). Function requires at least
2 argument(s) and no more than 2 argument(s).
window['app'] = MyApp('a');
                ^
0 error(s), 2 warning(s), 100.0% typed

Closure Compiler Example

/**
 * @param {string} one The first parameter.
 * @param {number} two The second parameter.
 * @constructor
 */
function MyApp(one, two) {
  this.values_ = {un: one, deux: two};
}
window['app'] = new MyApp('a', 'b');
broken.js:9: WARNING - actual parameter 2 of MyApp does not match formal parameter
found   : string
required: number
window['app'] = new MyApp('a', 'b');
                               ^
0 error(s), 1 warning(s), 100.0% typed

Closure Compiler Example

/**
 * @param {string} one The first parameter.
 * @param {number} two The second parameter.
 * @constructor
 */
function MyApp(one, two) {
  this.values_ = {un: one, deux: two};
}
window['app'] = new MyApp('a', 2);

becomes

window.app=new function(){}("a",2);
      

Closure Compiler and the Maps API

function init() {
  var map = new google.maps.Map(document.getElementById('map'), {
    center: new google.maps.LatLng(0, 0),
    zoom: 3,
    mapTypeId: google.maps.MapTypeId.ROADMAP
  });
}

Closure Compiler and the Maps API

> java -jar compiler.jar --js map-app.js --warning_level VERBOSE

map-app.js:2: WARNING - Property Map never defined on google.maps
  var map = new google.maps.Map(document.getElementById('map'), {
                ^
map-app.js:2: WARNING - Property maps never defined on google
  var map = new google.maps.Map(document.getElementById('map'), {
                ^
map-app.js:3: WARNING - Property LatLng never defined on google.maps
    center: new google.maps.LatLng(0, 0),
                ^
map-app.js:3: WARNING - Property maps never defined on google
    center: new google.maps.LatLng(0, 0),
                ^
map-app.js:5: WARNING - Property MapTypeId never defined on google.maps
    mapTypeId: google.maps.MapTypeId.ROADMAP
               ^
map-app.js:5: WARNING - Property ROADMAP never defined on google.maps.MapTypeId
    mapTypeId: google.maps.MapTypeId.ROADMAP
               ^
map-app.js:5: WARNING - Property maps never defined on google
    mapTypeId: google.maps.MapTypeId.ROADMAP
               ^
0 error(s), 7 warning(s), 58.6% typed

Closure Compiler and the Maps API

Externs to the rescue!

Download Maps API externs from closure-compiler.googlecode.com.

> java -jar compiler.jar --js map-app.js --warning_level VERBOSE --externs google_maps_api_v3.js

Closure Compiler and the Maps API

function init(latitude, longitude, zoom, mapTypeId) {
  var map = new google.maps.Map(document.getElementById('map'), {
    center: new google.maps.LatLng(latitude, longitude),
    zoom: zoom,
    mapTypeId: mapTypeId
  });
}
window['init'] = init;

becomes


function init(a,b,c,d){new google.maps.Map(document.getElementById("map"),{center:new
google.maps.LatLng(a, b),zoom:c,mapTypeId:d})}window.init=init;

Closure Compiler

Testing

  • This is important!
  • Unit testing
    • Tests a single component in isolation.
  • Automated integration testing
    • Tests the application as a whole.

Testing

Be savvy

Going from good to great

Drag this marker and the circle moves with it.

Key-Value Observation

Centering the circle without MVCObject:

var marker = new google.maps.Marker({
  position: new google.maps.LatLng(-25, 133),
  draggable: true,
  map: map
});
var circle = new google.maps.Circle({
  radius: 1000000,
  map: map
});
google.maps.event.addListener(marker, 'position_changed', function() {
 circle.setCenter(marker.getPosition());
);
circle.setCenter(marker.getPosition());

Key-Value Observation using MVCObject

  • The MVCObject class allow for easy key-value observation.
  • Bind Circle's "center" to Marker's "position" to keep them in sync.
var marker = new google.maps.Marker({
  position: new google.maps.LatLng(-25, 133),
  draggable: true,
  map: map
});
var circle = new google.maps.Circle({
  radius: 1000000,
  map: map
});
circle.bindTo('center', marker, 'position');

Custom map type control binds to map's mapTypeId.

Autocomplete Widget

Who has seen an address entry form like this?

Autocomplete Widget

Isn't this better?

Autocomplete Widget

  • Need to include the places library.
var autocomplete = new google.maps.places.Autocomplete(inputElement);
google.maps.event.addListener(autocomplete, 'place_changed', function() {
  var place = autocomplete.getPlace();
  ...
});

Autocomplete + Maps

Geocoding

When geocoding a fixed set of addresses, you should use the Geocoding web service and cache these results instead of geocoding dynamically in the client.

http://maps.googleapis.com/maps/api/geocode/json?address=Bondi+Beach+Australia&sensor=false
http://maps.googleapis.com/maps/api/geocode/json?address=Byron+Bay+Australia&sensor=false
http://maps.googleapis.com/maps/api/geocode/json?address=Bells+Beach+Australia&sensor=false
      

Custom Markers

Custom Markers — Spriting

Improve performance by combining your images into one (a sprite).

var icon = new google.maps.MarkerImage(
    'images/beachflag.png',
    new google.maps.Size(20, 32),   // The size of the icon.
    new google.maps.Point(0, 0),    // The icon is at an offset of (0,0) in the sprite.
    new google.maps.Point(0, 32)       // The offset of the "tip" of the icon.
);

var shadow = new google.maps.MarkerImage(
    'images/beachflag.png',
    new google.maps.Size(35, 32),  // The size of the shadow.
    new google.maps.Point(39, 0),  // The shadow is at an offset of (39,0) in the sprite.
    new google.maps.Point(0, 32)      // The offset of the "tip" of the shadow.
);

Custom Markers — Scaling

3 markers (with shadows); 1 image file.

Custom Markers — Scaling

Show the same image at different sizes.

var icon = new google.maps.MarkerImage(
    'images/beachflag.png',
    new google.maps.Size(20, 34),   // Original display size.
    new google.maps.Point(0, 0),    // Offset within the sprite.
    new google.maps.Point(10, 34)   // anchor
);

var doubled = new google.maps.MarkerImage(
    'images/beachflag.png',
    new google.maps.Size(20 * 2, 34 * 2),    // Display size of image is doubled.
    new google.maps.Point(0 * 2, 0 * 2),     // Offset in the sprite is doubled.
    new google.maps.Point(10 * 2, 34 * 2),   // Anchor is doubled.
    new google.maps.Size(57 * 2, 34 * 2)     // Entire size of image is doubled.
);

Marker Symbols

Custom Overlays

Full control over the DOM elements. Redrawn only when required.

Custom Overlays

Inherits from google.maps.OverlayView.

Implements three methods:

  • onAdd – adds element to the DOM using the MapPanes
  • draw – positions the overlay by converting a LatLng to a coordinate within its pane
  • onRemove – removes the element from the DOM

Custom Overlays

Constructor

function FlagMarker(label, icon, location) {
  this.location_ = location;
  this.icon_ = icon;
  this.label_ = label;
}
FlagMarker.prototype = new google.maps.OverlayView();

Custom Overlays

onAdd()

FlagMarker.prototype.onAdd = function() {
  this.element_ = document.createElement('div');
  this.element_.className = 'flag';
  this.element_.appendChild(document.createTextNode(this.label_));

  this.getPanes().floatPane.appendChild(this.element_);
};

Custom Overlays

draw()

FlagMarker.prototype.draw = function() {
  // Convert from LatLng to coordinate within the map's pane.
  var position = this.getProjection().fromLatLngToDivPixel(this.location_);
  this.element_.style.top = Math.round(position.y) + 'px';
  this.element_.style.left = Math.round(position.x) + 'px';
};

Custom Overlays

onRemove()

FlagMarker.prototype.onRemove = function() {
  this.element_.parentNode.removeChild(this.element_);
  this.element_ = null;
};

The beautiful sights of Sydney

The beautiful sights of Sydney

Heading not set.

Street View Orientation

Naive approach

var sydneyHarbour = new google.maps.LatLng(-33.849509,151.213557);
var panoramaOptions = {
  position: sydneyHarbour,
};
var myPano = new
  google.maps.StreetViewPanorama(document.getElementById("map"), panoramaOptions);
myPano.setVisible(true);

Street View Orientation

Heading is hard-coded

Street View Orientation

Hard-coded approach

var sydneyHarbour = new google.maps.LatLng(-33.849509,151.213557);
var panoramaOptions = {
  position: sydneyHarbour,
  pov: {
    heading: 190,
    pitch: 0,
    zoom: 1
  }
};
var myPano = new
  google.maps.StreetViewPanorama(document.getElementById("map"), panoramaOptions);
myPano.setVisible(true);

Street View Orientation

Heading is calculated automatically.

Street View Orientation

Automatically calculate the heading

var heading =
  google.maps.geometry.spherical.computeHeading(origin, destination);
myPano.setOptions({
  position: origin,
  pov: {
    heading: heading,
    pitch: 0,
    zoom: 1
  }
});

Street View Orientation

Heading is taken from link data.

Street View Orientation

google.maps.event.addListener(myPano, 'links_changed', function() {
  var links = myPano.getLinks();
  var minDiff = 360;
  var heading = 0;
  for (var i = 0, link; link = links[i]; ++i) {
    var diff = Math.abs(link.heading - targetHeading);
    diff = Math.min(diff, 360 - diff)
    if (minDiff > diff) {
      heading = link.heading;
      minDiff = diff;
    }
  }
});

Be awesome

Building powerful applications.

James and his tin-foil roof

Demo details

  • places.Autocomplete
    • Adds suggest functionality to address entry fields and returns the LatLng of the selected address.
  • MaxZoomService.getMaxZoomAtLatLng
    • Returns the zoom level of the best available satellite imagery at the specified LatLng.
  • drawing.DrawingManager
    • Allows the user to draw a polygon, or other shapes, on the map.
  • geometry.spherical.computeArea
    • Computes the area of the path drawn on the map.
  • geometry.spherical.computeLength
    • Computes the length of the path drawn on the map.

Chris wants to ride his fixie to work

Demo details

  • places.Autocomplete
    • Adds suggest functionality to address entry fields.
  • DirectionsService.route
    • Returns driving directions between the two locations in the address fields.
  • DirectionsRenderer.setDirections
    • Renders the route on the map.
  • ElevationService.getElevationAlongPath
    • Returns information about the elevation at points along the path shown on the map.
  • MarkerImage
    • Allows for customization of the marker icon.

Dave's home needs improvement.

Dave wants to stop at Bunnings on his way home from work

Get Dave to Bunnings!

But there are many Bunnings from which to choose

Demo details

  • PlacesService.search
    • Returns all Bunnings locations in a particular viewport.
    • Render these results on the map.
  • DistanceMatrix.getDistanceMatrix
    • Computes the distance between a set of origins (home and work) and a set of destinations (Bunnings locations).
    • Returns an array of distances from work to each of the Bunnings and an array of distances from home to each of the Bunnings.
    • Add these together and find the minimum total distance.
  • DirectionsService.route
    • Returns driving directions from work to home, stopping over at the Bunnings found by the above computation.
  • DirectionsRenderer.setDirections
    • Renders the route on the map and in the panel.

Summary

  • Follow good programming practices. (Yes, even in JS!)
  • Create awesome apps: use features together.
  • Every address field on the web should use Autocomplete!

Questions?

Thank You!