Freitag, 21. Juni 2013

Geocoding location names with Geoff

What is geocoding?

Imagine you have some data in a database that (logically) could be visualized on a geographical map, but you are missing the geographic coordinates (typically latitude/longitude pairs), i.e. you only have the object's logical name:
  • city names
  • postal addresses, street names
  • points of interest: restaurants and bars, libraries, shops, etc.
In order to get the coordinates, you could look up those names in a search engine and show it on a map like Goolge Maps does, then, grab its coordinates and assign them to the objects.
This process is called geocoding: assigning geographical coordinates to a location name.

Querying geocoding services

As you guess, manually geocoding location names can take a lot of time if you have a bunch of objects.
Luckily, this procedure can be automated. There are (public) geocoding services available that can be used to automatically lookup location names and get a geocoded equivalent:
  • Commercial geocoding APIs/webservices providers (Google, Microsoft, Nokia, etc.)- may be 'free' but constrained depending on how many queries you execute
    • Obtain a proper key from those providers
  • OpenStreetMap's geocoding (public) API/webservice - not to be queried in productional environments
    • Host the OpenStreetMap planet files yourself or 
    • consume from a (commercial) provider that has done this already

Using Geoff's API to query a geocoding service

The Geoff project (which is a proposed project at the Eclipse Locationtech IWG)  has a simple interface (IGeocodingService) that acts as a facade/frontend to the public/commercial geocoding services:

public List executeQuery(String query);

It takes a query string as parameter (a location name, for example) and returns a list of potential matches of type POI (point of interest) which has a LongLat (geographic coordinates) and service specific properties.
The result is a List of best matches of geocoded POIs as in general it is not always possible to uniquely identify the location name when automatic geocoding is done. In such cases, you will have to check and resolve the best match.

Currently, there is a default implementation that queries OpenStreetMaps's Nominatim to geocode location names. Other implementations may follow.

Visualizing geocoded location names with Geoff

Once you have finished the geocoding process, you can use Geoff to visualize the POIs on a map (in your Eclipse RCP application). The following steps will walk you through a simple use case...

Setup the map

Geoff has an EMF ecore meta model to define a map object.

1. create the root map container
GeoMap map = MapFactory.eINSTANCE.createGeoMap();
MapOptions opts = MapFactory.eINSTANCE.createMapOptions();
opts.setNumZoomLevels(16);
map.setOptions(opts);

2. create an OpenStreetMap base layer
OSM layer = LayersFactory.eINSTANCE.createOSM();
LayerOptions opts = LayersFactory.eINSTANCE.createLayerOptions();
opts.setIsBaseLayer(true);
layer.setOptions(opts);
map.getLayers().add(layer);

3. create a second layer that will contain some POIs (Markers)
Markers markersLayer = LayersFactory.eINSTANCE.createMarkers();markersLayer.setName("Markers");map.getLayers().add(markersLayer);

4. a) obtain a list of location names from some backend data source
String[] cities = new String[] { "Berlin", "Paris", "London", "Madrid", "Rom" };

4. b) geocode the location names and add to second layer
IGeocodingService geo = IGeocodingService.Util.getFirstFound();
for (String city : cities) { List results = geo.executeQuery(city); if (!results.isEmpty()) { POI poi = results.get(0); Marker marker = MarkersFactory.eINSTANCE.createMarker(); LonLat lonLat = TypesFactory.eINSTANCE.createLonLat(); lonLat.setLon((float) poi.getLatLon().getLon()); lonLat.setLat((float) poi.getLatLon().getLat()); marker.setLonLat(lonLat); markersLayer.getMarkers().add(marker); }}

Setup the UI container (this part is SWT/E4 specific)

@PostConstruct
public void createUI(Composite parent) {
GeoMap map = ... //map object populated in first step
GeoffMapComposite mapComposite = new GeoffMapComposite(parent, SWT.NONE);
mapComposite.setMap(map, "map");
}

Preview

The output should be similar to the following screenshot...


How was that done?

GeoffMapComposite is a wrapper for SWT's Browser widget. It runs the JavaScript framework OpenLayers. Once you call GeoffMapComposite.setMap(), it will use Geoff's code generation functionality to generate OpenLayers specific JavaScript code at run-time and execute it in the wrapped Browser widget.