Extension:Maps/Extending Maps

Warning: This documentation applies to Maps and Semantic Maps 0.3.3. The current version has undergone considerable internal changes, and will require a different approach to hook into. At some point this documentation will be updated. Until then, please contact one of the developers for assistance.

Note: this article is in the process of being updated to match the current version of 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.

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

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, but later on, you will also need to add some array items to enable specific functionality.

Aliases
Every service can have an unlimited amount of aliases. Aliases enable you to enter alternate values for the parameter specifying the mapping service, that will then be interpreted by the code as if they where the main service name. Aliases are added by placing a new array key with the name 'aliases', and an array holding the aliases as value into the service's array. The below example will add 3 aliases to your-service.

$egMapsServices['your-service'] = array(					'aliases' => array('yourservice', 'ys', 'your service')					);

Note that you need to add the 'aliases' array, even when there are no aliases.

'aliases' => array

Parameters
Maps and Semantic Maps validate the parameters used in parser functions, form inputs and queries. They do so by comparing them to an array of allowed parameter names, which also holds aliases for the names. This array is defined in MapsMapper.

private static $mainParams = array (			'service' => array,			'coordinates' => array('coords', 'location', 'locations'),			'zoom' => array,			'centre' => array('center'),			'width' => array,			'height' => array,			'controls' => array,			'label' => array,			'title' => array							);

Every service can add it's own array of service specific parameters, which will then also be allowed, when that service is used to display a map. This is done by adding a 'parameters' element to the service's array. The value of this new element should be an array which holds named elements. The names of these elements represent the main parameter name, and their value, which should be an array, contain their aliases.

$egMapsServices['your-service'] = array(					'parameters' => array( 'a-specific-parameter' => array('specific-parameter', 'specificparameter'), 'one-without-alias' => array );

Note that you need to add the 'parameters' array, even when there are no mapping service specific parameters.

Example
This example is taken from the Google Maps code in Maps, and contains both alias and parameter definitions.

$egMapsServices['googlemaps'] = array(					'aliases' => array('google', 'googlemap', 'gmap', 'gmaps'),					'parameters' => array( 'type' => array('map-type', 'map type'), 'types' => array('map-types', 'map types'), 'earth' => array, 'autozoom' => array('auto zoom', 'mouse zoom', 'mousezoom'), 'class' => array, 'style' => array )					);

Parser functions
This section describes how you can add parser function support for your new mapping service. This means that when you do the described tasks, your mapping service will be usable via Map's parser functions: display_point, display_points, display_address and display_addresses. The examples assume you have an extension that uses Exp (Extension prefix) as prefix. Please use your own prefix for your extension, and not Maps or SM.

Map class
Maps provides an abstract class MapsBaseMap that contains functionality that is common to all mapping services and enforces the implementation of several abstract methods. It is highly recommended you create a class inheriting from MapsBaseMap to handle your parser functions. This will save you a lot of work, and guarantee convention to some extend.

So, first of all create your class. It should be placed in a directory named after the mapping service, and the file name should be your extensions prefix, followed by an underscore, and then the mapping service name. The full path should then have this format:.

After this you need to do is add your class to the mapping service's info array. By doing this, Maps will recognize that your service supports the parser functions, and will also automatically load the classes into the $wgAutoloadClasses array.

$egMapsServices['your-service'] = array(					'pf' => array('class' => 'ExpYourService', 'file' => 'YourExtension/YourService/Exp_YourService.php', 'local' => false)					);

As you can see in the above example, the name of the array element should be 'pf', short for parser function. The value is an array containing the name of your class, the file and it's path, and a value indication if the file is local. The local parameter will cause the complete path to the maps extension directory to be added when true. If you have your own extension, the value should be set to false, and you should provide the relative path from the extension directory in the file parameter.

The next step is adding the code to handle your mapping service to your class. First of all, start off with defining a new class, that inherits MapsBaseMap.

Now add the service name (the same one you added as array key name to $egMapsServices) to your class. This is required for Maps to be able to identify the class.

By inheriting from MapsBaseMap, you will be forced to implement 3 methods: setMapSettings, doMapServiceLoad and addSpecificMapHTML.

1. Method setMapSettings gets called before the others, and needs to set 3 class fields. This example is code from the MapsGoogleMaps class of Maps.

holds an array with default values for the mapping service specific service. In this example, the value is retrieved by a function in a special utility class. The use of such a class is discussed in the next section.

should be set to an identical prefix for your mapping service, like 'yourservice_map'. It's recommended that you make this value a setting, and put it in Maps_Settings.php, like done in the example.

should be set to the default zoom level of your mapping service. Just like with the element name prefix, it's recommended that you make a setting of this value.

2. Method doMapServiceLoad does map service specific map count and loading of dependencies. This means add an html string that contains all the stuff that needs to be loaded to $this->output. This can include loading map API's via html script tags. When no dependencies need to be loaded (which is unlikely), you still need to return an empty string. You should also set the elementNr field, which contains the number of the map. This number should also be increased. This example is again an out-take of the Google Maps code in Maps.

3. The addSpecificMapHTML should add the actual map HTML and JavaSctipt to $this->output. It also contains all mapping service specific map property manipulation. The output you add in this method is entirely dependant on your mapping service. It most likely consists out of a div element to old the map, and some javascript to create it, set it's properties and add markers. It's highly recommended that you add as little JavaScript as possible. It is not good practice to use PHP to create JavaScript. The map logic should be handled by JavaScript, and JavaScript alone. A good approach is to create a JavaScript function to create your map, put it in a seperate .js file, load it with the mapping service's dependencies, and then only add a JavaScript call to that function to $this->output.

This is the HTML output added in MapsGoogleMaps, of Maps.

Utility class
Maps uses utility classes for each mapping service that contain static functions that get called from their map class. This is done to enable other classes, like form inputs and query printers to use common code, and prevent redundancy. It is recommended you choose a similar setup, but you can also simply put all code in your map class (note that this will make extending your mapping service more difficult), or use multiple utility classes (to increase modularity when you have a lot of common code).

You can either manually take care of the including of your utility classes, or add them as a new item in the 'classes' item of your mapping service's array.

$egMapsServices['your-service'] = array(					'classes' => array( array('class' => 'ExpYourServiceUtils', 'file' => 'YourService/Exp_YourServiceUtils.php', 'local' => false) ),					);

If you go for the recommended structure in your map class, you'll need to add several methods to your utilities class. These methods then get called by your code in the 3 function your implemented in your map class, as discussed in the previous section.


 * Method getDefaultParams. It should return an array with the default values for your mapping service specific parameters. The array keys represent the parameter names, the values represent the parameters default value. Snippet from GoogleMapsUtils:


 * A method that adds the dependencies for your service. Snippet from GoogleMapsUtils:

It's likely more things should be put into the utilities class, but this is dependent on your mapping service. The underneath list contains some common things. It does not reflect in any way what you should have in your utilities class, since some of the items might not be applicant to your mapping service.


 * Map types validation. (A function that takes in an user provided map type, checks if it's valid, and returns it, or false/the default when it isn't.)

Geocoding services
Maps does currently not support any hook like system for geocoding services. It is however quite easy to create your own, and then submit it as a patch.

This is a very rough list of the basic steps you'll need to take to add support for a geocoding service to Maps:


 * 1) Create a new class, that inherits from MapsBaseGeocoder, and place it in the Geocoders directory of Maps.
 * 2) Add a   to your class.
 * 3) Do the call to the geocoding service there, and return an array with keys 'lat' and 'lon', containing the latitude and longitude, respectively, or false when geocoding fails.
 * 4) Modify MapsGeocoder (Maps/Maps_Geocoder.php): add a new case element to the switch statement in   with the name for your geocoding service. Call the addAutoloadClassIfNeeded method, and add a call to the geocode method in your new class.
 * 5) Add the name for your geocoding service to the $egMapsAvailableGeoServices array in Maps_Settings.php.

That are all the steps you need to do. You can have a look at the present services as examples.

Extending Semantic Maps
The adding of support for a mapping service to one of Semantic Map's features is very similar to adding support to Maps parser functions. It is recommended you read how to add support to parser functions first, if you haven't already done so, to optimally understand the contents of this section.

The examples in this section assume you have an extension that uses Sep (Semantic extension prefix) as prefix. Please use your own prefix for your extension, and not Maps or SM.

Query printers
This section describes how you can add query support for your new mapping service. This means that when you do the described tasks, users will be able to aggregate information with a query that will then be displayed on a map of your mapping service.

Semantic Maps provides an abstract class SMMapPrinter that contains functionality that is common to all mapping services and enforces the implementation of several abstract methods. It is highly recommended you create a query printer that inherits from SMMapPrinter. This will save you a lot of work, and guarantee convention to some extend.

So, first of all create your class. It should be placed in a directory named after the mapping service, and the file name should be your extensions prefix, followed by an underscore, and then the mapping service name. The full path should then have this format: extensions/YourExtension/YourService/Sep_YourService.php. If this name is already taken by a parser function class, you should name it Sep_YourServiceQueryPrinter.php.

After this you need to do is add your class to the mapping service's info array. By doing this, Semantic Maps will recognize that your service has a query printer, and will also automatically load the classes into the $wgAutoloadClasses array. In most cases the service will already be added to the mapping service array in Maps, to handle parser function support. If not, you should create it first. To avoid overriding values set in Maps, you are discouraged to use the syntaxis demonstrated in the examples of how you add a parser function, instead use the below format. This rule also applies for when no parser function support is present!

$egMapsServices['your-service']['qp'] = array('class' => 'SepYourServiceQueryPrinter', 'file' => 'YourExtension/YourService/Sep_YourServiceQueryPrinter.php', 'local' => false);

As you can see in the above example, the name of the array element should be 'qp', short for query printer. The value is an array containing the name of your class, the file and it's path, and a value indication if the file is local. The local parameter will cause the complete path to the maps extension directory to be added when true. If you have your own extension, the value should be set to false, and you should provide the relative path from the extension directory in the file parameter.

Creating the Query Printer
The next step is creating your query printer. First of all, start off with defining a new class, that inherits from SMMapPrinter.

Now add the service name (the same one you added as array key name to $egMapsServices) to your class. This is required for Semantic Maps to be able to identify the class. If you have a parser function class, it's most likely already defined there, and can be reused. If not, you will have to define it.

By inheriting from SMMapPrinter, you will be forced to implement 3 methods: setQueryPrinterSettings, doMapServiceLoad and addSpecificMapHTML. These functions should do pretty much the same as the ones in a parser function class.

The only notable difference is in the addSpecificMapHTML function. In this function you need to add all results to your output. The difference with parser function classes is that the results are stored in a class field $m_locations. This is an array containing an array with 5 elements for each location. These elements are unnamed, and placed in this order: latitude, longitude, title, label, icon. You can get them like this.

This is an out take of the Google Maps query printer of Semantic Maps that demonstrates how you can create JS output for your markers.

There is an additional function you should override: getName. If you don't do this, your code will work, but people viewing the Special:Ask page will get an error. This function should return a localized string with the name of your query printer, defined in your own i18n file.

Google Maps query printer example:

Form inputs
This section describes how you can add form input for your new mapping service. This means that when you do the described tasks, users will be able to insert and edit coordinate data via an easy-to use form interface. If you're not familiar with it yet, have a look at Semantic Forms, the extension that's hooked into to achieve this functionality. The 'Defining new inputs' sub page of the Semantic Forms docs contains a brief overview of how to define a hook for it.

Semantic Maps provides an abstract class SMFormInput that contains functionality that is common to all mapping services and enforces the implementation of several abstract methods. It is highly recommended you create a query printer that inherits from SMFormInput. This will save you a lot of work, and guarantee convention to some extend.

So, first of all create your class. It should be placed in a directory named after the mapping service, and the file name should be your extensions prefix, followed by an underscore, and then the mapping service name. The full path should then have this format: extensions/YourExtension/YourService/Sep_YourServiceFormInput.php.

After this you need to do is add your class to the mapping service's info array. By doing this, Semantic Maps will recognize that your service has a form input, and will also automatically load the classes into the $wgAutoloadClasses array. In most cases the service will already be added to the mapping service array in Maps, to handle parser function support. If not, you should create it first. To avoid overriding values set in Maps, you are discouraged to use the syntaxis demonstrated in the examples of how you add a parser function, instead use the below format. This rule also applies for when no parser function support is present!

$egMapsServices['your-service']['fi'] = array('class' => 'SepYourServiceFormInput', 'file' => 'YourExtension/YourService/Sep_YourServiceFormInput.php', 'local' => false);

As you can see in the above example, the name of the array element should be 'fi', short for form input. The value is an array containing the name of your class, the file and it's path, and a value indication if the file is local. The local parameter will cause the complete path to the maps extension directory to be added when true. If you have your own extension, the value should be set to false, and you should provide the relative path from the extension directory in the file parameter.

Creating the Form Input
The next step is creating your from input. First of all, start off with defining a new class, that inherits from SMFormInput.

Now add the service name (the same one you added as array key name to $egMapsServices) to your class. This is required for Semantic Maps to be able to identify the class. If you have a parser function class, it's most likely already defined there, and can be reused. If not, you will have to define it.

By inheriting from SMFormInput, you will be forced to implement 3 methods: setMapSettings, doMapServiceLoad and addSpecificMapHTML. These functions are identical to the ones in the parser function class, and have to be implemented in pretty much the same way.

1. Method setMapSettings gets called before the others, and needs to set 5 class fields. This example is code from the SMGoogleMapsFormInput class of Semantic Maps.

,  and   re identical to the fields in parser function classes.

Should be set to the name of a JavaScript function that removes the current marker, places a new one on the new provided location, and (if possible) pans to it. You will have to create this JavaScript function.

Should be set to the a zoom level on which the whole planet is visible.

2. Method doMapServiceLoad is identical to the on in parser function classes.

3. The addSpecificMapHTML is similar to the on in parser function classes. The differences are that you only have one point, of which the coordinates are stored in  and , and that you have to call a different JavaScript function. This JavaScript function should create a map, add a click handler that sets the contents of the coordinate field of the form to the clicked coordinates. To allow this, the SMFormInput class provides you with a field  that holds the id of the field, and can be passed to your JS function.

This is the HTML output added in SMGoogleMapsFormInput, of Semantic Maps.

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.