Friday, July 26, 2013

Google Maps and Google Earth Synchronized

Inspired by Google Map Maker's visualization of updates, I created this mashup of Google Earth and Google Maps. Here, if the user pans the map (2D), the earth (3D) follows, and vice-versa.

NOTE: You'll need Google Earth Plug-in installed for this to work.

Below is a demo that is initially showing Manila, Philippines. Go ahead and pan either view and see if it works.

Loading...
Loading...

Synchronizing Earth and Maps

Using Google Earth and Google Maps is straight-forward using their JavaScript APIs. The tricky part is synchronizing them. When a user pans the map (2D), the earth (3D) should follow. Likewise, when the user pans the earth (3D), the map (2D) should follow.

My first attempt was to listen to the earth's viewchangeend event, and the map's center_changed event.

function syncEarthWithMap() {
  …
}

function syncMapWithEarth() {
  …
}
…
google.maps.event.addListener(map, 'center_changed', syncEarthWithMap);
…
google.earth.addEventListener(ge.getView(), 'viewchangeend', syncMapWithEarth);
…

Unfortunately, this resulted in an infinite loop, since the event handlers would update the view, which will trigger more events. To fix this, we'll need to remove the event handlers and restore them at a later time. The idea looks something like this:

function syncEarthWithMap() {
  …
  if (earthEventHandler.isEnabled()) {
    earthEventHandler.disableAndEnableLater();
  }
  …
  // Now that the earth event handler is disabled,
  // we can safely change it without triggering more calls.
  ge.getView().setAbstractView(lookAt);
}

function syncMapWithEarth() {
  …
  if (mapEventHandler.isEnabled()) {
    mapEventHandler.disableAndEnableLater();
  }
  …
  // Now that the map event handler is disabled,
  // we can safely change it without triggering more calls.
  map.setZoom(zoom);
  map.panTo(center);
}

Here's how the event handler would look like:

function createEventHandler(enableHandler, disableHandler, enableLater) {
  var enabled = false;
  return {
    enable: function() {
      enableHandler();
      enabled = true;
    },
    isEnabled: function() {
      return enabled;
    },
    disableAndEnableLater: function() {
      disableHandler();
      enabled = false;
      enableLater(enable);
    }
  };
}

Creating the map event handler looks something like this.

var mapEventHandler;

mapEventHandler = createEventHandler(function() {
  /* enable event handler */
  … = google.maps.event.addListener(map, 'center_changed', syncEarthWithMap);
  … = google.maps.event.addListener(map, 'bounds_changed', syncEarthWithMap);
}, function() {
  /* disable event handler */
  google.maps.event.removeListener(…);
  google.maps.event.removeListener(…);
}, function(callback) {
  /* later, enable event handler */
  google.earth.addEventListenerOnce(map, 'idle', function() {
    callback();
  });
});
mapEventHandler.enable();

The earth event handler is similar.

var earthEventHandler;

earthEventHandler = createEventHandler(function() {
  /* enable event handler */
  google.earth.addEventListener(
            ge.getView(), 'viewchangeend', syncMapWithEarth);
}, function() {
  /* disable event handler */
  google.earth.removeEventListener(
            ge.getView(), 'viewchangeend', syncMapWithEarth);
}, function() {
  /* later, enable event handler */
  …
});
earthEventHandler.enable();

Synchronizing both views can be a bit tricky. But thanks to good documentation from Google, synchronizing them can be quite fun!

While developing this, I was looking for interesting places in the Philippines that would look good on Google Earth (largely due to the presence of 3D structures). If you know of some, please let me know (include latitude and longitude if possible) via comments, and I'd add them as links that will reposition the views to those locations. Cheers!

No comments:

Post a Comment