The MapPoint service is a commercial offering by Microsoft which gives developers access to a wide variety of mapping functionality through a web service interface. Well that's how it used to be anyway. For a while MapPoint was just a web service and a technology that powered products like Autoroute, then for a while it became Microsoft Virtual Earth which did nothing apart from change what appeared on the map images that you loaded from the servers. Then Microsoft launched their Bing extravaganza which meant that it's now called Bing Maps - well it is when one logs into the control panel but the service is still called MapPoint. It's highly confusing and makes it intensely difficult to find what one wants on the Microsoft site, especially on plumbing the depths of MSDN. For the purposes of this diatribe however, MapPoint is a web service that uses the SOAP protocol.
Regardless, if you want to integrate with MapPoint, the only real option you had was a relatively complete API by Carlos Jorge Machado Antunes which aimed to provide a complete replica of the web service in PHP. Upon first using it in 2006, things worked well - the API has a custom Soap client which extends the built in SoapClient class and does various things to ensure that everything runs smoothly. Unfortunately, it started running less smoothly as each successive version of PHP was released and it got to the point where at times my development environment worked while testing and production didn't or vice versa, eventually it became apparent that the SoapClient class was doing things which were no longer necessary or not doing things which were necessary. The most prominent of these was the service's refusal to accommodate references: e.g. to reduce the envelope size of a SOAP request it would specify an object once and then reference that by ID at other points in the XML document - this is smart thinking but the MapPoint service is a fickle beast. The solution to this (as with most SOAP foibles) is to intercept the XML before it is sent (on the __doRequest() method), create a full DOM object, execute an XPath query to nab all of the instances of a reference and then duplicate the referenced node in place. This is an immensely costly operation to do for each action and coupled with the round trip time of the request means each query is laboriously slow, especially at busy periods.
With one of the big sites that uses MapPoint under renovation, the chance to clean up and improve the way PHP can talk to MapPoint presented itself. At risk of reinventing the wheel, Carlos' PHP API was used as a base - it already comes with all of the relevant classes and documentation built in so there seemed little point in starting this from scratch. With the aim to ultimately integrate the result into the Zend Framework, the first step was to organise the classes into some sort of hierarchy. With over a 100 classes this was easier said than done, thankfully the organisation is already pre-determined by the class names, so obviously ArrayOfDouble goes into an ArrayOf and so forth. Next was to namespace all of the classes; despite its completeness, all of the class names are loaded into the standard namespace which isn't too much of a problem for small applications or sites, but having common names like Address and Location as globals isn't the best forward-thinking plan - putting them all into a Mappoint namespace (with the aim to go into a Zend_Service_Mappoint one in the future) meant prefixing all of the class names and any references which was no small feat. Alongside this was a tidying up of some code foibles such as using the class name as the constructor name rather than the more standard magic function __construct(); likewise other ye olde PHP object idiosyncrasies such as having to call the parent of an extended class were removed or otherwise optimised.
While all of this was underway, a question as to how far a rebuild should go was raised. The MapPoint web service goes against common PHP programming style by using an uppercase first letter for function names e.g. FindNearRoute() rather than findNearRoute() - neither is incorrect however the latter is more preferable in context. However changing the case of the function call would go against the documented API, anybody using the PHP implementation would need to do a mental check before calling a function. This is a minor case but illustrates the point of what to change in the quest of betterment. For instance some classes contain, what are for all intents and purposes, constants, however in Carlos' implementation they are static variables e.g. Class::$And; if they were to be converted to constants, common code style dictates they should be made entirely uppercase to denote them as constants e.g. Class::And becomes Class::AND. However this not only refers back to the "what to change" question, but also raises a problem in that what was once a valid variable name ($And) becomes an invalid constant name (AND) due to being a language construct and reserved keyword. Converting them to constants then becomes a question of what could be prefixed or suffixed to constants to make them practicable and consisten.
As the rebuild progressed, it become readily apparent that not only were the previous eccentricities in place for a reason, but that the service itself is ill suited to PHP. For starters, the service requires the use of the antiquated SOAP version 1.1 and forgoes the improvements in 1.2 - this is not a huge issue but is just the start of a number of aggravations and problems. The previously mentioned optimisation issue is unable to be worked around conventionally and only a single bug report on the PHP site mentions this as a problem for other users which means the on-the-fly XML transforms are retained:
$dom = DOMDocument::loadXML($request); $path = new DOMXPath($dom); $references = $path->query("//*[starts-with(@href, '#ref')]"); foreach($references AS $reference) { $target = $path->query("//*[@id='".substr($reference->getAttribute('href'), 1)."']"); if($target->length > 0) { $t = $target->item(0); // if not the same node name then duplicate the children if($t->nodeName != $reference->nodeName) { for($j = 0; $j < $t->childNodes->length; $j++) { $reference->appendChild($t->childNodes->item($j)->cloneNode(true)); } $reference->removeAttribute('href'); } else { // otherwise just clone the target node $x = $t->cloneNode(true); $x->removeAttribute("id"); $reference->parentNode->replaceChild($x, $reference); } } } $request = $dom->saveXML(); return parent::_doRequest($client, $request, $location, $action, $version, $oneWay);
Benchmarking isn't necessary to demonstrate this is an overhead that ideally would be omitted. Likewise there is the necessity to force the transformation of some objects into the SoapVar class to enable them to retain their type and namespace identifiers - an odd and enigmatic problem that seems to stem from PHP itself and a process that I'm not entirely familiar with. This means that for some objects there is an additional operation required to enable the SOAP request to be accepted by the MapPoint service:
// lifted from Carlos Jorge Machado Antunes' original API public function transform() { $uri = Mappoint::NAMESPACE_URI; $view = new SoapVar($this, SOAP_ENC_OBJECT, "ViewByBoundingRectangle", $uri, "ViewByBoundingRectangle",$uri); $view->enc_value->BoundingRectangle = new SoapVar($view->enc_value->BoundingRectangle, SOAP_ENC_OBJECT, "LatLongRectangle", $uri, "LatLongRectangle", $uri); $southwest = new SoapVar($view->enc_value->BoundingRectangle->enc_value->Southwest, SOAP_ENC_OBJECT, "LatLong", $uri, "LatLong", $uri); $view->enc_value->BoundingRectangle->enc_value->Southwest = $southwest; $northeast = new SoapVar($view->enc_value->BoundingRectangle->enc_value->Northeast, SOAP_ENC_OBJECT, "LatLong", $uri, "LatLong", $uri); $view->enc_value->BoundingRectangle->enc_value->Northeast = $northeast; return $view; }
There seems to be little methodology as to why this is necessary and I'm loathe to explore for reasons soon to be explained, however the nature of SOAP means that any implementation problem is difficult to locate and debug. This is compounded by the depth of some of the objects which can plumb eight, sometimes twelve levels deep which makes simple var_dump output near impossible to decipher. To stand any chance of debugging the process, screeds of data was stored upon each request:
- The XML generated by PHP
- The resultant XML after reference removal
- Parameters passed
- The desired action
- The XML response from the server
Everything was timestamped while every piece of XML was beautified to provide better navigation and stored in the application log directory - for a standard routing request this resulted in a maximum of six files:
- Start location search
- End location search
- Via location search (optional)
- Route calculation
- Corridor search for points of interest
- Map render request
At any point the process could fail catastrophically, mostly due to the abject bloody-mindedness of the MapPoint service. Actions within FindService usually return a result object (e.g. FindResult) which consists of an ArrayOf object (e.g. ArrayOf_Location) which is a wrapper around a standard PHP array, the problem occurs when only a single result is returned which then silently transforms the ArrayOf array into an object. This means that every result needs branching logic to determine the disposition of what has been returned and ugly, fragile code results.
On their own these are small issues with what is evidently a complex and full-featured service, however cumulatively they present an API that feels more combative than accommodating and makes working with it tiresome and more infuriating than is necessary. The original idea to create a clean, manageable API implementation that was fit for modern and rapid usage was swiftly quelled by the realisation that MapPoint wears its heritage on its sleeve and was always designed for .NET environments and languages rather than PHP. Realistically the desire to create a PHP API with those characteristics may be moot when more modern successors exist such as Bing Maps or the Multimap API - creating for an obtuse and superseded service lacks the utility and draw when put in perspective.
In the end the golden land of an elegant, flawless PHP version of the MapPoint API was replaced by a robust and serviceable update of an existing implementation that provides a step between MapPoint and future technologies. In a way it is disappointing but the continuous disdain felt while working with the service is enough to mitigate that.