Table of Contents
List of Figures
List of Tables
List of Examples
Table of Contents
The geocoder plug-in provides a standard mechanism to convert text, representing an address or point of interest, in a map location.
For this purpose a command "command.geocoder.GetLocationForString" is provided which allows you to do the conversion
This input string is converted to a bounding box which is intended to be zoomed to. A marker may be displayed at the center of that area.
For users of the GWT face, a widget is provided to allow users to type the string and which changes the view to the matching area. When the string is ambiguous, the user will be presented with some alternate locations to choose from.
The actual conversion is done using a pluggable list of geocoder service. Some standard implementations are provided. Either the first services which produces a match wins, or the results may be combined.
The GetLocationForStringCommand class handles a request to get a location for a string.
This string is first cut into relevant parts, for example "London, UK" may be split into parts "London" and "UK". This is handled by the SplitGeocoderStringService. The default implementation uses comma as separator and removes whitespace between the parts.
After that, each of the configured GeocoderServices is given a chance to convert the strings into a list of locations. If the geocoder service returns one location, it is considered matched. When it returns multiple locations, the search term is considered ambiguous and the locations are considered as alternatives. You can configure whether all geocoders need to be given a chance to find the location or whether it should stop as soon as one service has returned at least one location.
The geocoder services can return either an area (bounding box) or a point. When they returned a point, this is extended to an area centered around that point.
At the end, the result is prepared for return. The matching locations are combined using the configured CombineResultService. The default implementation uses the union of the area. When there were no matches, then all alternatives are returned.
The location also contains the canonical search string.
Table of Contents
How to use the geocoder plug-in.
The main access point for the functionality which is provided by this plug-in is the GetLocationForString command.
Table 2.1. GetLocationForStringCommand
GetLocationForStringCommand | |
---|---|
Registry key | command.geocoder.GetLocationForString |
Module which provides this command | geomajas-plugin-geocoder |
Request object class | org.geomajas.plugin.geocoder.command.dto.GetLocationForStringRequest |
Parameters |
|
Description | This command allows you to find a map location from a string representation. |
Response object class | org.geomajas.plugin.geocoder.command.dto.GetLocationForStringResponse |
Response values |
|
As part of other plug-ins, tests or code which has the back-end in the same VM, this can be run as in listing Example 2.1, “Usage of the geocoder command”.
Example 2.1. Usage of the geocoder command
@Autowired private CommandDispatcher commandDispatcher; @Test public void oneResultTest() throws Exception { GetLocationForStringRequest request = new GetLocationForStringRequest(); request.setCrs("EPSG:4326"); request.setLocation("booischot"); CommandResponse commandResponse = commandDispatcher.execute(GetLocationForStringRequest.COMMAND, request, null, "en"); }
For details on calling this command from inside a face, see the specific face's documentation.
To make it easier for you, the plug-in contains a widget which can be included in the GWT face.
This widget consists of a text box where the user can type the location string. On pressing <enter> or indicating the search icon, the GetLocationForString command is called. When this returns a match, the map will zoom to that area. When the search is ambiguous, a pop-up with the alternatives is displayed.
Including the widget is easy. In the initialization code for your application, add in instance of the GeocoderWidget where you want to put it. This requires a couple of parameters, the map the widget is connected with, the description and label for the widget. In listing Example 2.2, “Create GWT widget in toolbar” you see an excerpt where the widget in placed inside the map's toolbar. The widget tries to adapt itself to the currently active locale.
Example 2.2. Create GWT widget in toolbar
final MapWidget map = new MapWidget("mapGeocoderOsm", "appGeocoder"); final Toolbar toolbar = new Toolbar(map); final GeocoderWidget geocoderWidget = new GeocoderWidget(map, "description", "Geocoder"); toolbar.addMember(geocoderWidget);
Table of Contents
Configuration for the geocoder plug-in.
Make sure sure you include the correct version of the plug-in in your project. Use the following excerpt (with the correct version) in the dependencyManagement section of your project:
<dependency> <groupId>org.geomajas.plugin</groupId> <artifactId>geomajas-plugin-geocoder-all</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency>
If you are using geomajas-dep, this includes the latest released version of the caching plug-in (at the time of publishing of that version). If you want to overwrite the caching plug-in version, make sure to include this excerpt before the geomajas-dep dependency.
You can now include the actual dependency without explicit version.
Example 3.1. Plug-in dependency
<dependency> <groupId>org.geomajas.plugin</groupId> <artifactId>geomajas-plugin-geocoder</artifactId> </dependency>
If you want to use the geocoder widget for the GWT face, you need the following dependency:
Example 3.2. Plug-in GWT dependency
<dependency> <groupId>org.geomajas.plugin</groupId> <artifactId>geomajas-plugin-geocoder-gwt</artifactId> </dependency>
To use the widget, you have to include the geocoder GWT module description as in listing Example 3.3, “GWT module include geocoder”.
Example 3.3. GWT module include geocoder
<module rename-to="GeocoderExample"> <inherits name="com.smartgwt.tools.SmartGwtTools"/> <inherits name="org.geomajas.gwt.GeomajasClient"/> <inherits name="org.geomajas.plugin.geocoder.Geocoder"/> <inherits name="com.smartclient.theme.enterprise.Enterprise"/> <entry-point class="org.geomajas.plugin.geocoder.gwt.example.client.GeocoderExampleStandalone"/> </module>
When you include the geocoder dependency, you also have to configure the geocoder command or you will get an exception when the application context is built.
An example configuration looks like this:
Example 3.4. example configuration
<bean name="geocoderInfo" class="org.geomajas.plugin.geocoder.api.GeocoderInfo"> <property name="loopAllServices" value="false"/> <property name="pointDisplayWidth" value="2000" /> <property name="pointDisplayHeight" value="1000" /> <property name="geocoderServices"> <list> <bean class="org.geomajas.plugin.geocoder.service.GeonamesGeocoderService"> <property name="name" value="geonames" /> </bean> </list> </property> </bean>
To configure the geocoder plugin, you have to create a bean instantiating the GeocoderInfo class.
In the bean, you have to set the geocoderServices property to select which geocoder services you want to be used to attempt to convert the string to a location.
Other properties you may want to set:
splitGeocoderStringService: the service which should be used to split the location string into parts and to determine the order in which parts are given. Depending on this service, you may tell your users to search for "Antwerpen, BE", "BE, Antwerpen" or "Antwerpen; BE",...
combineResultService: service which combines the result when several geocoder services found a match for the location string.
loopAllServices: the command will loop all geocoder services which have been specified. Should this loop stop when one geocoder found either a match or some alternatives?
pointDisplayWidth: width in meters for the area which needs to be displayed on the map when the geocoder service returned a point. Defaults to 2000 (2km).
pointDisplayHeight: width in meters for the area which needs to be displayed on the map when the geocoder service returned a point. Defaults to 1000 (1km).
The StaticRegexGeocoderService allows you to define the combinations of string to match and the locations directly in the configuration file.
The strings to match are specified using regular expressions[1]to allow more flexibility. Listing Example 3.5, “Base configuration for StaticRegexGeocoderService” shows a base configuration. You have to use the geocoderInfo property to configure the geolocator. This is done using a StaticRegexGeocoderInfo object which contains the coordinate space name (EPSG:900913 in this case, which is Mercator) and define the location mappings.
Example 3.5. Base configuration for StaticRegexGeocoderService
<bean name="staticRegexGeocoderService" class="org.geomajas.plugin.geocoder.service.StaticRegexGeocoderService"> <property name="geocoderInfo"> <bean class="org.geomajas.plugin.geocoder.api.StaticRegexGeocoderInfo"> <property name="crs" value="EPSG:900913"/> <property name="locations"> <list> <ref bean="BooischotShort"/> </list> </property> </bean> </property> </bean>
The location mappings themselves are contained in StaticRegexGeocoderLocationInfo instances. You have to specify the strings toMatch, and a location as either point of bounding box. You can specify the canonical form for the search.
In listing Example 3.6, “Defining a point” you see a definition which will match a single location string starting with second. As this is done case independently, some examples of matching strings are "second" and SECondary". It indicates a point with coordinates (10000,10000).
Example 3.6. Defining a point
<bean class="org.geomajas.plugin.geocoder.api.StaticRegexGeocoderLocationInfo"> <property name="toMatch"> <list> <value>second.*</value> </list> </property> <property name="canonical"> <list> <value>secondService</value> </list> </property> <property name="x" value="10000"/> <property name="y" value="10000"/> </bean>
The location info object can also be used to match an area. In listing Example 3.7, “Defining an area bbox” you see the location bounding box defined using the bbox property. If you would accidentally define both a bounding box and point coordinates, then the bounding box will be used for the result.
Example 3.7. Defining an area bbox
<bean class="org.geomajas.plugin.geocoder.api.StaticRegexGeocoderLocationInfo"> <property name="toMatch"> <list> <value>bbox</value> </list> </property> <property name="bbox"> <bean class="org.geomajas.geometry.Bbox"> <property name="x" value="0"/> <property name="y" value="50000"/> <property name="width" value="100000"/> <property name="height" value="80000"/> </bean> </property> </bean>
A location can also include extra data in the result. You need to wrap this data in a subclass of ClientUserDataInfo. The object to be returned can be defined using the userData property.
Example 3.8. Defining an area and user data
<bean class="org.geomajas.plugin.geocoder.api.StaticRegexGeocoderLocationInfo"> <property name="toMatch"> <list> <value>bla</value> </list> </property> <property name="bbox"> <bean class="org.geomajas.geometry.Bbox"> <property name="x" value="30000"/> <property name="y" value="50000"/> <property name="width" value="10000"/> <property name="height" value="10000"/> </bean> </property> <property name="userData"> <bean class="org.geomajas.plugin.geocoder.service.UserDataTestInfo"> <property name="value" value="xobb"/> </bean> </property> </bean>
The toMatch property contains a list of strings which need to be matched in order. The matching checks every string in the location strings for a matching string in the toMatch list, in order. The matching is case independent and always matches the entire string. A level can be marked as optional in the location strings by using a question marks as prefix for the regular expression. The question mark is removed before the actual evaluation of the regular expression.
As an example we will apply the example in listing Example 3.9, “Multiple strings to match.” to a couple of data sets.
["Belgium", "Antwerpen", "Booischot"]: matches, all three parts match the specific regular expressions.
["Booischot", "Antwerpen", "Belgium"]: no match as the "BE.*" regular expression does not match "Booischot".
["BE", "Booischot"]: matches, the "Antwerp.*" regular expression is marked as option using the "?" prefix.
["Belgium","Antwerpen"]: does not match as the "Booischot" regular expression is not matched for lack of input strings.
["Belgium", "Antwerpen", "Booischot", "Broekmansstraat"]: not matches as the last string "Broekmansstraat" does not have a matching regular expression.
Example 3.9. Multiple strings to match.
<bean name="BooischotStrict" class="org.geomajas.plugin.geocoder.api.StaticRegexGeocoderLocationInfo"> <property name="toMatch"> <list> <value>Be.*</value> <value>?Antwerp.*</value> <value>Booischot</value> </list> </property> </bean>
For this last case, where smaller divisions are not know (in this case the street name), you can end the list of regular expressions with "**" (see listing Example 3.10, “Multiple strings to match with open end.”). This will assure that any remaining strings from the input are discarded if any are remaining. This would assure that the last case in the previous list matches. The other cases would still have the same result.
Example 3.10. Multiple strings to match with open end.
<bean name="Booischot" class="org.geomajas.plugin.geocoder.api.StaticRegexGeocoderLocationInfo"> <property name="toMatch"> <list> <value>Be.*</value> <value>?Antwerp.*</value> <value>Booischot</value> <value>**</value> </list> </property> </bean>
The GeonamesGeocoderService uses the search web service at geonames.org to handle the geocoder requests. You can only configure the "userName" property which is the geonames user which is registered to access the service. This can either be passed directly or using the "userNameProperty" which indicates the property which contains the user name to use. Defining the service is pretty straightforward.
Example 3.11. Defining the Geonames geocoder service
<bean class="org.geomajas.plugin.geocoder.service.GeonamesGeocoderService" > <property name="userName" value="geomajasHudson" /> </bean>
The GeoNames service never returns more than 50 results.
When the initial query returned no results, it will retry the search using fuzzy matching.
This uses the Yahoo! PlaceFinder service (http://developer.yahoo.com/geo/placefinder/). When using this geocoder, you need a appid from Yahoo! and you have to make sure you comply with their terms of use.
To use the geocoder, just create the bean and set the appId.
Example 3.12. Configuring the Yahoo! PlaceFinder geocoder
<bean name="ypf" class="org.geomajas.plugin.geocoder.service.YahooPlaceFinderGeocoderService"> <property name="appIdProperty" value="YahooAppId" /> </bean>
There are a couple of properties which influence how the appId can be passed:
appId: you just define the appId in the configuration file.
appIdProperty: the appId is read from the property which is specified. This can be helpful if you don't want to hardcode the property in your configuration files for some reason.
skipAppIdCheck: normally an exception is thrown when the Yahoo! PlaceFinder geocoder is created without a appId. By setting this property to true, you can avoid this exception, making sure your application will run without the appId (though obviously no results can be found).
This is a simplistic geocoder which allows the user to directly type the coordinate. To configure it, you just have to supply the default CRS used for the coordinates (if not specified, this defaults to EPSG:4326).
Example 3.13. TypeCoordinateService configuration
<bean name="tcs" class="org.geomajas.plugin.geocoder.service.TypeCoordinateService"> <property name="defaultCrs" value="EPSG:900913" /> </bean>
The service accepts input strings like "4.77397 51.05125" to jump to a coordinate, using a space is used as separator between the ordinates. This uses the defaultCrs as configured. You can also explicitly specify the CRS by using a string like "4.77397 51.05125 crs:EPSG:4326".
Table of Contents
This chapter details the extension possibilities of the geocoder plug-in.
If you want your users to select between several geocoder services, you can use the servicePattern property in the command request to select the service.
You could for example configure both the Yahoo! PlaceFinder and GeoNames geocoder services. It is best that you provide explicit names to each service. You can now add a selection widget in your user interface. Depending on the selected value, you can use the setServicePattern() method of GeocoderWidget or alternatively on the GetLocationForStringRequest object of the command invocation to assure the selected geocoder service is used. Note that the service pattern is a regular expression. For alphanumerical names, just providing the name as pattern will work.
Writing a geocoder service is reasonable easy. All you have to do is create an implementation of the GeocoderService interface (listing Example 4.1, “Geocoder service interface definition”). The getCrs() method is used by Geomajas to know the coordinate system which is used for the results of your service. This is used to convert to the coordinate system of the client.
The name is used to select which services should be used for the search. It is recommended that you provide both a default name and a setter to allow users to change this.
Example 4.1. Geocoder service interface definition
public interface GeocoderService { /** * Name for the geocoder service. This name can be used to select which geocoder services are considered during the * search. * . * @return name for this geocoder */ String getName(); /** * CRS which is used for the results of this geocoder. * * @return CRS */ CoordinateReferenceSystem getCrs(); /** * Try to get a location for the strings passed. This can be either a coordinate or an envelope, as passed in the * result object. * * @param location location strings, from general to more specific * @param maxAlternatives maximum number of alternatives which can be replied to the user, use as a hint, you can * return more, but they will be discarded. * @param locale locale use for the location if known (can be null if not known) * @return results objects, when only one this is a definite result, when several the request was ambiguous and the * result are the alternatives. When no results an empty array or null may be returned */ GetLocationResult[] getLocation(List<String> location, int maxAlternatives, Locale locale); }
The getLocation() method does the actual work of converting the location strings (ordered from most general, biggest area, to more specific) to a location. The method is expected to return null or an empty list if no results where found for the location string, or one object if the strings were matched, or multiple results when the matching was ambiguous and resulted in several alternatives.
The result itself contains the information from listing Example 4.2, “Fields which are defined in GetLocationResult”.
Example 4.2. Fields which are defined in GetLocationResult
public class GetLocationResult { private List<String> canonicalStrings; private Coordinate coordinate; private Envelope envelope; private String geocoderName; private ClientUserDataInfo userData;
The fields include:
canonicalStrings: the preferred strings to use for the location. Can be null if no preferred string exists or it is not knows. In that case, the client will get the search strings as result.
coordinate: the coordinate for the location. This field is only used when no envelope was given. The coordinate will be converted to the requested CRS and an area will be built around this point (according to command configuration).
envelope: the bounding box for the location. This is the preferred result and has precedence over the coordinate field.
geocoderName: name of te geocoder which produced the result. You don't have to set this, it will be set by the command.
userData: any additional user data the geocoder may wish to return to the client.
The geocoder command uses an instance of SplitGeocoderStringService to split and sort the initial search string. This should help to make the searching easier and assure the separator for location indicators can be configured and is geocoder service independent.
This way you can configure whether you prefer your users to type "London, UK", "UK, London", "London - UK" or something else.
You basically have to implement the service in listing Example 4.3, “Service interface for splitting the search string” and set that in the GeocoderInfo instance in your application context.
Example 4.3. Service interface for splitting the search string
public interface SplitGeocoderStringService { /** * Split the given string in a list of strings according to the separator convention used. After splitting the * biggest area should come first (assuming the original format had a notion of such ordering). * * @param location location to split * @return list of strings with split location */ List<String> split(String location); /** * Combine the list of strings back into one string accoring to the conventions used for this service. Should be * the reverse of the split functions. As end result split(combine(split(x)) should equal split(x). * * @param matchedStrings strings to combine * @return combined string */ String combine(List<String> matchedStrings); }
You have to provide two methods, one for splitting and one for combining.
When multiple geocoder services found a match for the search string, an instance of CombineResultService is used to combine these results to one area. Two obvious options would be either to use the union or the intersection of the areas (these are already provided as CombineUnionService and CombineIntersectionService), but you can also define your own combination strategy.
Example 4.4. Service to combine search results
public interface CombineResultService { /** * Combine the envelopes from the match results into the end result. * * @param results results which need to be combined * @return result envelope */ Envelope combine(List<GetLocationResult> results); }
All you have to do is implement the combine() method. The strategy can be configured in the GeocoderInfo instance in your application context.
You can use the following tool configuration to add tools in the toolbar.
Example 4.5. Tool configuration
<bean name="Geocoder" class="org.geomajas.configuration.client.ClientToolInfo"> <property name="parameters"> <list> <bean class="org.geomajas.configuration.Parameter"> <property name="name" value="name" /> <property name="value" value="geocoder" /> </bean> <bean class="org.geomajas.configuration.Parameter"> <property name="name" value="title" /> <property name="value" value="Location" /> </bean> </list> </property> </bean>
The widgets behaviour is controlled using two handler classes, SelectLocationHandler and SelectAlterativeHandler. For both only one handler can be configured for the widget.
The SelectLocationHandler has a default implementation which zooms to the location which was found or selected. The default implementation is displayed in listing Example 4.6, “Default implementation for SelectLocationHandler”.
Example 4.6. Default implementation for SelectLocationHandler
public void onSelectLocation(SelectLocationEvent event) { org.geomajas.geometry.Bbox bbox = event.getBbox(); map.getMapModel().getMapView().applyBounds(new Bbox(bbox), MapView.ZoomOption.LEVEL_FIT); geocoderWidget.setValue(event.getCanonicalLocation()); }
The SelectAlternativeHandler is used when the location was considered ambiguous and several alternatives are returned. The default implementation (listing ???) puts a window on the map in which the correct location can be selected by the user. This uses the GeocoderAlternativesGrid which lists the options and invokes the SelectLocationHandler on the geocoder when the user selects a line.
Example 4.7. Default implementation for SelectAlternativeHandler
public void onSelectAlternative(SelectAlternativeEvent event) { if (null == altWindow) { altGrid = new GeocoderAlternativesGrid(geocoderWidget, event.getAlternatives()); altWindow = new Window(); altWindow.setAutoSize(true); altWindow.setTitle(messages.alternativeSelectTitle()); altWindow.setAutoSize(true); altWindow.setLeft(20); altWindow.setTop(20); altWindow.setCanDragReposition(true); altWindow.setCanDragResize(true); altWindow.addItem(altGrid); altWindow.addCloseClickHandler(new CloseClickHandler() { public void onCloseClick(CloseClientEvent closeClientEvent) { removeAltWindow(); } }); map.addChild(altWindow); } else { altGrid.update(event.getAlternatives()); } }