
This is an example of how to use the ws-pinwin HTTP server to request a map for a location, rendered with a “pinwin” style marker, on to which a source image can be pasted. It builds on topics already covered in the ws-compose and ws-pinwin tutorial.
Much of the code below has also been wrapped up the in wscompose/client.py library but since the goal is to understand what’s going on underneath the hood we’ll walk through all of the steps explicitly in both Python, on the left, and PHP, on the right. If you prefer Perl, you can refer to code written for the Net::Flickr::Geo::ModestMaps package, available on the CPAN.
First, load the libraries we’ll need to use.
# python import httplib import urllib import re import string import Image import StringIO
# php # nothing except php5 with the curl and gd extensions
For the sake of brevity, we’ll assume that we know the URL and lat/lon coordinates of the image.
# python im_url = 'http://farm1.static.flickr.com/29/52139344_21e210d829.jpg' lat = 37.507653 lon = -122.339994
# php $im_url = 'http://farm1.static.flickr.com/29/52139344_21e210d829.jpg'; $lat = 37.507653; $lon = -122.339994;
Next, fetch the image and for its dimensions.
# python try : im_data = urllib.urlopen(im_url).read() im_obj = Image.open(StringIO.StringIO(im_data)) except Exception, e : raise e (image_width, image_height) = im_obj.size
# php $im_data = file_get_contents($im_url); if (! $im_data){ echo "failed to retrieve '$im_url'"; exit; } $im = imagecreatefromstring($im_data); if (! $im){ echo "failed to create image from '$im_url'"; exit; } $im_width = imagesx($im); $im_height = imagesy($im);
Now that we have the basic information, build the arguments that will be passed to Modest Maps. In this case we asked for a map centered on the source image’s latitude and longitude and zoomed in to "street" level with a pinwin-style marker large enough to paste the image in to.
Markers are defined
by passing one or more marker arguments, each of which is a string
composed of the following comma-separated values :
In this example we also ask Modest Maps to build the map image using tiles from Microsoft and to filter the final composited version with an Atkinson dithering before sending it back.
# python marker_args = ('example', lat, lon, im_width, im_height) marker_args = map(str, marker_args) marker_args = ",".join(marker_args) mm_args = { 'filter' : 'atkinson', 'provider' : 'MICROSOFT_AERIAL', 'method' : 'center', 'latitude' : lat, 'longitude' : lon, 'zoom' : 17, 'height' : 1239, 'width' : 1771, 'marker' : marker_args, } mm_params = urllib.urlencode(mm_args) mm_url = '127.0.0.1:9999' mm_endpoint = "/?%s" % mm_params
# php $marker_args = array( 'example', $lat, $lon, $im_width, $im_height ); $marker_args = implode(",", $marker_args); $mm_args = array( 'filter=' . 'atkinson', 'provider=' . 'MICROSOFT_AERIAL', 'method=' . 'center', 'latitude=' . urlencode($lat), 'longitude=' . urlencode($lon), 'zoom=' . 17, 'height=' . 1239, 'width=' . 1771, 'marker=' . urlencode($marker_args), ); $map_url = '127.0.0.1:9999/?' . implode("&", $mm_args);
Fetch the map image. Note that if the ws-pinwin server
encounters a problem it will return error details in the
x-errorcode and x-errormessage HTTP headers.
# python try : conn = httplib.HTTPConnection(mm_url) conn.request("GET", mm_endpoint) res = conn.getresponse() except Exception, e : raise e if res.status != 200 : if res.status == 500 : code = res.getheader('x-errorcode') msg = res.getheader('x-errormessage') errmsg = "(%s) %s" % (code, msg) raise Exception, errmsg else : raise Exception, res.message conn.close()
# php $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $map_url); curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $res = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); $err = curl_error($ch); curl_close ($ch); if ($err){ print "failed to retrieve '{$map_url}' : {$err}"; exit; } if ($status != 200){ list($head, $data) = explode("\r\n\r\n", $res, 2); $details = array(); foreach (explode("\r\n", $head) as $ln){ list($k, $v) = explode(":", $ln, 2); $k = trim($k); $v = trim($v); if (preg_match("/^x-error/i", $k)){ $k = str_replace("x-", "", strtolower($k)); $details[$k] = strtolower($v); } } echo "({$details['errorcode']}) {$details['errormessage']}"; exit; }
Iterate over the HTTP headers looking for ones
that begin with x-wscompose. These will contain information
about the image like height and width as well as the x and y
coordinates where our original image can pasted into. Metadata
is further broken down into marker and minor classifactions.
For example, all the markers for a map would begin with marker-.
The label assigned to the marker in this example is “example” so
its data would be returned in the x-wscompose-marker-example
header.
# python meta = {} re_xheader = re.compile(r"^x-wscompose-", re.IGNORECASE) for key, value in res.getheaders() : if re_xheader.match(key) : parts = key.split("-") parts = map(string.lower, parts) major = parts[2] minor = parts[3] if not meta.has_key(major) : meta[major] = {} meta[major][minor] = value
# php list($head, $data) = explode("\r\n\r\n", $res, 2); $map = imagecreatefromstring($data); $meta = array(); foreach (explode("\r\n", $head) as $ln){ list($k, $v) = explode(":", $ln, 2); $k = trim($k); $v = trim($v); if (preg_match("/^x-wscompose-/i", $k)){ $parts = explode("-", $k); $major = strtolower($parts[2]); $minor = strtolower($parts[3]); if (! isset($meta[$major])){ $meta[$major] = array(); } $meta[$major][$minor] = $v; } }
Most headers are self-explanatory; markers are a little more
complicated. The string after x-wscompose-marker- is the label assigned to the
marker when the API call was made. The value is a comma separated list
interpreted as follows :
Pick out the x and y offset that we can use as the top left corner where we will paste the original image on to the pinwin marker.
# python coords = map(int, meta['marker']['example'].split(",")) x_off = coords[2] y_off = coords[3]
# php $coords = explode(",", $meta['marker']['example']); $x_off = $coords[2]; $y_off = $coords[3];
(Finally) paste the source image in to the map and display it!
# python map_data = res.read() map_obj = Image.open(StringIO.StringIO(map_data)) map_obj.paste(image_obj, (x_off, y_off)) map_obj.show()
# php imagecopy($map, $im, $x_off, $y_off, 0, 0, $im_width, $im_height); imagepng($map);
Profit!