Skip to content

More fun with the Yahoo! Maps API

I spent a few hours with the Javascript-Flash Yahoo! Maps API today. The result is here:

      http://lerdorf.com/map

Have a look at the source. There is a source link at the top-right on the page. It is about 90 lines of HTML and Javascript and virtually no server-side scripting.
I usually throw PHP code at all sorts of problems, but in this case I could do pretty much everything I wanted to directly from Javascript via the excellent API. The initial zooming from way out is a bit distracting and doesn't work too well, but I wanted to play with that a bit. Once you are zoomed in and searching for things, the search is updated as you move around the map and everything is event-based so the page doesn't need to be redrawn from scratch.


Both input fields are free-form. You can put a city name, a zip code or a full address in the Location field and in the What field you put what you are looking for. There is a special hack that checks for something like 4* and the filters the results to only show you those entries that were rated 4* or higher on Yahoo! Local. You can of course also just put something like "pizza" or "mexican" in that field.


Most of the magic here is done by 2 things. The LocalSearchOverlay and the event handling. Note how Map.EVENT_MOVE and Map.EVENT_ZOOM_END are registered events in the onInitialize() function. When you scroll the map or zoom it the onOverlayInit function will get called and the LocalSearchOverlay will be recalculated for the new map coordinates. Same thing happens when you change something in the input fields. The updateMap() function is called which will center the map at the new location and update the LocalSearchOverlay appropriately. There are a few more Javascript tricks here and there in it, like updating the link at the top so you always have a way to grab a link to your current search and send it to someone, but other than that there really isn't all that much to figure out here. Once you understand which events happen when and which methods are available where, you can do some really powerful things with this. It is all documented here:

http://developer.yahoo.net/maps/flash/V2/flashReference.html

and people are discussing it here:

http://groups.yahoo.com/group/yws-maps/messages


In my last entry I showed how to parse a geocoded XML file and put markers for each entry on the map. Someone asked me how I would do the using the JS-DHTML API instead of the JS-Flash API. It's a whole lot harder in DHTML, but it works. You can see it here:

http://lerdorf.com/php/ymap/dquakes.php

And I talk a bit about it here:

http://groups.yahoo.com/group/yws-maps/message/612

GeoCool!

Web 2.0 and the programmable web that I and others have been talking about for a while has mostly been vapourware so far. There are a few generic components that are useful, but it is somewhat limited what you can do with them. And yes, you may consider this a somewhat biased view, but I think Yahoo!'s new geocoding platform is a huge step in the right direction.


There is of course the fancy new maps.yahoo.com/beta site which is fun, but as far as I am concerned the killer app here is the geocoding platform that drives this. And it is completely accessible for anyone to use. It's also a sane API that anybody can figure out in minutes. Here are a few tips for using this API from PHP 5.


Step 0 - The raw geocoding API

Whenever I do anything with web services, I always add a request caching layer. So here are the base building blocks implemented in 2 functions. One for doing request caching and the second to do the actual REST query to the geocoding service.

<?php
function request_cache($url$dest_file$timeout=43200) {
  if(!
file_exists($dest_file) || filemtime($dest_file) < (time()-$timeout)) {
    
$stream fopen($url,'r');
    
$tmpf tempnam('/tmp','YWS');
    
file_put_contents($tmpf$stream);
    
fclose($stream);
    
rename($tmpf$dest_file);
  }
}

function 
yahoo_geo($location) {
  
$q 'http://api.local.yahoo.com/MapsService/V1/geocode';
  
$q .= '?appid=rlerdorf&location='.rawurlencode($location);
  
$tmp '/tmp/yws_geo_'.md5($q);
  
request_cache($q$tmp43200);
  
libxml_use_internal_errors(true);
  
$xml simplexml_load_file($tmp); 
  
$ret['precision'] = (string)$xml->Result['precision'];
  foreach(
$xml->Result->children() as $key=>$val) {
    if(
strlen($val)) $ret[(string)$key] =  (string)$val;
  } 
  return 
$ret;
}
?>
The above code is the contents of geo.inc which you will see included in the following examples.


Easy enough? No real tricks here. We simply send a regular GET request to http://api.local.yahoo.com/MapsService/V1/geocode with the location parameter set to an address. You can try it yourself directly from your browser by clicking here:

http://api.local.yahoo.com/MapsService/V1/geocode?appid=rlerdorf&location=701%20First%20Ave,%2094089


You can read more about the geocoding service here:

http://developer.yahoo.net/maps/rest/V1/geocode.html


Step 1 - Writing your first application

We can just toss a form around this and dump the results to make sure things are working.

<html>
<head>
<title>GeoCoding API Example</title>
</head>
<body>
<form action="/php/ymap/geo1.php" method="GET">
<input type="text" size="80" name="location" />
</form>
<?php
include './geo.inc';
if(!empty(
$_REQUEST['location'])) {
  
$a yahoo_geo($_REQUEST['location']);
  echo 
"<pre>"print_r($a); echo "</pre>";
}
?>
</body></html>
You can see this one in action here:

http://lerdorf.com/php/ymap/geo1.php


Note how it is able to fill in missing details for a partial address. eg.

http://lerdorf.com/php/ymap/geo1.php?location=701+First+Avenue+94089

results in:

    [precision] => address
    [Latitude] => 37.416384
    [Longitude] => -122.024853
    [Address] => 701 FIRST AVE
    [City] => SUNNYVALE
    [State] => CA
    [Zip] => 94089-1019
    [Country] => US
This means that you can use it for a bunch of different things. Address to lat/long, of course, but also address to city, or city to zip code conversions. Or 5-digit zip to 5+4. This is of course rather US-centric right now, but that will improve over time.



Step 2 - Adding a map

The geocoding is cool, but an actual map is cooler. Easy enough:

<html><head>
<script type="text/javascript" src="http://api.maps.yahoo.com/v2.0/fl/javascript/apiloader.js"></script>
<style type="text/css">
#mapContainer { 
height: 600px; 
width: 800px; 

</style> 
<title>GeoCoding API Example</title>
</head><body>
<form action="/php/ymap/geo2.php" method="GET">
<input type="text" size="80" name="location" />
</form>
<?php
include './geo.inc';
if(!empty(
$_REQUEST['location'])) {
  
$a yahoo_geo($_REQUEST['location']);
  echo 
"[ {$a['Latitude']}, {$a['Longitude']} ] {$a['precision']}-level coordinate accuracy<br />\n";
  if(!empty(
$a['Address'])) echo $a['Address'].', ';
  if(!empty(
$a['City'])) echo $a['City'].', ';
  if(!empty(
$a['State'])) echo $a['State'].' ';
  if(!empty(
$a['Zip'])) echo $a['Zip'].' ';
  if(!empty(
$a['Country'])) echo $a['Country'].' ';
}
?>
<div id="mapContainer"></div>
<script type="text/javascript">
var latlon = new LatLon(<?php echo $a['Latitude']?><?php echo $a['Longitude']?>);
var map = new Map("mapContainer", "rlerdorf", latlon, 3);
map.addTool( new PanTool(), true );
</script>
We are using the Flash-Javascript API here. Try it out!

http://lerdorf.com/php/ymap/geo2.php?location=701+First+Avenue%2C+94089

And yes, of course the map is draggable. The PanTool() part of the above script adds the panning feature.


This is an API that lets you embed a Flash-based map, but control it with Javascript. It's quite cool even if you think Flash sucks. It is described at:

http://developer.yahoo.net/maps/flash/jsGettingStarted.html

and the AJAX-DHTML API is described at:

http://developer.yahoo.net/maps/ajax/index.html


Step 3 - Making the map prettier

The map looks a bit bare. We don't see our address marker, for example. So let's add that.

<html><head>
<script type="text/javascript" src="http://api.maps.yahoo.com/v2.0/fl/javascript/apiloader.js"></script>
<style type="text/css">
#mapContainer { 
height: 600px; 
width: 800px; 

</style> 
<title>GeoCoding API Example</title>
</head><body>
<form action="/php/ymap/geo3.php" method="GET">
<input type="text" size="80" name="location" />
</form>
<?php
include './geo.inc';
if(!empty(
$_REQUEST['location'])) {
  
$a yahoo_geo($_REQUEST['location']);
  echo 
"[ {$a['Latitude']}, {$a['Longitude']} ] {$a['precision']}-level coordinate accuracy<br />\n";
  
$mtitle ''
  if(!empty(
$a['Address'])) { 
    echo 
$a['Address'].', '
    
$mtitle $a['Address'];
  }
  if(!empty(
$a['City'])) {
    echo 
$a['City'].', ';
    if(!
$mtitle$mtitle $a['City'];
  }
  if(!empty(
$a['State'])) echo $a['State'].' ';
  if(!empty(
$a['Zip'])) echo $a['Zip'].' ';
  if(!empty(
$a['Country'])) echo $a['Country'].' ';
  
$info str_replace("\n",'',nl2br(print_r($a,true)));
}
?>
<div id="mapContainer"></div>
<script type="text/javascript">
var latlon = new LatLon(<?php echo $a['Latitude']?><?php echo $a['Longitude']?>);
var mymap = new Map("mapContainer", "rlerdorf", latlon, 3);
mymap.addTool( new PanTool(), true );
marker1 = new CustomPOIMarker('A','<?php echo $mtitle?>', '<?php echo $info?>', '0x0012f0', '0x88CCFF'); 
mymap.addMarkerByLatLon(marker1, latlon);
</script>
</body></html>
There is a lot of stuff there, but all I really changed was a bit of code related to picking information out of the address so I can fill in the expanded marker, and then the marker code. The last 2 lines of the Javascript there that creates a new CustomPOIMarker and then uses addMarkerByLatLon to the map does the trick. When you mouse over it, it will expand to show the title ($mtitle) and when you click on it, it will show the contents of $info.



Step 4 - But but, why Flash?

Aside from portability and less DHTML browser quirks, it gives us widgets! We can add the Navigation widget very easily. 2 lines of Javascript:

  navWidget = new NavigatorWidget(); 
  mymap.addWidget(navWidget); 
That's all. Have a look at it now:

http://lerdorf.com/php/ymap/geo4.php?location=701+First+Avenue%2C+94089



But if the thought of Flash still makes your skin crawl. No worries. You can get pretty close to the Flash version with straight DHTML. Here is the geo4 demo using the DHTML-AJAX API:

http://lerdorf.com/php/ymap/dgeo4.php?location=701+First+Avenue%2C+94089



Step 5 - Something real and useful

By using nothing more than what I have showed so far you can build this:

http://lerdorf.com/php/ymap/yquakes.php

This uses the simple_rss parser I wrote a while ago. You can see the source for the RSS parser here:

http://lerdorf.com/php/simple_rss.phps

The code then just loops through the entries for the earthquakes and adds a marker for each quake. A very simple little application:

<html><head>
<script type="text/javascript" src="http://api.maps.yahoo.com/v2.0/fl/javascript/apiloader.js"></script>
<style type="text/css">
#mapContainer { 
height: 600px; 
width: 800px; 

</style> 
<?php
include '/usr/local/php5/lib/php/simple_rss.php';

$url 'http://earthquake.usgs.gov/recenteqsww/catalogs/eqs7day-M2.5.xml';
$feed rss_request($url$timeout=3600);
echo <<<EOB
<title>{$feed['title'][0]}</title>
</head><body>
<h1>
{$feed['title'][0]}</h1>
<p><font size="+2">
{$feed['description'][0]}<br />
{$feed['pubDate'][0]}</font></p>
EOB;
?>
<div id="mapContainer"></div>
<script type="text/javascript">
var latlon = new LatLon(37.416384, -122.024853);
var mymap = new Map("mapContainer", "rlerdorf", latlon, 13);
mymap.addTool( new PanTool(), true );
navWidget = new NavigatorWidget(); 
mymap.addWidget(navWidget); 
<?php 
  $i 
0;
  while(!empty(
$feed[$i])) {
    
$info  $feed[$i]['description'][0]."<br />";
    
$info .= '<a href="'.$feed[$i]['link'][0].'">'.$feed[$i]['link'][0]."</a>";
?>  
mymap.addMarkerByLatLon(
   new CustomPOIMarker('<?php echo $i?>',     '<?php echo $feed[$i]['title'][0]?>', '<?php echo $info?>', '0x0012f0', '0xFFFFFF'),
   new LatLon(<?php echo $feed[$i]['lat'][0].','.$feed[$i]['long'][0]?>));
<?php
    $i
++;
  }
?>
</script>


You can also let the API figure out your markers for you which makes this even simpler. If the RSS feed is using georss correctly you can use the GeoRSSOverlay mechanism. Here it is using the earthquake RSS feed directly:

http://lerdorf.com/php/ymap/rssquakes.php

And here is the code. I am still loading the RSS feed myself from PHP because I want to get the pubDate and title from it, but everything else is handled automatically.

<html><head>
<script type="text/javascript" src="http://api.maps.yahoo.com/v2.0/fl/javascript/apiloader.js"></script>
<style type="text/css">
#mapContainer { 
height: 600px; 
width: 800px; 

</style> 
<?php
include '/usr/local/php5/lib/php/simple_rss.php';

$url 'http://earthquake.usgs.gov/recenteqsww/catalogs/eqs7day-M2.5.xml';
$feed rss_request($url$timeout=3600);
echo <<<EOB
<title>{$feed['title'][0]}</title>
</head><body>
<h1>
{$feed['title'][0]}</h1>
<p><font size="+2">
{$feed['description'][0]}<br />
{$feed['pubDate'][0]}</font></p>
EOB;
?>
<div id="mapContainer"></div>
<script type="text/javascript">
var latlon = new LatLon(37.416384, -122.024853);
var mymap = new Map("mapContainer", "rlerdorf", latlon, 13);
mymap.addTool(new PanTool(), true);
navWidget = new NavigatorWidget(); 
mymap.addWidget(navWidget); 
overlay = new GeoRSSOverlay('http://earthquake.usgs.gov/recenteqsww/catalogs/eqs7day-M2.5.xml');           
mymap.addOverlay(overlay);
</script>
</body></html>

There are an amazing number of things you can do with this API. What I have described here is just the surface of it. Overlays and events can do nifty things. You can even get at the low-level tile api directly using this:

http://developer.yahoo.net/maps/rest/V1/mapImage.html

I am very much looking forward to see what people out there end up doing with this. I timed how long it took me to write the Earthquake mapping application above. 21 minutes from the time I started looking for the geotagged earthquake data until I was happy with the final app. And that included normal office interruptions and tracking down a dumb syntax error. Note also that any local.yahoo search through the API now includes lat/long info and Flickr has lat/long information as well. And even if a service doesn't provide geotags, as long as they provide addresses you can use the geocoding api to get the lat/long data and do interesting geospatial things with it. Getting to the point where we have full and trivial control over geocoding and mapping opens up a whole new class of application.