Extension:Maps/Extending Maps

Introduction
Maps provides several hooks allowing you to extend it in various ways. You can add support for new mapping services, implement new functionality and add support for additional geocoding services. Adding a hook to Maps has several huge advantages above creating your own extension to hold this functionality, or simply adding it to your own. These advantages include that you do not need to worry about the basics that come with mapping features, that you can use the in-place and ready-to-use scaffolding Maps provides for new functionality, and the easy of distribution via one central extension. The most noteworthy extension to Maps, using these hooks, is Semantic Maps, which adds semantic result formats and form inputs for several mapping services. This article explains how you can create your own Maps hooks.

Extending Maps
When you want to extend Maps in any way, it's recommended you do this in your own extension, that then will hold all your code. This way the relative stability of Maps stays assured, and users can optionally add your service. The code can later on very easily be put into Map itself.

Parameter definition
Maps uses Validator to validate, correct and handle parameter errors. Review how parameters should be defined before continuing. Maps defines parameters on 4 different levels. All definitions are merged together when a mapping feature handling a specific service is called, where the definitions of the lower levels can add to, and override these of the above ones.


 * Global: These parameters are available for every mapping service, mapping feature, combination. Width and height are examples of such parameters.
 * Feature: These parameters are available for every implementation of this feature. Centre is an example here since display_point defines it, and display_map doesn't.
 * Service: These parameters are available for every implementation of this service. The layers parameter, only defined by OpenLayers, is an example here.
 * Specific: These parameters are available only for the mapping service, mapping feature, combination in which they are defined.

Depending on how you are extending Maps, you will be faced with one of the later 3 levels.

Adding mapping services
Each mapping service has a file holding it's specific data, initialization methods, and utility functions. If you follow the proper setup, the only thing that will be required to enable this mapping service in Maps is including that file. Similarly, disabling it should be as easy as removing the includes. The inclusion should typically happen in a settings file. See Maps_Settings.php for the inclusion of the native supported mapping services.

The following subsections will walk you through the contents of your mapping service file. Although there is no clean made-for-manual example, you can always refer to the implemntations of Google Maps, [http://svn.wikimedia.org/viewvc/mediawiki/trunk/extensions/Maps/YahooMaps/Maps_YahooMaps.php?view=markup Yahoo! Maps] and OpenLayers in Maps.

Service data
Maps defines an array that holds the list of available mapping services and their related information in Maps.php. The name of this variable is. Adding a new service to it is as easy as creating a new array element, with as key the service name, and as value an array holding it's information.

$egMapsServices['your-service'] = array;

You need to place this code globally, and preferably in the initialization file of your extension. In other words, you can not place it in a function or method. The next step is adding the service's information to the array. This includes general mapping service information, like aliases and parameters, the information of classes that need to be loaded, and handlers for mapping features /* TODO: link */.

This code from the Google Maps file is a good example.

$egMapsServices['googlemaps'] = array(									'classes' => array( array('class' => 'MapsGoogleMaps', 'file' => 'GoogleMaps/Maps_GoogleMaps.php', 'local' => true) ),									'aliases' => array('google', 'googlemap', 'gmap', 'gmaps'),									);

The Google Maps implementation in Maps uses the key 'googlemaps' as identifier. You can use whatever key you feel suited, as long as it's not in use by another service. This key is used to bind handler classes for certain functionality to your service.

The aliases are put into an array that has the key 'aliases'. You can also load extra classes by adding them to a 'classes' key. These will only be loaded when your mapping service is enabled. The native services of Maps only use one class that holds all utility functions, but you can just as well work with multiple.

Initialization method
Parameter definitions often require defaults that are not available yet when the above code is run. Therefore Maps has a hook mechanism that checks if there exists any method named 'initialize' in the extra classes loaded, and run it when that's the case. In this method you can then assign service specific parameter definitions to the $egMapsServices array. This example from the OSM service demonstrates this:

const SERVICE_NAME = 'osm'; public static function initialize { self::initializeParams; }	private static function initializeParams { global $wgLang; global $egMapsServices, $egMapsOSMZoom, $egMapsOSMControls; $egMapsServices[self::SERVICE_NAME]['parameters'] = array(			'zoom' => array( 'default' => $egMapsOSMZoom, ),			'controls' => array( 'type' => 'list', 'criteria' => array(					'all_in_array' => self::getControlNames					), 'default' => $egMapsOSMControls ),				'lang' => array( 'aliases' => array('locale', 'language'), 'criteria' => array(					'in_array' => array_keys( Language::getLanguageNames( false ) )					), 'default' => $wgLang->getCode ),															);	}

Utility functions
You can put any utility functions specific to your mapping service in the class where you have your initialization method, or another one you added to the $egMapsServices array. You can then use these utility functions from all mapping features implementations that use your service.

Service settings
Maps has several mapping service related settings you should be aware of. All settings work with the identifier keys of the mapping services.


 * $egMapsAvailableServices: This is an array containing all services that should be made available to the user. If your service is not in it, the initialization method will not be run, and none of the handlers will be loaded. So make sure you add your service like $egMapsAvailableServices[] = 'your-service-key';


 * $egMapsDefaultServices: Array of String. The default mapping service for each feature, which will be used when no valid service is provided by the user. Each service needs to be enabled, if not, the first one from the available services will be taken. Example: . You probably don't need to bother this setting, but be aware of it's existence.


 * $egMapsDefaultService: The default mapping service, which will be used when no default service is present in the $egMapsDefaultServices array for a certain feature. You probably don't need to bother this setting, but be aware of it's existence.

Adding mapping features
Adding new features to Maps as similar, although probably more simple, then adding services. You need to have a class that holds an initialization method that loads all the handlers from mapping services that support your feature, and you need to add a reference to this class itself in a settings file. Let's look at an example of such a reference:

$egMapsAvailableFeatures['pf'] = array(							'name' => 'Parser Functions',							'class' => 'MapsParserFunctions',							'file' => 'ParserFunctions/Maps_ParserFunctions.php',							'local' => true,							);

$egMapsAvailableFeatures is an array that holds all the available features. A feature can be disabled/enabled by removing/adding the assignment to your settings file.

The array key is the identifier key for your feature. (In the example 'pf' is used since it's the asynchronism for Parser Functions) Just like with identifier keys for mapping services, you can choose whatever you feel is suitable, as long as it's not in use yet.

For examples of the class you refer to here, see Maps_ParserFunctions.php, SM_QueryPrinters.php and SM_FormInputs.php.

Defining a base class
Assuming you want to create something that output maps, it's very likely the best way to create your new functionality is by having a general base class that gets inherited by others that hold the things specific to the mapping service they handle. Note that if you want to do something completely different then outputting an actual map, you can simply go your own way with structuring your code. An example of this is the geocoding feature of Maps, which stands loose from any interaction with the mapping services.

By using this approach, it'll be relatively little work to add new handlers for other mapping services, and more easy to make changes to the core logic of your mapping feature. Maps also provides a base class that can get inherited by such base classes, since they all have common work to do, like parameter handling. This is the MapsMapFeature class.

For examples of how this class is inherited, see Maps_BasePointMap.php, Maps_BaseMap.php and SM_FormInput.php.

For examples of classes using such a base class (here MapsBasePointMap), see Maps_GoogleMapsDispPoint.php, Maps_OpenLayersDispPoint.php and Maps_YahooMapsDispPoint.php.

Adding geocoder services
The geocoder feature has a build in hook that allows you to add support for new geocoding services by adding their handling class to the $egMapsAvailableGeoServices array.

his is how the build in supported geocoding services get added in Maps:

$egMapsAvailableGeoServices = array(									'google' => array( 'class' => 'MapsGoogleGeocoder', 'file' => 'Geocoders/Maps_GoogleGeocoder.php', 'local' => true, 'overrides' => array('googlemaps'), ),									'yahoo' => array( 'class' => 'MapsYahooGeocoder', 'file' => 'Geocoders/Maps_YahooGeocoder.php', 'local' => true, 'overrides' => array('yahoomaps'), ),									'geonames' => array( 'class' => 'MapsGeonamesGeocoder', 'file' => 'Geocoders/Maps_GeonamesGeocoder.php', 'local' => true, ),									);

The geocoding class needs to inherit from MapsBaseGeocoder, which will force you to implement. This function needs to either return false when geocoding failed, or an array containing the latitude and longitude, with keys 'lat' and 'lon', respectively.

For examples, see Maps_GeonamesGeocoder.php, Maps_GoogleGeocoder.php and Maps_YahooGeocoder.php.

Adding parser functions
The parser functions feature has a build in hook that allows you to easily add new mapping parser functions. The parser functions feature itself will handle the coordinates and address(es) parameters, and ensure no map is shown when they are invalid.

This hook works similar to the one of mapping services; you need to include an initialization file that contains the parser function information and does it's initialization.

Examples of such files are Maps_DisplayMap.php, Maps_DisplayPoint.php and Maps_GeocodeFunctions.php.

If these parser functions should work together with the mapping service hook system, in other words, have support specific to mapping services, it's most likely best to have a general base class that gets inherited by child classes that hold the service specific code.

If this is the case, just add your class name to $egMapsAvailableFeatures['pf']['hooks'][], like:. Also add a  function to your class, like in this example:

public static function displayPointRender(&$parser) { $args = func_get_args; return MapsParserFunctions::getMapHtml($parser, $args, 'display_point'); }

More support
If you have any additional questions, or feel some aspects of extending maps have not been documented enough, feel free to contact Jeroen De Dauw.