Google Earth API Integration via GeoWebCache

If you are local to the Mecklenburg County NC area and use our GeoPortal a lot, you might have noticed something new lately.

If you have the Google Earth API installed, you get this in your list of overlays:

Should you be brave enough to click the Earth button, behold!

3D! Or 2.5D if you're one of those types. Complete with parcel lines and sundry crap.

One of the best things that happens at conferences are the conversations you have with other attendees. On the shuttle ride to the airport I happened to sit next to somebody that works at Google on the Fusion Tables project and had the opportunity to hit him with a few questions. One of those questions was when the Google Earth API was going to catch up to and be integrated with Google Maps API v3. I didn’t get a direct answer on that (my guess is they’re planning a WebGL replacement of the plugin down the road), but he did point me to a great script that helps integrate the two, including transferring overlays from one to the other. Perfect!

Except for one thing. It transfers every Google Maps overlay but the one I use most often - ImageMapType. GAAAA!

Fortunately, GeoWebCache can serve regionated KML tiles, and the Google Earth API supports that. A little fancy footwork and I can get our layers showing up on Google Earth in the browser.

I ended up monkeying about with the original script a lot, including tossing some things I didn’t need, so I’d recommend starting with the original as a base. Here’s basically what I did:

I made a new function called addMeckLayers and added it to the refresh function.

1
2
3
4
5
6
7
8
9
10
11
GoogleEarth.prototype.refresh_ = function() {
this.overlays_ = {};
this.flyToMapView_(true);
this.clearPlacemarks_();
this.displayCounter_++;
this.clearMoveEvents_();
this.addMapOverlays_();

// Tobin added this
this.addMeckLayers_();
};

I also declared a global folder var, which I set in the initializeEarth function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GoogleEarth.prototype.initializeEarth_ = function() {
var that = this;
google.earth.createInstance(this.earthDiv_, function(instance) {
that.instance_ = /** @type {google.earth.GEPlugin} */(instance);

// Added by Tobin Bradley. 'Sup.
folder = instance.createFolder('');
folder.setOpacity(0.7);

that.addEarthEvents_();
that.refresh_();

}, function(e) {
that.hideEarth_();
//TODO(jlivni) record previous maptype
that.map_.setMapTypeId(google.maps.MapTypeId.ROADMAP);
throw 'Google Earth API failed to initialize: ' + e;
});
};

Here’s the function. A lot of this is very specific to my app, so just use it as a baseline. I’m also rocking some jQuery in there, which won’t help if you aren’t doing so also. First I dump the folder contents. Then I loop through the layer control back on the main page, and if the layer’s active and it has a kmlnetworkpath property set in my config, I add the layer to the folder. Then I add the parcel lines (they’re always on) and add the folder the map. Bob’s your uncle.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* Added by Tobin for GeoPortal specific crap
*/
GoogleEarth.prototype.addMeckLayers_ = function() {
// clear out folder
features = folder.getFeatures()
while (features.getFirstChild()) {
features.removeChild(features.getFirstChild());
}

ge = this.getInstance();

// loop through layer control to add layers with kmlnetworkpath
$.each(overlayMaps, function(index) {
if($("#" + index).is(':checked') && overlayMaps[index].kmlnetworkpath) {
var link = ge.createLink('');
link.setHref(overlayMaps[index].kmlnetworkpath);

var networkLink = ge.createNetworkLink('');
networkLink.setLink(link);

folder.getFeatures().appendChild(networkLink);
}
});

// add parcel lines always
var link = ge.createLink('');
link.setHref('http://maps.co.mecklenburg.nc.us/geoserver/gwc/service/kml/postgis:tax_parcels.png.kml');
var networkLink = ge.createNetworkLink('');
networkLink.setLink(link);
folder.getFeatures().appendChild(networkLink);

// add folder to Google Earth
ge.getFeatures().appendChild(folder);
}

There’s more going on here and there in the app. I attach the opacity slider and layer control to Earth if it’s active. I also get the elevation/lat/lng on mouse move via:

1
2
3
4
// Mouse move event listener for coordinate display
google.earth.addEventListener(ge.getGlobe(), 'mousemove', function(event){
$("#toolbar-coords").text(event.getLongitude().toFixed(4) + " " + event.getLatitude().toFixed(4) + " Elev: " + Math.round(event.getAltitude() * 3.2808399) + "ft");
});

You can get my whole unsquished script here, but again, I’d start with the original and work from there. I’ll add it to the GeoPortal open source template when I can pry a couple of screaming projects off of my desk.

One thing to note about GeoWebCache’s KML. It doesn’t use the proxy settings from GeoServer inside the KML, it uses the the requesting host URL. That means if you’re using and Apache proxy pointing to GeoWebCache on localhost:8080, your KML will have localhost:8080 embedded. That will lead to disappointment. Your Apache config needs this to fix it:

[crayon lang=”default”]
ProxyPreserveHost On