<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
  <channel>
    <title>Derick Rethans - tag: openstreetmap</title>
    <link>http://derickrethans.nl/feed-openstreetmap.xml</link>
    <description>This feed shows the latest 15 items with the tag openstreetmap</description>
    <language>en-us</language>
    <copyright>All rights reserved - Derick Rethans</copyright>
    <managingEditor>derick@derickrethans.nl (Derick Rethans)</managingEditor>
    <pubDate>Wed, 12 Jun 2013 10:00:16 +0000</pubDate>
    <lastBuildDate>Wed, 12 Jun 2013 10:00:16 +0000</lastBuildDate>
    <generator>eZ Components Feed dev (http://ezcomponents.org/docs/tutorials/Feed)</generator>
    <docs>http://www.rssboard.org/rss-specification</docs>
    <ttl>60</ttl>
    <item>
      <title>Importing OpenStreetMap data into MongoDB</title>
      <link>http://derickrethans.nl/importing-osm-into-mongodb.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="importing_openstreetmap_data_into_mongodb"/&gt;Importing OpenStreetMap data into MongoDB&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Tuesday, June 11th 2013, 09:49 BST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;In many recent &lt;a href="http://mongodb.org"&gt;MongoDB&lt;/a&gt; related &lt;a href="http://derickrethans.nl/talks.html"&gt;presentations&lt;/a&gt; I have used &lt;a href="http://openstreetmap.org"&gt;OpenStreetMap&lt;/a&gt; data as basis for most of my examples. I wrote a script that imports OpenStreetMap (OSM) nodes, ways and to a lesser extend relations into MongoDB with a specific and optimal schema. I have written about this briefly before in &lt;a href="http://derickrethans.nl/indexing-free-tags.html"&gt;Indexing Freeform-Tagged Data&lt;/a&gt;, but now MongoDB received numerous updates to geospatial indexes I find it warrants a new article.&lt;/p&gt;
      &lt;p&gt;In MongoDB 2.2 and before, the index type that MongoDB used was the &lt;em&gt;2d&lt;/em&gt; type, which was basically a two dimensional flat earth coordinate system index—with some spherical features built on top. MongoDB 2.4 has a new index type: &lt;em&gt;2sphere&lt;/em&gt;. Instead of just being able to index points described by x/y coordinates (longitude/latitude) it now has support for indexing points, &lt;em&gt;line strings&lt;/em&gt; and &lt;em&gt;polygons&lt;/em&gt; as defined by &lt;a href="http://www.geojson.org/"&gt;GeoJSON&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;I have modified my import script to use those new types, and also added support for very simple multi-polygons that OpenStreetMap records through its &lt;em&gt;relation&lt;/em&gt; tag. The script also creates an indexes on &lt;em&gt;{ l: '2dsphere' }&lt;/em&gt; (the GeoJson object), &lt;em&gt;{ ts: 1 }&lt;/em&gt; (the tags), and &lt;em&gt;{ ty: 1 }&lt;/em&gt; (the type).&lt;/p&gt;
      &lt;p&gt;The structure it converts a &lt;a href="http://www.openstreetmap.org/browse/node/26486695"&gt;node&lt;/a&gt; from OpenStreetMap to looks like:&lt;/p&gt;
      &lt;pre&gt;{
        "_id" : "n26486695",
        "ty" : NumberLong(1),
        "l" : {
                "type" : "Point",
                "coordinates" : [
                        -0.1580359,
                        51.4500055
                ]
        },
        "ts" : [
                "addr:housenumber=97",
                "addr:postcode=SW12 8NX",
                "addr:street=Nightingale Lane",
                "amenity=pub",
                "name=The Nightingale",
                "operator=Youngs",
                "source:name=photograph",
                "toilets=yes",
                "toilets:access=customers",
                "website=http://www.youngs.co.uk/pub-detail.asp?PubID=430"
        ],
        "m" : {
                "v" : NumberLong(5),
                "cs" : NumberLong(11229430),
                "uid" : NumberLong(652021),
                "ts" : NumberLong(1333911628)
        }
}

&lt;/pre&gt;
      &lt;p&gt;There are several sections that make up the document:&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;em&gt;_id&lt;/em&gt;: Is a combination of &lt;em&gt;n&lt;/em&gt; and the OSM node id.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;em&gt;ty&lt;/em&gt;: Is the type. For nodes, this is always &lt;em&gt;1&lt;/em&gt;.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;em&gt;l&lt;/em&gt;: The point's location in GeoJson format. An OSM point is translated to a GeoJson &lt;em&gt;Point&lt;/em&gt; feature with an array describing the longitude and latitude.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;em&gt;ts&lt;/em&gt;: Are the tags that describe the node. Each tag is stored as a concatenation of its &lt;em&gt;key&lt;/em&gt; and its &lt;em&gt;value&lt;/em&gt;. This creates both a smaller index and it still allows for exact tag/value matches as well as matching against specfic keys through a regular expression match. For example, we could find the above document with:&lt;/p&gt;
          &lt;p&gt;
            &lt;code&gt;db.poiConcat.find( { ts: 'name=The Nightingale' } );&lt;/code&gt;
          &lt;/p&gt;
          &lt;p&gt;And the index would also be used when we look for all amenities:&lt;/p&gt;
          &lt;p&gt;
            &lt;code&gt;db.poiConcat.find( { ts: /^amenity=/ } );&lt;/code&gt;
          &lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;em&gt;m&lt;/em&gt;: Contains meta information that describes the node. The following fields are currently present:&lt;/p&gt;
          &lt;ul&gt;
            &lt;li&gt;
              &lt;p&gt;&lt;em&gt;v&lt;/em&gt;: The object's version. This is the version number of the version that was found in the imported file. There is always just one version per object.&lt;/p&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;p&gt;&lt;em&gt;cs&lt;/em&gt;: The changeset ID in which this object was last updated.&lt;/p&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;p&gt;&lt;em&gt;uid&lt;/em&gt;: The user ID of the OpenStreetMap contributor who uploaded the latest version.&lt;/p&gt;
            &lt;/li&gt;
            &lt;li&gt;
              &lt;p&gt;&lt;em&gt;ts&lt;/em&gt;: The Unix timestamp of when this object was last updated.&lt;/p&gt;
            &lt;/li&gt;
          &lt;/ul&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;OpenStreetMap ways are stored in two different types. &lt;a href="http://www.openstreetmap.org/browse/way/2423886"&gt;Unclosed ways&lt;/a&gt; are stored as &lt;a href="http://www.geojson.org/"&gt;GeoJSON&lt;/a&gt; LineStrings (think roads):&lt;/p&gt;
      &lt;pre&gt;{
        "_id" : "w2423886",
        "ty" : NumberLong(2),
        "l" : {
                "type" : "LineString",
                "coordinates" : [
                        [ -0.1044769, 51.508462 ],
                        [ -0.1044093, 51.5106306 ],
                        [ -0.1044139, 51.5107814 ],
                        [ -0.104427, 51.5108453 ],
                        [ -0.1044459, 51.5109208 ],
                        [ -0.1045131, 51.5110686 ]
                ]
        },
…

&lt;/pre&gt;
      &lt;p&gt;And &lt;a href="http://www.openstreetmap.org/browse/way/24257746"&gt;closed ways&lt;/a&gt; are stored as &lt;a href="http://www.geojson.org/"&gt;GeoJSON&lt;/a&gt; Polygons (think buildings and parks):&lt;/p&gt;
      &lt;pre&gt;{
    "_id" : "w24257746",
    "ty" : NumberLong(2),
    "l" : {
        "type" : "Polygon",
        "coordinates" : [
            [
                [ -0.0745133, 51.560977 ],
                [ -0.0742252, 51.5609742 ],
                [ -0.0742308, 51.5606721 ],
                [ -0.0745217, 51.5606721 ],
                [ -0.0745133, 51.560977 ]
            ]
        ]
    },
    "ts" : [
        "amenity=park",
        "leisure=park",
        "name=Kynaston Gardens"
    ],
    "m" : {
        "v" : NumberLong(1),
        "cs" : NumberLong(357805),
        "uid" : NumberLong(5139),
        "ts" : NumberLong(1210169336)
    }
}

&lt;/pre&gt;
      &lt;p&gt;Both ways and areas (closed ways) will have a &lt;em&gt;ty&lt;/em&gt; value of 2, as they both come from a &lt;em&gt;way&lt;/em&gt; primitive as stored in OpenStreetMap.&lt;/p&gt;
      &lt;p&gt;The script is available on GitHub as part of the &lt;a href="https://github.com/derickr/3angle"&gt;3angle&lt;/a&gt; repository. The latest version is at &lt;a href="https://raw.github.com/derickr/3angle/master/import-data.php"&gt;https://raw.github.com/derickr/3angle/master/import-data.php&lt;/a&gt; and it also requires &lt;a href="https://raw.github.com/derickr/3angle/master/classes.php"&gt;https://raw.github.com/derickr/3angle/master/classes.php&lt;/a&gt; for some GeoJSON helper classes and &lt;a href="https://raw.github.com/derickr/3angle/master/config.php"&gt;https://raw.github.com/derickr/3angle/master/config.php&lt;/a&gt; where you can set the database name and collection name (in my case, &lt;em&gt;demo&lt;/em&gt; and &lt;em&gt;poiConcat&lt;/em&gt;).&lt;/p&gt;
      &lt;p&gt;
        &lt;em&gt;Map data © OpenStreetMap contributors (&lt;a href="http://www.openstreetmap.org/copyright"&gt;terms&lt;/a&gt;).&lt;/em&gt;
      &lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201306110949</guid>
      <pubDate>Tue, 11 Jun 2013 08:49:00 +0000</pubDate>
    </item>
    <item>
      <title>Crowd-serfing</title>
      <link>http://derickrethans.nl/google-map-maker.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="crowd-serfing"/&gt;Crowd-serfing&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Friday, April 12th 2013, 09:27 BST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;Yesterday, Google &lt;a href="http://google-latlong.blogspot.co.uk/2013/04/welcoming-united-kingdom-to-google-map.html"&gt;announced&lt;/a&gt; that they have made Google Map Maker available in the United Kingdom. Like &lt;a href="http://openstreetmap.org"&gt;OpenStreetMap&lt;/a&gt; it allows everybody to update and add things to the map. But there is one big difference: With MapMaker you don't get access to the data.&lt;/p&gt;
      &lt;p&gt;Fellow OpenStreetMapper, &lt;a href="http://systemed.net/"&gt;Richard Fairhurst&lt;/a&gt;, describes this as Crowd-serfing:&lt;/p&gt;
      &lt;blockquote&gt;
        &lt;p&gt;Crowd-serfing, n.: when a large corp uses crowd-sourced volunteering for its own financial gain, without giving back. See: @googlemapmaker.&lt;/p&gt;
      &lt;/blockquote&gt;
      &lt;p&gt;I will never understand why people do work for a commercial company without getting any real benefit back: &lt;a href="http://www.bbc.co.uk/news/business-21226623"&gt;http://www.bbc.co.uk/news/business-21226623&lt;/a&gt;&lt;/p&gt;
      &lt;p&gt;Unfortunately, today's BBC &lt;a href="http://www.bbc.co.uk/news/technology-22099960"&gt;coverage&lt;/a&gt; on the availability of Google MapMaker in the UK read more like a manual on MapMaker than a nicely unbiased piece on crowd-sourced maps. Only after one of the OpenStreetMappers reached out to the journalist that wrote the piece, they added some background on &lt;a href="http://openstreetmap.org"&gt;OpenStreetMap&lt;/a&gt;:&lt;/p&gt;
      &lt;blockquote&gt;
        &lt;p&gt;"The biggest problem with Google Map Maker is that anything people contribute may appear on Google's map, but only Google can get at the underlying data to be able to do anything else with it," said &lt;a href="http://chris-osm.blogspot.co.uk/"&gt;Chris Hill&lt;/a&gt;.&lt;/p&gt;
        &lt;p&gt;"If someone includes a Google map on their web site to show where their business is they may also be showing where their competitors are and they can't change that."&lt;/p&gt;
      &lt;/blockquote&gt;
      &lt;p&gt;Sure, it's nice to have some roads on the Google Map, but you will never even have full access back to your data, unlike OpenStreetMap where you can download and work with all the &lt;a href="http://planet.openstreetmap.org/"&gt;data&lt;/a&gt;. Even nicer is that often, OpenStreetMap still has better maps than GoogleMaps - for example, have a look at this in North Korea: &lt;a href="http://tools.geofabrik.de/mc/?mt0=mapnik&amp;mt1=googlemap&amp;lon=125.74677&amp;lat=39.01863&amp;zoom=14"&gt;http://tools.geofabrik.de/mc/?mt0=mapnik&amp;mt1=googlemap&amp;lon=125.74677&amp;lat=39.01863&amp;zoom=14&lt;/a&gt; . And closer to home in the United Kingdom, compare some of the hiking trails in the Peak District: &lt;a href="http://tools.geofabrik.de/mc/?mt0=mapnik&amp;mt1=googlemap&amp;lon=-1.99876&amp;lat=53.17827&amp;zoom=15"&gt;http://tools.geofabrik.de/mc/?mt0=mapnik&amp;mt1=googlemap&amp;lon=-1.99876&amp;lat=53.17827&amp;zoom=15&lt;/a&gt; . Even in places like London, the accuracy of the locations of addresses and points-of-interest (POIs) is often a lot better, as OpenStreetMap doesn't use web site scraping and post-code-centroid locations to place POIs: &lt;a href="http://tools.geofabrik.de/mc/?mt0=mapnik&amp;mt1=googlemap&amp;lon=-0.12405&amp;lat=51.50862&amp;zoom=18"&gt;http://tools.geofabrik.de/mc/?mt0=mapnik&amp;mt1=googlemap&amp;lon=-0.12405&amp;lat=51.50862&amp;zoom=18&lt;/a&gt; . OpenStreetMap mostly relies on surveys, done by individuals (like you!) to verify things are actually there, aided a little by the availability of &lt;a href="http://www.bing.com/maps/"&gt;Bing Maps&lt;/a&gt; as background imagery.&lt;/p&gt;
      &lt;p&gt;One of the things that most people forget, is the terms and conditions that commercial entities state. An except from MapMaker's reads:&lt;/p&gt;
      &lt;blockquote&gt;
        &lt;p&gt;"You give Google a perpetual, irrevocable, worldwide, royalty-free, and non-exclusive licence to reproduce, adapt, modify, translate, publish, publicly perform, publicly display, distribute, and create derivative works of the user submission."&lt;/p&gt;
      &lt;/blockquote&gt;
      &lt;p&gt;Note that it never mentions that you can do anything with the data yourself…&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/stamen-hydepark.jpg" class="left" alt="stamen-hydepark.jpg"/&gt;
      &lt;p&gt;If you are contributing time and knowledge, why not allow yourself to benefit from it as well? I realise that OpenStreetMap might not be as accessible, and the map-tiles on their web site aren't the prettiest, but the real benefit is in the access to the raw data that makes up the images in the map. The OpenStreetMap wiki also has an &lt;a href="http://wiki.openstreetmap.org/wiki/Google_Map_Maker"&gt;article&lt;/a&gt; on this.&lt;/p&gt;
      &lt;p&gt;Access to the data allows you to do so many more things. From creating your own fancy &lt;a href="http://maps.stamen.com/#watercolor/13/51.5068/-0.1055"&gt;map-styles&lt;/a&gt;, creating &lt;em&gt;"washable, wearable, all-weather maps designed for the real outdoors"&lt;/em&gt; such as &lt;a href="http://www.splashmaps.net/"&gt;SplashMaps&lt;/a&gt;, to powering web-sites that show &lt;a href="http://wheelmap.org/"&gt;accessibility&lt;/a&gt;. If you don't like the way map data is rendered, you can produce something in your own style, just like Nike did with this &lt;a href="http://blog.oobrien.com/2010/04/nike-grid/"&gt;campaign&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;Also, there is nothing better if some of your &lt;a href="http://osm.org/go/ZUfFRnGPx-"&gt;handiwork&lt;/a&gt; shows up in a &lt;a href="http://bestofosm.org/poster/"&gt;"best of OSM" poster&lt;/a&gt; :-) I would never spend my time adding data to Google's Maps. Instead, I prefer to contribute to OpenStreetMap and &lt;a href="https://vimeo.com/56374742"&gt;do awesome things&lt;/a&gt; with the data. In the meanwhile, OpenStreetMap continues to support humanitarian relief efforts in &lt;a href="https://vimeo.com/61282773"&gt;Mali&lt;/a&gt; and many other &lt;a href="http://hot.openstreetmap.org/"&gt;places&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;If you're near London, come and join us this &lt;a href="http://wiki.openstreetmap.org/wiki/London/Summer_2013_events"&gt;summer&lt;/a&gt;! (Other places will also run mapping parties).&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201304120927</guid>
      <pubDate>Fri, 12 Apr 2013 08:27:00 +0000</pubDate>
    </item>
    <item>
      <title>OpenStreetMap: A Year of Edits 2012</title>
      <link>http://derickrethans.nl/year-of-edits-2012.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="openstreetmap_a_year_of_edits_2012"/&gt;OpenStreetMap: A Year of Edits 2012&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Tuesday, January 1st 2013, 00:04 GMT&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;Just like &lt;a href="http://derickrethans.nl/year-of-edits.html"&gt;last year&lt;/a&gt; I've analysed all the edits to OpenStreetMap and produced a visualisation. It was a bit trickier this year as I also wanted to visualize the work of the redaction bot as well as the "redaction period" and the switch to &lt;a href="http://en.wikipedia.org/wiki/ODbL"&gt;ODbL&lt;/a&gt; for OpenStreetMap data.&lt;/p&gt;
      &lt;p&gt;The result is: &lt;strong&gt;OpenStreetMap: A Year of Edits (2012)&lt;/strong&gt;:&lt;/p&gt;
      &lt;div class="video"&gt;
        &lt;iframe src="http://player.vimeo.com/video/56374742?title=0&amp;byline=0&amp;portrait=0" width=" 599" height=" 337" frameborder="0"/&gt;
        &lt;p&gt;&lt;a href="http://vimeo.com/56374742"/&gt;OpenStreetMap: A Year of Edits (2012)&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;This animation shows all additions and modifications of nodes (flashes) and cumulative edits (fading to purple) in 2012. Additional visualisations are the redaction bot's work (flying away yellow spheres) and the shift to ODbL (green flash). It gives a good overview of the effort that 1000s of contributors put in to make OpenStreetMap the best source of mapping data.&lt;/p&gt;
      &lt;p&gt;You can see the video in HD on &lt;a href="http://vimeo.com/derickr/osm-2012"&gt;Vimeo&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;I have started to make the code for this available through GitHub, but the &lt;a href="https://github.com/derickr/osm-year-in-edits"&gt;code&lt;/a&gt; is very much not modularized and it is likely it will only work on my set-up. Expect this to change for next year!&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Mappy New Year!&lt;/strong&gt;
      &lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201301010004</guid>
      <pubDate>Tue, 01 Jan 2013 00:04:00 +0000</pubDate>
    </item>
    <item>
      <title>Address lookups with Leaflet and Nominatim</title>
      <link>http://derickrethans.nl/leaflet-and-nominatim.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="address_lookups_with_leaflet_and_nominatim"/&gt;Address lookups with Leaflet and Nominatim&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Tuesday, November 20th 2012, 10:52 GMT&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;I recently wrote a &lt;a href="https://github.com/joindin/joind.in/pull/554"&gt;patch&lt;/a&gt; for &lt;a href="http://joind.in/"&gt;joind.in&lt;/a&gt; to add a map of an event's location to the event detail page. With the same patch, I also replaced the location part of the event edit page with a solution that uses &lt;a href="http://jquery.com"&gt;JQuery&lt;/a&gt;, &lt;a href="http://leafletjs.com"&gt;Leaflet&lt;/a&gt; as map API, &lt;a href="http://openstreetmap.org"&gt;OpenStreetMap&lt;/a&gt; &lt;a href="http://wiki.openstreetmap.org/wiki/Tiles"&gt;tiles&lt;/a&gt; and &lt;a href="http://nominatim.openstreetmap.org/"&gt;Nominatim&lt;/a&gt; for doing address lookups.  This article forms a small tutorial on how to use this same set-up yourself.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;The Basics&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;To start, we create a new directory for our project:&lt;/p&gt;
      &lt;pre&gt;mkdir addresses
cd addresses

&lt;/pre&gt;
      &lt;p&gt;Then I downloaded the Leaflet and jQuery libraries and extracted them in the &lt;code&gt;js&lt;/code&gt; directory of the project:&lt;/p&gt;
      &lt;pre&gt;mkdir js
curl -L https://github.com/CloudMade/Leaflet/zipball/v0.4.5 -o leaflet.zip
unzip leaflet.zip
mv CloudMade-Leaflet-*/dist/* js
rm -rf CloudMade-Leaflet-*
rm leaflet.zip
curl http://code.jquery.com/jquery-1.8.2.min.js -o js/jquery-1.8.2.min.js

&lt;/pre&gt;
      &lt;p&gt;As first step, we are simply going to show a map on a web page. The map is going to be full screen, and will not have any bells and whistles. The code to embed a map is small, but we will separate it into three files for clarity: a CSS file for our styles (&lt;code&gt;site.css&lt;/code&gt;), an HTML file for the structure (&lt;code&gt;index.html&lt;/code&gt;) and a JS file for all our JavaScript functions (&lt;code&gt;js/map.js&lt;/code&gt;).&lt;/p&gt;
      &lt;p&gt;Let's start with the HTML file:&lt;/p&gt;
      &lt;pre&gt;&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Leaflet and Nominatim example&lt;/title&gt;

    &lt;link rel="stylesheet" href="js/leaflet.css" /&gt;
    &lt;!--[if lte IE 8]&gt;&lt;link rel="stylesheet" href="js/leaflet.ie.css" /&gt;&lt;![endif]--&gt;
    &lt;link rel="stylesheet" type="text/css" href="site.css"&gt;

    &lt;script src="js/leaflet.js"&gt;&lt;/script&gt;
    &lt;script src="js/jquery-1.8.2.min.js"&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id="map"/&gt;
    &lt;script src="js/map.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;

&lt;/pre&gt;
      &lt;p&gt;This HTML file includes the &lt;a href="http://leafletjs.com"&gt;Leaflet&lt;/a&gt; and &lt;a href="http://jquery.com"&gt;jQuery&lt;/a&gt; libraries, as well as the default CSS file that Leaflet needs.  We are also including our own CSS file (&lt;code&gt;site.css&lt;/code&gt;):&lt;/p&gt;
      &lt;pre&gt;body {
  margin: 0;
}
div#map {
  width: 100%;
  height: 100%;
}

&lt;/pre&gt;
      &lt;p&gt;In the body of the HTML file, we place a &lt;code&gt;&lt;div&gt;&lt;/code&gt; as contained for the map, and then include a JavaScript file that is responsible for embedding them map:&lt;/p&gt;
      &lt;pre&gt;var map;

function load_map() {
  map = new L.Map('map', {zoomControl: false});

  var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
    osmAttribution = 'Map data &amp;copy; 2012 &lt;a href="http://openstreetmap.org"&gt;OpenStreetMap&lt;/a&gt; contributors',
    osm = new L.TileLayer(osmUrl, {maxZoom: 18, attribution: osmAttribution});

  map.setView(new L.LatLng(51.538594, -0.198075), 12).addLayer(osm);
}

window.onload = load_map;

&lt;/pre&gt;
      &lt;p&gt;If you request the &lt;code&gt;index.html&lt;/code&gt; page now through the browser, you will see something like:&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/address-1.jpg" alt="address-1.jpg"/&gt;
      &lt;p&gt;
        &lt;strong&gt;Adding the Address Search&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;In order to add an address lookup form, we need to add more HTML. Our HTML will feature an input box (for the address), a submit button, and a place holder to show our results in. We add this code between the &lt;code&gt;&lt;div id="map"/&gt;&lt;/code&gt; and the &lt;code&gt;&lt;script...&lt;/code&gt; tags:&lt;/p&gt;
      &lt;pre&gt;&lt;div id="search"&gt;
  &lt;input type="text" name="addr" value="" id="addr" size="10" /&gt;
  &lt;button type="button" onclick="addr_search();"&gt;Search&lt;/button&gt;
  &lt;div id="results"/&gt;
&lt;/div&gt;

&lt;/pre&gt;
      &lt;p&gt;To style this, we add the following at the end of our CSS file:&lt;/p&gt;
      &lt;pre&gt;div#search {
  background-color: white;
  position: absolute;
  bottom: 40px;
  left: 40px;
  width: auto;
  height: auto;
  padding: 10px;
}
div#search input {
  width: 200px;
}
div#results {
  font-style: sans-serif;
  color: black;
  font-size: 75%;
}

&lt;/pre&gt;
      &lt;p&gt;If we reload the page in our browser, we will see something like:&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/address-2.jpg" alt="address-2.jpg"/&gt;
      &lt;p&gt;Now the only thing left to do is to implement the &lt;code&gt;addr_search&lt;/code&gt; function. In our JS file (&lt;code&gt;js/map.js&lt;/code&gt;) we add before &lt;code&gt;window.onload = load_map;&lt;/code&gt; the following lines (split over multiple sections in this tutorial):&lt;/p&gt;
      &lt;pre&gt;function addr_search() {
  var inp = document.getElementById("addr");

  $.getJSON('http://nominatim.openstreetmap.org/search?format=json&amp;limit=5&amp;q=' + inp.value, function(data) {

&lt;/pre&gt;
      &lt;p&gt;This above line uses jQuery's AJAX capabilities to request a URL, parse the JSON result and issue a callback if it worked. We query &lt;a href="http://nominatim.openstreetmap.org/"&gt;Nominatim&lt;/a&gt; here, with as format &lt;code&gt;json&lt;/code&gt; and limiting the result to &lt;code&gt;5&lt;/code&gt; items. Nominatim also supports other parameters, which are documented &lt;a href="http://wiki.openstreetmap.org/wiki/Nominatim#Parameters"&gt;here&lt;/a&gt;. &lt;/p&gt;
      &lt;pre&gt;var items = [];

$.each(data, function(key, val) {
  items.push(
    "&lt;li&gt;&lt;a href='#' onclick='chooseAddr(" +
    val.lat + ", " + val.lon + ");return false;'&gt;" + val.display_name +
    '&lt;/a&gt;&lt;/li&gt;'
  );
});

&lt;/pre&gt;
      &lt;p&gt;For each of the items in our result, we create an &lt;code&gt;&lt;li&gt;&lt;/code&gt; element which has an &lt;code&gt;&lt;a href&lt;/code&gt; containing a call to a JavaScript function (&lt;code&gt;chooseAddr&lt;/code&gt;). This function is responsible for re-centering the map according to the picked latitude and longitude. &lt;/p&gt;
      &lt;pre&gt;    $('#results').empty();
    if (items.length != 0) {
      $('&lt;p&gt;', { html: "Search results:" }).appendTo('#results');
      $('&lt;ul/&gt;', {
        'class': 'my-new-list',
        html: items.join('')
      }).appendTo('#results');
    } else {
      $('&lt;p&gt;', { html: "No results found" }).appendTo('#results');
    }
  });
}

&lt;/pre&gt;
      &lt;p&gt;This processes the results that came back from Nominatim. If there are results, we shows those including a &lt;code&gt;Search results:&lt;/code&gt; header, and if there are no results, we show &lt;code&gt;No results found&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;Then we need to add one more function, the &lt;code&gt;chooseAddr&lt;/code&gt; function which looks like:&lt;/p&gt;
      &lt;pre&gt;function chooseAddr(lat, lng, type) {
  var location = new L.LatLng(lat, lng);
  map.panTo(location);

  if (type == 'city' || type == 'administrative') {
    map.setZoom(11);
  } else {
    map.setZoom(13);
  }
}

&lt;/pre&gt;
      &lt;p&gt;We simply use the latitude and longitude from the function invocation, and in order to make things slightly nicer we zoom in a bit less if the item type is either a &lt;code&gt;city&lt;/code&gt; or an &lt;code&gt;administrative&lt;/code&gt; border. As each of the returned results actually includes a full bounding box, we probably can use that to zoom in better, but I will leave that for your own experiments - you'd want the &lt;code&gt;panInsideBounds()&lt;/code&gt; method of Leaflet's Map class for that.&lt;/p&gt;
      &lt;p&gt;In the end, if we click on the &lt;code&gt;Search&lt;/code&gt; button, a list is presented of all our search results:&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/address-3.jpg" alt="address-3.jpg"/&gt;
      &lt;p&gt;And after clicking one of the links, we see the map centered on Paris:&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/address-4.jpg" alt="address-4.jpg"/&gt;
      &lt;p&gt;The code for this example is available on github in my &lt;code&gt;osm-tools&lt;/code&gt; repository at &lt;a href="https://github.com/derickr/osm-tools/tree/master/leaflet-nominatim-example"&gt;https://github.com/derickr/osm-tools/tree/master/leaflet-nominatim-example&lt;/a&gt;&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201211201052</guid>
      <pubDate>Tue, 20 Nov 2012 10:52:00 +0000</pubDate>
    </item>
    <item>
      <title>Displaying OpenStreetMap data from MongoDB</title>
      <link>http://derickrethans.nl/osm-display-mongo.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="displaying_openstreetmap_data_from_mongodb"/&gt;Displaying OpenStreetMap data from MongoDB&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; Mainz, Germany&lt;/div&gt;
        &lt;div class="date"&gt;Tuesday, October 16th 2012, 11:12 CEST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;In an earlier &lt;a href="http://derickrethans.nl/indexing-free-tags.html"&gt;article&lt;/a&gt; I have already written about several different ways on how you could store &lt;a href="http://openstreetmap.org"&gt;OpenStreetmap&lt;/a&gt; data in &lt;a href="http://mongodb.org"&gt;MongoDB&lt;/a&gt;.&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201210161112</guid>
      <pubDate>Tue, 16 Oct 2012 09:12:00 +0000</pubDate>
    </item>
    <item>
      <title>Indexing Freeform-Tagged Data</title>
      <link>http://derickrethans.nl/indexing-free-tags.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="indexing_freeform-tagged_data"/&gt;Indexing Freeform-Tagged Data&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; Dieren, Netherlands&lt;/div&gt;
        &lt;div class="date"&gt;Tuesday, June 26th 2012, 09:11 CEST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;At last week's MongoDB UK I presented on &lt;a href="http://derickrethans.nl/talks/osm-mongouk12"&gt;"Geolocation, Maps and MongoDB"&lt;/a&gt;. During this talk I presented a few methods on how to store &lt;a href="http://openstreetmap.org"&gt;OpenStreetMap&lt;/a&gt;'s tags on geographical objects. OpenStreetMap's tags are free form and for example for a road can look like:&lt;/p&gt;
      &lt;pre&gt;alt_name:    The Strand
highway:     primary
name:        Strand
oneway:      yes
postal_code: WC2

&lt;/pre&gt;
      &lt;p&gt;For this article, I am using a data extra from OpenStreetMap for just London. There are about 2.2 million items in my database after importing taking up 413MB of storage (without indexes).&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Tags as a normal object&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;The first method for storing into MongoDB basically uses a &lt;code&gt;tags&lt;/code&gt; field with each of the OpenStreetMap tag taking up a property in the array:&lt;/p&gt;
      &lt;pre&gt;"_id" : "w1572026939",
"type" : NumberLong(2),
"tags" : {
        "alt_name" : "The Strand",
        "highway" : "primary",
        "name" : "Strand",
        "oneway" : "yes",
        "postal_code" : "WC2"
},
"loc" : [ [ -0.125262, 51.5086046 ], …, [ -0.1270609, 51.5076398 ] ]

&lt;/pre&gt;
      &lt;p&gt;In order to make looking up tags and values faster, we will need to create some indexes. We would like to look for streets by name:&lt;/p&gt;
      &lt;pre&gt;db.poi.ensureIndex( { 'tags.name': 1 } );
db.poi.find( { 'tags.name': 'Strand' } );

&lt;/pre&gt;
      &lt;p&gt;And also find roads and paths:&lt;/p&gt;
      &lt;pre&gt;db.poi.ensureIndex( { 'tags.highway': 1 } );
db.poi.find( { 'tags.highway' : { $exists: 1 } } );

&lt;/pre&gt;
      &lt;p&gt;As you can see, for every tag that we want to search on we would have to create an index. More indexes make inserts, updates and deletes slower and MongoDB is also limited to 64 indexes per collection. In order to have an index on &lt;strong&gt;every&lt;/strong&gt; tag we need to find another solution. Also note that the second query doesn't even use an index when you check it with calling &lt;code&gt;explain()&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Tags as key/value pairs&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;The second method I introduced was the one where instead of storing the tags as properties of the &lt;code&gt;tags&lt;/code&gt; field, I stored objects containing a tag/tagvalue pair each:&lt;/p&gt;
      &lt;pre&gt;"tags_indexed" : [
        { "k" : "alt_name", "v" : "The Strand" },
        { "k" : "highway", "v" : "primary" },
        { "k" : "name", "v" : "Strand" },
        { "k" : "oneway", "v" : "yes" },
        { "k" : "postal_code", "v" : "WC2" }
],

&lt;/pre&gt;
      &lt;p&gt;The index we then can create is:&lt;/p&gt;
      &lt;pre&gt;db.poi.ensureIndex( { 'tags_indexed.k' : 1, 'tags_indexed.v' : 1 } );

&lt;/pre&gt;
      &lt;p&gt;To answer the same questions as above we have to rewrite the queries. To find all documents where the &lt;code&gt;name&lt;/code&gt; is &lt;code&gt;Strand&lt;/code&gt; we can not simply do:&lt;/p&gt;
      &lt;pre&gt;db.poi.find( { 'tags_indexed.k' : 'name', 'tags_indexed.v' : 'Strand' } );

&lt;/pre&gt;
      &lt;p&gt;This query will find all documents where &lt;strong&gt;either&lt;/strong&gt; the key is &lt;code&gt;name&lt;/code&gt; or the value is &lt;code&gt;Strand&lt;/code&gt;. This includes documents where there is an array element like &lt;code&gt;{ "k" : "name", "v" : "Strand" }&lt;/code&gt; in the &lt;code&gt;tags_indexed&lt;/code&gt; array, but also every &lt;strong&gt;other&lt;/strong&gt; document where there is one &lt;code&gt;"k" : "name"&lt;/code&gt; and a &lt;code&gt;"v" : "Strand"&lt;/code&gt; element, such as in:&lt;/p&gt;
      &lt;pre&gt;{
        "_id" : ObjectId("4fe4c4b044670a41222088b5"),
        "tags_indexed" : [
                { "k" : "addr:housenumber", "v" : "79" },
                { "k" : "addr:postcode", "v" : "WC2R 0DW" },
                { "k" : "addr:street", "v" : "Strand" },
                { "k" : "building", "v" : "yes" },
                { "k" : "name", "v" : "Stamp Centre" }
        ]
}

&lt;/pre&gt;
      &lt;p&gt;In order to match only documents where there is &lt;strong&gt;one&lt;/strong&gt; array element with both the &lt;code&gt;"k" : "name"&lt;/code&gt; and &lt;code&gt;"v" : "Strand"&lt;/code&gt; properties, you need to use &lt;a href="http://www.mongodb.org/display/DOCS/Advanced+Queries#AdvancedQueries-%24elemMatch"&gt;$elemMatch&lt;/a&gt;:&lt;/p&gt;
      &lt;pre&gt;db.poi.find( {
        'tags_indexed' : { $elemMatch: { 'k' : 'name', 'v' : 'Strand' } }
} );

&lt;/pre&gt;
      &lt;p&gt;Let's look at the explain output of both variants. In the first case (with &lt;code&gt;'name': 'Strand'&lt;/code&gt;), the explain output looks like:&lt;/p&gt;
      &lt;pre&gt;&gt; db.poi.find( { 'tags.name': 'Strand' } ).explain();
{
        "cursor" : "BtreeCursor tags.name_1",
        "nscanned" : 12,
        "nscannedObjects" : 12,
        "n" : 12,
        "millis" : 1,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : false,
        "indexOnly" : false,
        "indexBounds" : {
                "tags.name" : [ [ "Strand", "Strand" ] ]
        }
}

&lt;/pre&gt;
      &lt;p&gt;And in the second case, the explain output looks like:&lt;/p&gt;
      &lt;pre&gt;&gt; db.poi.find( {
... 'tags_indexed' : { $elemMatch: { 'k' : 'name', 'v' : 'Strand' } }
... } ).explain();
{
        "cursor" : "BtreeCursor tags_indexed.k_1_tags_indexed.v_1",
        "nscanned" : 172145,
        "nscannedObjects" : 172145,
        "n" : 12,
        "millis" : 429,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : true,
        "indexOnly" : false,
        "indexBounds" : {
                "tags_indexed.k" : [ [ "name", "name" ] ],
                "tags_indexed.v" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ]
        }
}


&lt;/pre&gt;
      &lt;img src="http://derickrethans.nl/images/content/mongodb-logo.png" class="right" alt="mongodb-logo.png"/&gt;
      &lt;p&gt;In the first case, 12 documents are scanned and found. But in the second case, the same 12 documents are returned, but 172145 documents are scanned.  This is because at the moment, &lt;code&gt;$elemMatch&lt;/code&gt; does not use the index fully and will only use the first part of the compound index&lt;a href="http://derickrethans.nl#_footnote_0_1" class="footnote"&gt;1&lt;/a&gt; — &lt;code&gt;tags_indexed.k&lt;/code&gt; in our case.&lt;/p&gt;
      &lt;p&gt;In order to make index usage better, it therefore makes more sense to have the property with the highest &lt;a href="http://en.wikipedia.org/wiki/Cardinality"&gt;cardinality&lt;/a&gt; to be the first key in the compound index. In my data-set, the cardinality of &lt;code&gt;tags_indexed.v&lt;/code&gt; is (a lot) higher than &lt;code&gt;tags_indexed.k&lt;/code&gt;:&lt;/p&gt;
      &lt;pre&gt;&gt; db.poi.distinct( 'tags_indexed.k' ).length;
1281
&gt; db.poi.distinct( 'tags_indexed.v' ).length;
151581

&lt;/pre&gt;
      &lt;p&gt;So let's change the keys in the compound index around, and run the explain for the second query again:&lt;/p&gt;
      &lt;pre&gt;&gt; db.poi.dropIndex( 'tags_indexed.k_1_tags_indexed.v_1' );
&gt; db.poi.ensureIndex( { 'tags_indexed.v' : 1, 'tags_indexed.k' : 1 } );

&gt; db.poi.find( {
... 'tags_indexed' : { $elemMatch: { 'k' : 'name', 'v' : 'Strand' } }
... } ).explain();
{
        "cursor" : "BtreeCursor tags_indexed.v_1_tags_indexed.k_1",
        "nscanned" : 44,
        "nscannedObjects" : 44,
        "n" : 12,
        "millis" : 0,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : true,
        "indexOnly" : false,
        "indexBounds" : {
                "tags_indexed.v" : [ [ "Strand", "Strand" ] ],
                "tags_indexed.k" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ]
        }
}

&lt;/pre&gt;
      &lt;p&gt;Now with the higher cardinality on the first part of the compound key only 44 documents are scanned. And instead of 429 milliseconds it now took less than 1 millisecond. Excellent news!&lt;/p&gt;
      &lt;p&gt;The second question from the first section ("find roads and paths") can be rewritten as:&lt;/p&gt;
      &lt;pre&gt;db.poi.find( { 'tags_indexed.k': 'highway' } );

&lt;/pre&gt;
      &lt;p&gt;However, this query can not be serviced by our rewritten index (&lt;code&gt;'tags_indexed.v' : 1, 'tags_indexed.k' : 1&lt;/code&gt;) as MongoDB can only use keys starting from the left side of a compound index for queries. We can now either add another index that only encompasses the &lt;code&gt;k&lt;/code&gt; part or perhaps find a different solution.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Tag keys and values combined&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;There is one other alternative that I would like to explore, and that is where I combine the key and value of each tag into one string, and store those all in an array in the object, like:&lt;/p&gt;
      &lt;pre&gt;"tags_combined" : [
        "alt_name=The Strand",
        "highway=primary",
        "name=Strand",
        "oneway=yes",
        "postal_code=WC2"
],

&lt;/pre&gt;
      &lt;p&gt;And then we index this as:&lt;/p&gt;
      &lt;pre&gt;db.poi.ensureIndex( { 'tags_combined': 1 } );

&lt;/pre&gt;
      &lt;p&gt;The first query we used earlier in this article ("all items where the name is &lt;code&gt;Strand&lt;/code&gt;") now looks like:&lt;/p&gt;
      &lt;pre&gt;&gt; db.poi.find( { 'tags_combined': "name=Strand" ).explain();
{
        "cursor" : "BtreeCursor tags_combined_1",
        "nscanned" : 12,
        "nscannedObjects" : 12,
        "n" : 12,
        "millis" : 0,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : true,
        "indexOnly" : false,
        "indexBounds" : {
                "tags_combined" : [ [ "name=Strand", "name=Strand" ] ]
        }
}

&lt;/pre&gt;
      &lt;p&gt;As you can see the index is very good at resolving this case, as it only scanned the 12 documents that the query also returned—just like where we had a direct index on &lt;code&gt;tags.name&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;The second query (all paths and roads) and its explain syntax are:&lt;/p&gt;
      &lt;pre&gt;&gt; db.poi.find( { 'tags_combined': /^highway=/ } ).explain();
{
        "cursor" : "BtreeCursor tags_combined_1 multi",
        "nscanned" : 210441,
        "nscannedObjects" : 210440,
        "n" : 210440,
        "millis" : 741,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : true,
        "indexOnly" : false,
        "indexBounds" : {
                "tags_combined" : [
                        [ "highway=", "highway&gt;" ], [ /^highway=/, /^highway=/ ]
                ]
        }
}

&lt;/pre&gt;
      &lt;p&gt;Here we use a regular expression search (&lt;code&gt;/^highway=/&lt;/code&gt;) anchored to the start of the string (with the &lt;code&gt;^&lt;/code&gt;). This can use an index, and is almost as good as the the case where &lt;code&gt;tags_indexed.k&lt;/code&gt; was the first part of the compound key, except that it is a bit slower (527 vs 741 milliseconds):&lt;/p&gt;
      &lt;pre&gt;&gt; db.poi.find( { 'tags_indexed.k': 'highway' } ).explain();
{
        "cursor" : "BtreeCursor tags_indexed.k_1_tags_indexed.v_1",
        "nscanned" : 210440,
        "nscannedObjects" : 210440,
        "n" : 210440,
        "millis" : 527,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "isMultiKey" : true,
        "indexOnly" : false,
        "indexBounds" : {
                "tags_indexed.k" : [ [ "highway", "highway" ] ],
                "tags_indexed.v" : [ [ { "$minElement" : 1 }, { "$maxElement" : 1 } ] ]
        }
}

&lt;/pre&gt;
      &lt;p&gt;
        &lt;strong&gt;Conclusion&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;Right now, I think that having an indexed on &lt;code&gt;tags_combined&lt;/code&gt; where each tag is combined with key and value and an array element &lt;code&gt;tags_combined&lt;/code&gt; is the best solution. Its index scanning is better for the &lt;code&gt;name=Strand&lt;/code&gt; case than the other, and not significantly slower in the &lt;code&gt;highway=*&lt;/code&gt; case. I would still like to see how the speed differences progress when I add more objects to the database.&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
    &lt;ul class="footnotes"&gt;
      &lt;li&gt;
        &lt;a name="_footnote_0_1"&gt;1&lt;/a&gt;
        &lt;p&gt;: &lt;a href="https://jira.mongodb.org/browse/SERVER-4520"&gt;https://jira.mongodb.org/browse/SERVER-4520&lt;/a&gt;&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201206260911</guid>
      <pubDate>Tue, 26 Jun 2012 07:11:00 +0000</pubDate>
    </item>
    <item>
      <title>Ignite London: Crowd Sourcing a Map of the World</title>
      <link>http://derickrethans.nl/ignite-openstreetmap.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="ignite_london_crowd_sourcing_a_map_of_the_world"/&gt;Ignite London: Crowd Sourcing a Map of the World&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Tuesday, May 15th 2012, 09:47 BST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;Almost  two weeks ago, I gave a talk at &lt;a href="http://ignitelondon.net"&gt;Ignite London&lt;/a&gt; about &lt;a href="http://openstreetmap.org"&gt;OpenStreetMap&lt;/a&gt;, titled "Crowd Sourcing a Map of the World". Ignite's presentation style is 20 slides which automatically advance every 15 seconds. Having never done &lt;a href="http://derickrethans.nl/talks.html"&gt;this&lt;/a&gt; before I actually wrote the whole talk out. The presentation that I gave slightly diverges from this but I thought it'd still be good to reproduce here. I did add some links to more information, and if you want to see the recording, you can find it at the end of this post.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-01-project-openstreetmap.png" alt="osm-01-project-openstreetmap.png"/&gt;
      &lt;p&gt;1. This talk is about a project, started here in the UK with as its major goal to create a free map of the whole planet. From roads and motorways to country-side footpaths, restaurants and of course pubs. This talk is about OpenStreetMap, the free map of the world.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-02-ordnance-survey.png" alt="osm-02-ordnance-survey.png"/&gt;
      &lt;p&gt;2. There are of course already plenty of mapping solutions available. Maybe one of the best maps can be acquired through Ordnance Survey. They can be regarded as the national authority on this subject. It's however expensive to get access to their maps, especially the very detailed maps from OS MasterMap. Additionally, it's only for the UK.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-03-google-maps.png" alt="osm-03-google-maps.png"/&gt;
      &lt;p&gt;3. Besides the commercial solutions, you might wonder why we simply can't do with GoogleMaps? It's mostly freely available for use and also provides you with satellite imagery and StreetView. They even allow you in some areas to update the map through Google MapMaker.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-04-the-data.png" alt="osm-04-the-data.png"/&gt;
      &lt;p&gt;4. But one thing Google doesn't give you access to, is the data behind the map. All you will ever see, is the rendered map tiles and perhaps some APIs to lookup locations and points of interest. Even for data that you have added yourself through MapMaker.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-05-free-and-open-data.png" alt="osm-05-free-and-open-data.png"/&gt;
      &lt;p&gt;5. Both aspects; the cost of commercial maps, as well as the access to the data that is behind the map tiles is something that the OpenStreetMap project addresses. But which steps have to be taken to obtain this enormous amount of geographical data?&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-06-survey.png" alt="osm-06-survey.png"/&gt;
      &lt;p&gt;6. We start by getting our wellies and trusty GPS out. Maybe even some pen and paper. We find a location that looks rather empty on the map and travel to that area to see what's on the ground. This is step one: data gathering in the field.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-07-urban.png" alt="osm-07-urban.png"/&gt;
      &lt;p&gt;7. In urban areas such as London the roads have often already been mapped and a GPS is not accurate enough to be useful. Then we just use pen and paper to record points of interest, such as shops, landmarks, restaurants and postboxes, my personal favourite.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-08-countryside.png" alt="osm-08-countryside.png"/&gt;
      &lt;p&gt;8. In the country side, donated aerial imagery makes it possible for us to easily trace tracks and footpaths. However, you can't be sure whether the imagery is up-to-date, and you can't always see where fences, streams and local wild life create barriers.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-09-mapping-party.png" alt="osm-09-mapping-party.png"/&gt;
      &lt;p&gt;9. In both situations, surveys are best done in groups: at mapping parties. It helps spread the workload and a larger area can be surveyed in one go. As an additional benefit, it allows us to go the pub and discuss our mapping adventures!&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-10-recording.png" alt="osm-10-recording.png"/&gt;
      &lt;p&gt;10. Doing a survey is important. We take photographs, video and notes with pen and paper of everything that seems to be of interest. This leaves a record that everything we map is actually existing and we can prove that nothing has been copied from other copyrighted maps.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-11-database.png" alt="osm-11-database.png"/&gt;
      &lt;p&gt;11. After collecting the data, we enter it into the database. This includes basic information such as street names, but we also record whether a café has wheelchair access, or whether a pub has wifi. Updates to the map show up on the site close to real time.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-12-tagging.png" alt="osm-12-tagging.png"/&gt;
      &lt;p&gt;12. Every map object has tags associated with it. Tags tell whether a line is a road, or perhaps a fence. All the tags are free form so you can generally add as much information about an object as you want. Sometimes however, this gets slightly out of hand and people tag pandas in trees and eyes on postboxes.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-13-display.png" alt="osm-13-display.png"/&gt;
      &lt;p&gt;13. Once the data has been added to the map, we can make use of it. One of the primary uses is obviously showing the data as map tiles. But with all the extra data, we can generate maps that show all the information you're interested in-and nothing more.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-14-visualisation.png" alt="osm-14-visualisation.png"/&gt;
      &lt;p&gt;14. Clockwise, we have four different visualisations of the map data: we have a &lt;a href="http://www.openstreetmap.org/?lat=51.51647&amp;lon=-0.16968&amp;zoom=17&amp;layers=C"&gt;cycling-specific&lt;/a&gt; style, a style that shows &lt;a href="http://www.openstreetmap.org/?lat=51.51647&amp;lon=-0.16968&amp;zoom=17&amp;layers=T"&gt;transport&lt;/a&gt; routes, a rendering with &lt;a href="http://www.openstreetmap.org/?lat=51.51647&amp;lon=-0.16968&amp;zoom=17&amp;layers=Q"&gt;MapQuest's&lt;/a&gt; style sheets and even a &lt;a href="http://maps.stamen.com/watercolor/#12/51.5168/-0.1515"&gt;water colours&lt;/a&gt; inspired style.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-15-mapquest.png" alt="osm-15-mapquest.png"/&gt;
      &lt;p&gt;15. Having mentioned MapQuest; they were one of the first companies to make use of OpenStreetMap data. They provide, free of charge, map tiles with their own rendering style as well as an instance of &lt;a href="http://wiki.openstreetmap.org/wiki/Nominatim"&gt;Nominatim&lt;/a&gt;, OpenStreetMap's geolocation sister project.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-16-switching.png" alt="osm-16-switching.png"/&gt;
      &lt;p&gt;16. Lots of companies have already switched to OpenStreetMap.  The property search site &lt;a href="http://blog.nestoria.co.uk/why-and-how-weve-switched-away-from-google-ma"&gt;Nestoria&lt;/a&gt; recently switched from using GoogleMaps to OpenStreetMap. Partly because of their costs, but also partly because "The maps are equal or better". geocaching.com, TfL's countdown website and Apple also use OpenStreetMap maps and data.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-17-creative-commons.png" alt="osm-17-creative-commons.png"/&gt;
      &lt;p&gt;17. Although OpenStreetMap provides a free and editable map of the world, there are certain requirements for using the data as well. The most important one is that you always need to attribute the OpenStreetMap project.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-18-switch2osm.png" alt="osm-18-switch2osm.png"/&gt;
      &lt;p&gt;18. In order to help people start using OpenStreetMap for their mapping needs, the Switch2OSM site has been launched. This website provides background information, case studies and technical information on how to use OpenStreetMap data.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-19-we-need-you.png" alt="osm-19-we-need-you.png"/&gt;
      &lt;p&gt;19. Right now, OpenStreetMap has very good data coverage in the country, but we are not nearly finished. A lot of work still has to be done, and we rely on you to improve the data too, even if you add just a postbox.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/osm-20-openstreetmap.png" alt="osm-20-openstreetmap.png"/&gt;
      &lt;p&gt;20. In the last 5 minutes we have looked at what OpenStreetMap is, how the data is gathered and how the data is added to the map. Further more, we had a look at different use cases of the data. OpenStreetMap in the UK: Footpaths and pubs a speciality!&lt;/p&gt;
      &lt;p&gt;And then I planned showing the &lt;a href="http://vimeo.com/derickr/osm-2011"&gt;"Year of Edits"&lt;/a&gt; video, but that sadly didn't work out. I'm including it for good measure here though. (If you want it in HD, follow the &lt;a href="http://vimeo.com/derickr/osm-2011"&gt;link&lt;/a&gt;).&lt;/p&gt;
      &lt;div class="video"&gt;
        &lt;iframe src="http://player.vimeo.com/video/34404102?title=0&amp;byline=0&amp;portrait=0" width=" 599" height=" 337" frameborder="0"/&gt;
        &lt;p&gt;&lt;a href="http://vimeo.com/34404102"/&gt;OpenStreetMap: A Year of Edits (2011)&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;The video of the talk itself is at &lt;a href="http://vimeo.com/41626116"&gt;http://vimeo.com/41626116&lt;/a&gt; and is embedded here:&lt;/p&gt;
      &lt;div class="video"&gt;
        &lt;iframe src="http://player.vimeo.com/video/41626116?title=0&amp;byline=0&amp;portrait=0" width=" 599" height=" 337" frameborder="0"/&gt;
        &lt;p&gt;&lt;a href="http://vimeo.com/41626116"/&gt;Crowdsourcing a Map of the World&lt;/p&gt;
      &lt;/div&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201205150947</guid>
      <pubDate>Tue, 15 May 2012 08:47:00 +0000</pubDate>
    </item>
    <item>
      <title>OpenStreetMap: A Year of Edits</title>
      <link>http://derickrethans.nl/year-of-edits.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="openstreetmap_a_year_of_edits"/&gt;OpenStreetMap: A Year of Edits&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Sunday, January 1st 2012, 00:00 GMT&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;In the past weeks I've been working on some visualisations related to additions and changes being made to OpenStreetMap. To start of the new year, I hereby present: &lt;strong&gt;OpenStreetMap: A Year of Edits (2011)&lt;/strong&gt;:&lt;/p&gt;
      &lt;div class="video"&gt;
        &lt;iframe src="http://player.vimeo.com/video/34404102?title=0&amp;byline=0&amp;portrait=0" width=" 599" height=" 337" frameborder="0"/&gt;
        &lt;p&gt;&lt;a href="http://vimeo.com/34404102"/&gt;OpenStreetMap: A Year of Edits (2011)&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;You can see the video in HD on &lt;a href="http://vimeo.com/derickr/osm-2011"&gt;Vimeo&lt;/a&gt;. &lt;strong&gt;Happy New Year!&lt;/strong&gt;&lt;/p&gt;
      &lt;p&gt;
        &lt;em&gt;How Did He Do That?&lt;/em&gt;
      &lt;/p&gt;
      &lt;p&gt;This animation is all made with Open Source software. I haven't had the time to clean up the code to make it releasable yet, but I am intending to do that in the near feature and add it to my &lt;a href="https://github.com/derickr/osm-tools"&gt;OpenStreetMap tools&lt;/a&gt; git repository on github.&lt;/p&gt;
      &lt;p&gt;The first thing I did, was download all the hourly replication files from &lt;a href="http://planet.openstreetmap.org/hour-replicate"&gt;http://planet.openstreetmap.org/hour-replicate&lt;/a&gt; for the year.&lt;/p&gt;
      &lt;p&gt;With a script, I scanned over all those XML files and for every three hours I created an image showing the edits of those last three hours (in white), as well as all the other edits in the background (in purple). With 4 frames per day, this gives me 1460 frames.&lt;/p&gt;
      &lt;p&gt;Each of the frames is an equirectangular projection which I then map onto a sphere with a faded background of "earthmap10k" of &lt;a href="http://planetpixelemporium.com/earth.html"&gt;http://planetpixelemporium.com/earth.html&lt;/a&gt; (I downloaded it &lt;em&gt;years&lt;/em&gt; ago when it was still free to download). For the 3D rendering of the Earth with the edits layed on top of it I used &lt;a href="http://www.povray.org/"&gt;POV-Ray&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;I generated two different loops so that I can make two full rotations at different angles. This gives me in total 2882 frames which is about two minutes. The POV-Ray generated images I post-processed by overlaying the progress bar in the lower left and added the fading and merging effects. Then I used &lt;a href="http://www.mplayerhq.hu/"&gt;mencoder&lt;/a&gt; to create a MPEG video out of it.  Finally I added the &lt;a href="http://www.archive.org/details/TBECD005-MP3-192k-VBR"&gt;sound track&lt;/a&gt; again by using mencoder and uploaded it to &lt;a href="http://vimeo.com/derickr/osm-2011"&gt;Vimeo&lt;/a&gt;.&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201201010000</guid>
      <pubDate>Sun, 01 Jan 2012 00:00:00 +0000</pubDate>
    </item>
    <item>
      <title>South Kensington Mapping Party Animation</title>
      <link>http://derickrethans.nl/mapping-party-animation.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="south_kensington_mapping_party_animation"/&gt;South Kensington Mapping Party Animation&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Tuesday, July 19th 2011, 09:17 BST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;Every two weeks or-so, the &lt;a href="http://wiki.openstreetmap.org/wiki/London/Summer_2011_mapping_parties"&gt;London OpenStreetMap group&lt;/a&gt; comes together for a &lt;a href="http://wiki.openstreetmap.org/wiki/Mapping_party"&gt;mapping party&lt;/a&gt;. The main idea is to go to an area of London to improve the map there. Sometimes that's adding streets, but as most of those are now completed, time is spend on buildings, address information and points-of-interest.&lt;/p&gt;
      &lt;p&gt;As an experiment I recorded a snapshot of the map for the area (&lt;a href="http://mapcraft.nanodesu.ru/pie/73"&gt;http://mapcraft.nanodesu.ru/pie/73&lt;/a&gt;) every hour to follow the progress of people adding new and improved data to the map. I then rendered an image of each map snapshot and put those together to create a video:&lt;/p&gt;
      &lt;div class="video"&gt;
        &lt;iframe src="http://player.vimeo.com/video/26470999?title=0&amp;byline=0&amp;portrait=0" width=" 599" height=" 337" frameborder="0"/&gt;
        &lt;p&gt;&lt;a href="http://vimeo.com/26470999"/&gt;South Kensington Mapping Party animation — Map data © OpenStreetMap contributors. CC-BY-SA&lt;/p&gt;
      &lt;/div&gt;
      &lt;p&gt;You can see the video in HD on &lt;a href="http://vimeo.com/derickr/osm-south-ken"&gt;Vimeo&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;How Did He Do That?&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;Well, first of all, I already have a script to keep a local version of the London XML map data (as &lt;code&gt;london.osm&lt;/code&gt;) up to date. Each hour, this script:&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;
          &lt;p&gt;Read the old &lt;code&gt;london.osm&lt;/code&gt; file and merge in changes through the OpenStreetMap &lt;a href="http://wiki.openstreetmap.org/wiki/Minutely_Mapnik"&gt;replication&lt;/a&gt; service and write it as &lt;code&gt;london-new.osm&lt;/code&gt;.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;Compare the old file and the new file and write out a changes file to &lt;code&gt;changes/diff-&lt;timestamp&gt;.osc&lt;/code&gt;. This is not needed for this to work, but I am interested in this information as well.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;Move the new file to the old file.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;Import it into my local PostGreSQL/PostGIS instance.&lt;/p&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;In script form, that looks like:&lt;/p&gt;
      &lt;pre&gt;#!/bin/bash

NOW=`date +%Y%m%d-%H%M`

export JAVACMD_OPTIONS="$JAVACMD_OPTIONS -Djava.net.preferIPv4Stack=true"
../osmosis-0.39/bin/osmosis --rri --simc --rx london.osm --ac --bb left=-0.563 right=0.28 top=51.68 bottom=51.26 --wx london-new.osm
../osmosis-0.39/bin/osmosis --rx london-new.osm --rx london.osm --derive-change --write-xml-change file="changes/diff-$NOW.osc"
mv london-new.osm london.osm
../try4/bin/osm2pgsql/osm2pgsql -S ../try4/bin/osm2pgsql/default.style --slim -d gis -C 2400 london.osm #-e 15 -o expiry.log

&lt;/pre&gt;
      &lt;p&gt;And from that I then can render maps of London quite easily:&lt;/p&gt;
      &lt;pre&gt;cd /backup/osm/try4/bin/mapnik
./generate_tiles_london.py | grep -v " exists"

&lt;/pre&gt;
      &lt;p&gt;In order to make a snapshot of South Kensington, or at least, the area that we were going to map in, I added another task. I placed it just after the moving of the old file to the new file. This task cuts out the South Kensington area and puts it in a file &lt;code&gt;south-ken/south-kensington-&lt;timestamp&gt;.osm&lt;/code&gt;:&lt;/p&gt;
      &lt;pre&gt;../osmosis-0.39/bin/osmosis --rx london.osm --bb left=-0.18256 right=-0.16853 top=51.49426 bottom=51.48733 --wx south-ken/south-kensington-$NOW.osm

&lt;/pre&gt;
      &lt;p&gt;This generated about 100 files in the &lt;code&gt;south-ken&lt;/code&gt; directory, where each XML file contained a snapshot of the map on each specific hour.&lt;/p&gt;
      &lt;p&gt;I then ran the following script to create a image of each of those map files:&lt;/p&gt;
      &lt;pre&gt;#!/bin/bash

export MAPNIK_MAP_FILE=/backup/osm/try4/bin/mapnik/osm.xml
mkdir -p images
LAT1=-0.18256
LON1=51.48833
LAT2=-0.16853
LON2=51.49326

for i in south*osm; do
  /backup/osm/try4/bin/osm2pgsql/osm2pgsql -S /backup/osm/try4/bin/osm2pgsql/default.style --slim -d gis -C 2400 $i
  /backup/osm/try4/bin/mapnik/generate_image.py ${LAT1} ${LON1} ${LAT2} ${LON2}

  mv image.png images/${i}-0.png
  LAT1=`echo "${LAT1} + 0.000005" | bc -q`
  LON1=`echo "${LON1} + 0.00001" | bc -q`
  LAT2=`echo "${LAT2} - 0.00001" | bc -q`
  LON2=`echo "${LON2} - 0" | bc -q`

  /backup/osm/try4/bin/mapnik/generate_image.py ${LAT1} ${LON1} ${LAT2} ${LON2}
  mv image.png images/${i}-1.png
  LAT1=`echo "${LAT1} + 0.000005" | bc -q`
  LON1=`echo "${LON1} + 0.00001" | bc -q`
  LAT2=`echo "${LAT2} - 0.00001" | bc -q`
  LON2=`echo "${LON2} - 0" | bc -q`
done

&lt;/pre&gt;
      &lt;p&gt;I had to modify the &lt;code&gt;generate_image.py&lt;/code&gt; script from the &lt;a href="http://mapnik.org/"&gt;Mapnik&lt;/a&gt; distribution slightly to allow the bounding box to be passed in through the command line. I also set the image width and height to 1280 by 720:&lt;/p&gt;
      &lt;pre&gt;...
ll = [];
for arg in sys.argv:
    ll.append(arg)
ll.pop(0);
for index, arg in enumerate(ll):
    ll[index] = float(arg)

z = 15
imgx = 1280
imgy = 720

m = mapnik.Map(imgx,imgy)
...

&lt;/pre&gt;
      &lt;p&gt;Once all the PNG files were created, I converted them to the JPEG format and ran &lt;code&gt;mencoder&lt;/code&gt; to generate the AVI film:&lt;/p&gt;
      &lt;pre&gt;for i in *png; do convert -quality 100 $i $i.jpg; done
mencoder "mf://*.jpg" -mf fps=10 -o test.avi -ovc lavc -lavcopts vcodec=msmpeg4v2:vbitrate=7200

&lt;/pre&gt;
      &lt;p&gt;And this video I then uploaded to &lt;a href="http://vimeo.com/derickr/osm-south-ken"&gt;Vimeo&lt;/a&gt;. Voilà! One OpenStreetMap mapping party progress animation!&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201107190917</guid>
      <pubDate>Tue, 19 Jul 2011 08:17:00 +0000</pubDate>
    </item>
    <item>
      <title>OpenStreetMap Quality Assurance with a Garmin GPS</title>
      <link>http://derickrethans.nl/osm-garmin-tools.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="openstreetmap_quality_assurance_with_a_garmin_gps"/&gt;OpenStreetMap Quality Assurance with a Garmin GPS&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Tuesday, July 5th 2011, 10:43 BST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;I've recently bought myself a new &lt;a href="http://wiki.openstreetmap.org/wiki/Garmin/eTrex_Vista_HCx"&gt;Garmin eTrex Vista HCx&lt;/a&gt; GPS to replace my older Garmin eTrex Legend unit. Because I don't want to shell out money for outdated maps, I have spend some time building my own map images for the Garmin with &lt;a href="http://wiki.openstreetmap.org/wiki/Mkgmap"&gt;mkgmap&lt;/a&gt; and &lt;a href="http://wiki.openstreetmap.org/"&gt;OpenStreetMap&lt;/a&gt; data. I will write more on that later.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/garmin-osm.jpg" class="left" alt="garmin-osm.jpg"/&gt;
      &lt;p&gt;Because most of the data in OpenStreetMap has been surveyed by volunteers, it is always possible that things are missing or done incorrectly. There are quite a few different tools on-line for quality assurance of the OpenStreetMap data, but I will be looking at two of them only.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;OSM Analysis&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;First of all there is &lt;a href="http://www.itoworld.com/"&gt;ITO World&lt;/a&gt;'s &lt;a href="http://www.itoworld.com/static/osm_analysis.html"&gt;OSM Analysis&lt;/a&gt;. This tool compares the OpenStreetMap data in the UK to the Ordnance Survey's &lt;a href="http://www.ordnancesurvey.co.uk/oswebsite/products/os-locator/"&gt;Locator&lt;/a&gt; data. The latter is supposed to be the authoritative source of road names in the UK. For each of the 417 areas in the UK, the tool produces two lists; for example the one for Brent is here: &lt;a href="http://www.itoworld.com/product/data/osm_analysis/area?name=Brent"&gt;http://www.itoworld.com/product/data/osm_analysis/area?name=Brent&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;The first column lists all the corrections that Ordnance Survey ought to make in an updated version because one of OpenStreetMap's surveyors found it incorrect. The second column lists all the streets that are missing from OpenStreetMap.&lt;/p&gt;
      &lt;p&gt;Because it would be really useful to have this sort of data available on a hand-held GPS to be able to notice any of the highlighted missing roads on the go, I set out to convert this data to a map file suitable for the Garmin devices. I've published the scripts on &lt;a href="https://github.com/derickr/osm-tools/tree/master/osm-analyis-to-garmin"&gt;github&lt;/a&gt; so that you can try it for yourself, but I've also put a ready made &lt;code&gt;osmanal.img&lt;/code&gt; file on-line for download at &lt;a href="http://derick.dev.openstreetmap.org/"&gt;http://derick.dev.openstreetmap.org/&lt;/a&gt;. In the near future I will use cron to run this conversion about once a week automatically.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Musical Chairs&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;Another tool that compares OpenStreetMap data with Ordnance Survey's Locator data is &lt;a href="http://ris.dev.openstreetmap.org/oslmusicalchairs/map"&gt;OS Locator Musical Chairs&lt;/a&gt;. Documentation on this project is available on the OpenStreetMap &lt;a href="http://wiki.openstreetmap.org/wiki/OS_Locator_Musical_Chairs/FAQ."&gt;wiki&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;The author of the Musical Chairs tool has provided a &lt;a href="http://wiki.openstreetmap.org/wiki/OS_Locator_Musical_Chairs/FAQ#Can_I_have_a_dump_of_the_data.3F"&gt;data dump&lt;/a&gt; highlighting all the disagreements it finds (including missing roads) while comparing OpenStreetMap data against Ordnance Survey's Locator data.  I've written a tool that converts this data to another Garmin map image files with the scripts that I have published at &lt;a href="https://github.com/derickr/osm-tools/tree/master/musical-chairs-to-garmin"&gt;github&lt;/a&gt;. With those scripts you can make your own image files, but like with the OSM Analysis I have made a ready-made file &lt;code&gt;muschair.img&lt;/code&gt; available at &lt;a href="http://derick.dev.openstreetmap.org/"&gt;http://derick.dev.openstreetmap.org/&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Using the map image files&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;Before you can use the two Garmin map image files, you either need to convert it to a stand-alone &lt;code&gt;gmapsupp.img&lt;/code&gt; file by running:&lt;/p&gt;
      &lt;pre&gt;java -jar /backup/osm/try4/bin/mkgmap-r1946/mkgmap.jar --gmapsupp osmanal.img

&lt;/pre&gt;
      &lt;p&gt;or:&lt;/p&gt;
      &lt;pre&gt;java -jar /backup/osm/try4/bin/mkgmap-r1946/mkgmap.jar --gmapsupp muschairs.img

&lt;/pre&gt;
      &lt;p&gt;These commands produce a &lt;code&gt;gmapsupp.img&lt;/code&gt; file that you can upload to your Garmin device. If you do this, you will &lt;strong&gt;not&lt;/strong&gt; have any road data.&lt;/p&gt;
      &lt;p&gt;You can also merge this file with all your other maps to keep the road data, by running something like:&lt;/p&gt;
      &lt;pre&gt;java -Xmx2048M -jar /backup/osm/try4/bin/mkgmap-r1946/mkgmap.jar \
    --gmapsupp /tmp/music.img /tmp/osmanal.img \
    UK/gmapsupp.img \
    contours/gmapsupp.img

&lt;/pre&gt;
      &lt;p&gt;Which merges the Musical Chairs image file (&lt;code&gt;music.img&lt;/code&gt;), the OSM Analysis image file (&lt;code&gt;osmanal.img&lt;/code&gt;), the UK maps (&lt;code&gt;UK/gmapsupp.img&lt;/code&gt;) as well as my UK contours line file (&lt;code&gt;contours/gmapsupp.img&lt;/code&gt;). This again produces a &lt;code&gt;gmapsupp.img&lt;/code&gt; file that you can &lt;a href="http://wiki.openstreetmap.org/wiki/OSM_Map_On_Garmin#Installing_the_map_onto_your_GPS"&gt;upload&lt;/a&gt; to your Garmin GPS.&lt;/p&gt;
      &lt;p&gt;For further &lt;code&gt;mkgmap&lt;/code&gt; usage details I would like you to point to it's page on the OpenStreetMap wiki: &lt;a href="http://wiki.openstreetmap.org/wiki/Mkgmap"&gt;http://wiki.openstreetmap.org/wiki/Mkgmap&lt;/a&gt; or more generally to &lt;a href="http://wiki.openstreetmap.org/wiki/OSM_Map_On_Garmin"&gt;http://wiki.openstreetmap.org/wiki/OSM_Map_On_Garmin&lt;/a&gt;. I am also intending to publish my UK and contour map creation scripts after I've streamlined this process.&lt;/p&gt;
      &lt;p&gt;And now the maps have been on my GPS for a few days, I've identified and &lt;a href="http://www.openstreetmap.org/browse/changeset/8628284"&gt;fixed&lt;/a&gt; a "&lt;a href="http://ris.dev.openstreetmap.org/oslmusicalchairs/map?osl_id=890373"&gt;disagreement&lt;/a&gt;" from Musical Chairs and a few &lt;a href="http://www.openstreetmap.org/browse/changeset/8580300"&gt;others&lt;/a&gt; from the ITO World OSM analysis tool.&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201107051043</guid>
      <pubDate>Tue, 05 Jul 2011 09:43:00 +0000</pubDate>
    </item>
    <item>
      <title>Spatial Indexes: Solr</title>
      <link>http://derickrethans.nl/spatial-indexes-solr.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="spatial_indexes_solr"/&gt;Spatial Indexes: Solr&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Tuesday, June 14th 2011, 09:04 BST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;In two previous articles I introduced &lt;a href="http://drck.me/spat-dist-8kf"&gt;the spherical Earth model&lt;/a&gt;, using &lt;a href="http://drck.me/spat-osm-sqlite-8la"&gt;SQLite&lt;/a&gt; as a geographical data storage and using &lt;a href="http://drck.me/spat-mysql-8ls"&gt;MySQL&lt;/a&gt; as a geographical data storage.  In this article we're going to have a look at importing the data into something else than a relational database: the search platform &lt;a href="http://lucene.apache.org/solr/"&gt;Solr&lt;/a&gt;. (Yes, I know I've skipped PostgreSQL, but I'll come back to that).&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Solr&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;&lt;a href="http://lucene.apache.org/solr/"&gt;Solr&lt;/a&gt; is "the popular, blazing fast open source enterprise search platform from the Apache Lucene project." Since version 3.1, Solr has support for &lt;a href="http://wiki.apache.org/solr/SpatialSearch"&gt;spatial search&lt;/a&gt;, including geospatial search. It can store coordinates in two different field types: &lt;code&gt;solr.PointType&lt;/code&gt; for n-dimensional points, and &lt;code&gt;solr.LatLonType&lt;/code&gt; for a two-dimensional point for geospatial search. The main difference is that with &lt;code&gt;solr.PointType&lt;/code&gt;, calculations are done according to the flat Earth model, and &lt;code&gt;solr.LatLonType&lt;/code&gt; does calculations for a spherical Earth model—which is just what we need.&lt;/p&gt;
      &lt;p&gt;In this example we will use the default Solr configuration, unless noted otherwise. First we download Solr 3.1 and then untar it with &lt;code&gt;tar -xvzf
apache-solr-3.1.0.tgz&lt;/code&gt;.  Then we edit &lt;code&gt;example/solr/conf/solrconfig.xml&lt;/code&gt; to comment out the section on &lt;code&gt;Query Elevation Component&lt;/code&gt; so that it looks like:&lt;/p&gt;
      &lt;pre&gt;&lt;!--
&lt;searchComponent name="elevator" class="solr.QueryElevationComponent" &gt;
  pick a fieldType to analyze queries
        &lt;str name="queryFieldType"&gt;string&lt;/str&gt;
        &lt;str name="config-file"&gt;elevate.xml&lt;/str&gt;
  &lt;/searchComponent&gt;
--&gt;

&lt;/pre&gt;
      &lt;p&gt;See &lt;a href="http://stackoverflow.com/questions/3631823/solr-queryelevationcomponent-requires-strfield-uniquekeyfield-error"&gt;here&lt;/a&gt; for the reason.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Setting Up Solr&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;Before we can start using Solr, we need to define a schema. This schema is quite analogous to a database schema, except that Solr only has one table.  By default Solr comes with a schema defining lots of fields in &lt;code&gt;example/solr/conf/schema.xml&lt;/code&gt;.  I've changed the whole &lt;code&gt;&lt;fields&gt;&lt;/code&gt; section to look like:&lt;/p&gt;
      &lt;pre&gt;&lt;fields&gt;
        &lt;field name="id" type="string" indexed="true" stored="true" /&gt;
        &lt;field name="type" type="int" indexed="true" stored="true" /&gt;
        &lt;field name="name" type="text" indexed="true" stored="true" /&gt;
        &lt;field name="amenity" type="string" indexed="true" stored="true" /&gt;
        &lt;field name="address" type="text" indexed="true" stored="true" /&gt;
        &lt;field name="postcode" type="text" indexed="true" stored="true" /&gt;
        &lt;field name="phone" type="text" indexed="true" stored="true" /&gt;
        &lt;field name="cuisine" type="string" indexed="true" stored="true" /&gt;
        &lt;field name="location" type="location" indexed="true" stored="true" /&gt;
        &lt;field name="location_0_coordinate" type="double" indexed="true" stored="true" /&gt;
        &lt;field name="location_1_coordinate" type="double" indexed="true" stored="true" /&gt;
&lt;/fields&gt;

&lt;/pre&gt;
      &lt;p&gt;I've defined specific fields for the &lt;code&gt;type&lt;/code&gt; of source (1=node, 2=way/area), the &lt;code&gt;name&lt;/code&gt; of the amenity (name of the pub, hospital, etc), what sort of &lt;code&gt;amenity&lt;/code&gt; it is (cafe, bank, etc), the &lt;code&gt;address&lt;/code&gt; and &lt;code&gt;postcode&lt;/code&gt;, the &lt;code&gt;phone&lt;/code&gt; number, the type of &lt;code&gt;cuisine&lt;/code&gt; as well as the &lt;code&gt;location&lt;/code&gt;. For the location I've also defined two subtypes: &lt;code&gt;location_0_coordinate&lt;/code&gt; and &lt;code&gt;location_1_coordinate&lt;/code&gt;. Solr requires this for multi-dimensional types such as &lt;code&gt;solr.LatLonType&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;The types for each field are also defined in &lt;code&gt;schema.xml&lt;/code&gt;. &lt;code&gt;text&lt;/code&gt; is for a string that is analysed and tokenized (broken down in smaller parts) so that you can search in only parts of the text, &lt;code&gt;string&lt;/code&gt; is for a string of characters that is not analysed or tokenized, and &lt;code&gt;location&lt;/code&gt; contains our latitude/longitude point. Each of those types are associated with a specific Java class with an entry such as:&lt;/p&gt;
      &lt;pre&gt;&lt;fieldType name="location" class="solr.LatLonType" subFieldSuffix="_coordinate"/&gt;

&lt;/pre&gt;
      &lt;p&gt;The &lt;code&gt;subFieldSuffix&lt;/code&gt; in this line configures the suffix in the fields &lt;code&gt;location_0_coordinate&lt;/code&gt; and &lt;code&gt;location_1_coordinate&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;Now we have changed the default schema, we can start Solr with: &lt;code&gt;cd example &amp;&amp;
java -jar start.jar&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;On the PHP side, we need to do a bit of set-up as well. I've been using the &lt;a href="http://pecl.php.net/package/solr"&gt;Solr PECL&lt;/a&gt; extension to talk to Solr which can be installed with &lt;code&gt;pecl
install solr&lt;/code&gt;.  Please do not forget to add the extension to php.ini with &lt;code&gt;extension=solr.so&lt;/code&gt;. Check whether it is installed correctly with: &lt;code&gt;php --ri
solr&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Importing&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;For our import, we need to change the script we used previously quite substantially.  Instead of just using London, I've also downloaded the whole of the UK from &lt;a href="http://geofabrik.de"&gt;Geofabrik&lt;/a&gt; and extracted all amenities from the dataset with &lt;a href="http://wiki.openstreetmap.org/wiki/Osmosis"&gt;Osmosis&lt;/a&gt;:&lt;/p&gt;
      &lt;pre&gt;wget http://download.geofabrik.de/osm/europe/great_britain.osm.bz2

./osmosis-0.39/bin/osmosis -v 5 \
--read-xml file=great_britain.osm.bz2 \
--tf reject-relations \
--tf accept-nodes amenity=* \
--tf reject-ways \
outPipe.0=POI \
\
--read-xml file=great_britain.osm.bz2 \
--tf reject-relations \
--tf accept-ways amenity=* \
--used-node outPipe.0=area \
\
--merge inPipe.0=POI inPipe.1=area \
--write-xml file=great_britain_amenity.osm

&lt;/pre&gt;
      &lt;p&gt;This is going to take quite some time, and will result in a 500MB XML file.&lt;/p&gt;
      &lt;p&gt;The main logic of the importing script remains the same, but instead of adding each item to a database, we now build a Solr import document and add it to the search index.  I've also split the address and postcode into two separate fields, and added the &lt;code&gt;amenity&lt;/code&gt; field to store all the different amenities. For now, I'm filtering out post boxes, parking areas and grave yards. The script, &lt;code&gt;parsepoi-solr.php&lt;/code&gt; is available &lt;a href="http://derickrethans.nl/files/parsepoi-solr.php.txt"&gt;here&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;After downloading, and renaming the downloaded file to &lt;code&gt;parsepoi-solr.php&lt;/code&gt; we can run the script with:&lt;/p&gt;
      &lt;pre&gt;php -dmemory_limit=1G parsepoi-solr.php great_britain_amenity.osm

&lt;/pre&gt;
      &lt;p&gt;When done, this should have imported more than 140.000 items into Solr. You can verify this, by going to &lt;a href="http://localhost:8983/solr/select/?q=*:*&amp;rows=0"&gt;http://localhost:8983/solr/select/?q=*:*&amp;rows=0&lt;/a&gt;&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Querying&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;As you can see, you can query Solr through its HTTP interface quite easily. In fact, all queries to Solr are done over HTTP. The Solr extension however abstracts this away from you for at least the import. I couldn't find any functionality in the extension to do spatial queries, so we'll do it manually.&lt;/p&gt;
      &lt;p&gt;If we look at the URL above, we can divide it into different parts:&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;code&gt;http://localhost:8983/solr/&lt;/code&gt;: The base URL for this Solr instance.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;code&gt;select/?&lt;/code&gt;: The action for running search queries.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;code&gt;q=*:*&lt;/code&gt;: &lt;code&gt;q&lt;/code&gt; is the search query parameter, and its value, &lt;code&gt;*:*&lt;/code&gt; means all fields (the first &lt;code&gt;*&lt;/code&gt;) and all possible values (the second &lt;code&gt;*&lt;/code&gt;). In this case that means return everything.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;code&gt;rows=0&lt;/code&gt;: Do not return any rows, so that we just get a count.&lt;/p&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;We get back the result as an XML file. If we want JSON instead, we can simply append &lt;code&gt;&amp;wt=json&lt;/code&gt;. In case you want a CSV file, append &lt;code&gt;&amp;wt=csv&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;In a first example, all we want to do is to return all cafes within 100 meter. We construct the search query as follows:&lt;/p&gt;
      &lt;ul&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;code&gt;http://localhost:8983/solr/select/?&lt;/code&gt;: The base URL with search action.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;code&gt;q=amenity:cafe&lt;/code&gt;: We search in the field &lt;code&gt;amenity&lt;/code&gt; and look for the value &lt;code&gt;cafe&lt;/code&gt;.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;code&gt;fq={!geofilt}&lt;/code&gt;: We set a query filter to &lt;code&gt;geofilt_&lt;/code&gt;. This filter restricts the result set according to the location (&lt;code&gt;pt&lt;/code&gt;) and the maximum distance from this location (&lt;code&gt;d&lt;/code&gt;) in km.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;code&gt;sfield=location&lt;/code&gt;: The field to use for location information.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;code&gt;pt=51.5375,-0.1934&lt;/code&gt;: The point that we center our search around.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;code&gt;d=0.1&lt;/code&gt;: The maximum distance in kilometers.&lt;/p&gt;
        &lt;/li&gt;
        &lt;li&gt;
          &lt;p&gt;&lt;code&gt;wt=csv&lt;/code&gt;: The format to return, in our case, CSV.&lt;/p&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
      &lt;p&gt;Together this makes the full GET request: &lt;a href="http://localhost:8983/solr/select/?q=amenity:cafe&amp;fq={!geofilt}&amp;sfield=location&amp;pt=51.5375,-0.1934&amp;d=0.1&amp;wt=csv"&gt;http://localhost:8983/solr/select/?q=amenity:cafe&amp;fq={!geofilt}&amp;sfield=location&amp;pt=51.5375,-0.1934&amp;d=0.1&amp;wt=csv&lt;/a&gt;&lt;/p&gt;
      &lt;p&gt;And the result is:&lt;/p&gt;
      &lt;pre&gt;id,phone,cuisine,location,address,location_1_coordinate,name,amenity,type,location_0_coordinate,postcode
w62088838,,,51.53750834,-0.19329616,75 Kilburn High Road,-0.19329616,Costa,cafe,2,51.53750834,
w78337118,,,51.53828298,-0.19410346,101 Kilburn High Road,-0.19410346,Caffè Nero,cafe,2,51.53828298,NW6 6JE
w105467205,,,51.537555925,-0.192445525,274 Belsize Road,-0.192445525,Belsize Cafe,cafe,2,51.537555925,NW6 4BT
w105467209,+44 207 3724002,,51.537624071429,-0.19228221428571,270 Belsize Road,-0.19228221428571,Lord Jim,cafe,2,51.537624071429,NW6 4BT
w107710475,+44 20 76245736,,51.53695276,-0.19263662,2 Kilburn Bridge,-0.19263662,Famished Cafe,cafe,2,51.53695276,NW6 6HT
w107710482,,,51.53717868,-0.19288912,Kilburn Bridge,-0.19288912,Mike's,cafe,2,51.53717868,NW6 6HT
w107710490,+44 20 76246942,,51.53732946,-0.1930577,12 Kilburn Bridge,-0.1930577,La Dolce Vita,cafe,2,51.53732946,NW6 6HT

&lt;/pre&gt;
      &lt;p&gt;Which fields are returned can be configured. In this case, we are only interested in the location and name of each amenity, so we restrict the number of returned fields with: &lt;code&gt;fl=id,location,name&lt;/code&gt;. The result now becomes:&lt;/p&gt;
      &lt;pre&gt;id,location,name
w62088838,51.53750834,-0.19329616,Costa
w78337118,51.53828298,-0.19410346,Caffè Nero
w105467205,51.537555925,-0.192445525,Belsize Cafe
w105467209,51.537624071429,-0.19228221428571,Lord Jim
w107710475,51.53695276,-0.19263662,Famished Cafe
w107710482,51.53717868,-0.19288912,Mike's
w107710490,51.53732946,-0.1930577,La Dolce Vita

&lt;/pre&gt;
      &lt;p&gt;Sadly, the results do not come back ordered by distance from our starting point. In order to do that, we need to add one more query parameter: &lt;code&gt;sort=geodist() asc&lt;/code&gt;.  The full query is now: &lt;a href="http://localhost:8983/solr/select/?q=amenity:cafe&amp;fq={!geofilt}&amp;sfield=location&amp;pt=51.5375,-0.1934&amp;d=0.10&amp;wt=csv&amp;fl=id,location,name&amp;sort=geodist()+asc"&gt;http://localhost:8983/solr/select/?q=amenity:cafe&amp;fq={!geofilt}&amp;sfield=location&amp;pt=51.5375,-0.1934&amp;d=0.10&amp;wt=csv&amp;fl=id,location,name&amp;sort=geodist()+asc&lt;/a&gt;&lt;/p&gt;
      &lt;p&gt;And the result:&lt;/p&gt;
      &lt;pre&gt;id,location,name
w62088838,51.53750834,-0.19329616,Costa
w107710490,51.53732946,-0.1930577,La Dolce Vita
w107710482,51.53717868,-0.19288912,Mike's
w105467205,51.537555925,-0.192445525,Belsize Cafe
w105467209,51.537624071429,-0.19228221428571,Lord Jim
w107710475,51.53695276,-0.19263662,Famished Cafe
w78337118,51.53828298,-0.19410346,Caffè Nero

&lt;/pre&gt;
      &lt;p&gt;It is however not possible to return the distance from our starting point directly with Solr. With the exception being that if you use only the &lt;code&gt;geodist()&lt;/code&gt; function as query argument. In this case, make sure to include the special field &lt;code&gt;score&lt;/code&gt; in the &lt;code&gt;fl=&lt;/code&gt; argument as well. An URL showing this is: &lt;a href="http://localhost:8983/solr/select/?q={!func}geodist()&amp;fq={!geofilt}&amp;sfield=location&amp;pt=51.5375,-0.1934&amp;d=0.10&amp;wt=csv&amp;fl=id,location,name,score&amp;sort=geodist()+asc"&gt;http://localhost:8983/solr/select/?q={!func}geodist()&amp;fq={!geofilt}&amp;sfield=location&amp;pt=51.5375,-0.1934&amp;d=0.10&amp;wt=csv&amp;fl=id,location,name,score&amp;sort=geodist()+asc&lt;/a&gt; Of course, in this case you can not filter for specific amenities.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Conclusion&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;In currently released versions of Solr (3.1 at the time of writing), it is not possible to return the distance to the starting point for the search as part of the result set. The Solr developers have already &lt;a href="https://issues.apache.org/jira/browse/SOLR-1298"&gt;implemented&lt;/a&gt; the capabilities to add the results of function queries to result documents for version 4.0. You can verify this by downloading a &lt;a href="https://builds.apache.org//job/Solr-trunk/lastSuccessfulBuild/artifact/artifacts/"&gt;nightly build&lt;/a&gt; and running the query: &lt;a href="http://localhost:8983/solr/select/?q=amenity:cafe&amp;fq={!geofilt}&amp;sfield=location&amp;pt=51.5375,-0.1934&amp;d=0.10&amp;fl=id,location,name,score,geodist()&amp;sort=geodist()+asc"&gt;http://localhost:8983/solr/select/?q=amenity:cafe&amp;fq={!geofilt}&amp;sfield=location&amp;pt=51.5375,-0.1934&amp;d=0.10&amp;fl=id,location,name,score,geodist()&amp;sort=geodist()+asc&lt;/a&gt; In this query I've added &lt;code&gt;geodist()&lt;/code&gt; to the &lt;code&gt;fl=&lt;/code&gt; parameter. The result now includes an extra field called &lt;code&gt;geodist()&lt;/code&gt;:&lt;/p&gt;
      &lt;pre&gt;&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;response&gt;
  &lt;!-- missing header --&gt;
  &lt;result name="response" numFound="7" start="0" maxScore="4.352648"&gt;
        &lt;doc&gt;
          &lt;str name="id"&gt;w62088838&lt;/str&gt;
          &lt;str name="name"&gt;Costa&lt;/str&gt;
          &lt;str name="location"&gt;51.53750834,-0.19329616&lt;/str&gt;
          &lt;float name="score"&gt;4.352648&lt;/float&gt;
          &lt;double name="geodist()"&gt;0.0072415726934058865&lt;/double&gt;
        &lt;/doc&gt;
        &lt;doc&gt;
          &lt;str name="id"&gt;w107710490&lt;/str&gt;
          &lt;str name="name"&gt;La Dolce Vita&lt;/str&gt;
          &lt;str name="location"&gt;51.53732946,-0.1930577&lt;/str&gt;
          &lt;float name="score"&gt;4.352648&lt;/float&gt;
          &lt;double name="geodist()"&gt;0.030333097323281075&lt;/double&gt;
        &lt;/doc&gt;

&lt;/pre&gt;
      &lt;p&gt;I could not manage to alias it to a different field, or get it to work with the CSV format (&lt;code&gt;wt=csv&lt;/code&gt;).&lt;/p&gt;
      &lt;p&gt;If the return format is &lt;code&gt;xml&lt;/code&gt; (the default) or &lt;code&gt;json&lt;/code&gt;, then the result includes, besides the number of found items, the total search time for this query.  Look for this information in the &lt;code&gt;QTime&lt;/code&gt; field. In all of the examples here, the &lt;code&gt;QTime&lt;/code&gt; has been less than 5, meaning 5 milliseconds. Solr is extremely fast, even with huge amounts of data. I will try to import the amenities of the whole "planet" at some point, and report back with some benchmarking information.&lt;/p&gt;
      &lt;p&gt;In the next installment of this series on storing geospatial data, I will be looking at using &lt;a href="http://www.mongodb.org"&gt;MongoDB&lt;/a&gt; as data store for geographical information.&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201106140904</guid>
      <pubDate>Tue, 14 Jun 2011 08:04:00 +0000</pubDate>
    </item>
    <item>
      <title>What is OpenStreetMap?</title>
      <link>http://derickrethans.nl/what-is-openstreetmap.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="what_is_openstreetmap"/&gt;What is OpenStreetMap?&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Thursday, May 12th 2011, 09:04 BST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;With people more and more complaining that Google Maps &lt;a href="http://www.flickr.com/photos/harrywood/5702396769/"&gt;gets it wrong&lt;/a&gt;, I often reply that the complainers have a look at &lt;a href="http://openstreetmap.org"&gt;OpenStreetMap&lt;/a&gt; instead.  But there are a few misunderstandings on what OpenStreetMap actually is.&lt;/p&gt;
      &lt;img src="http://c.tile.openstreetmap.org/18/130974/87188.png" class="right" alt="87188.png"/&gt;
      &lt;p&gt;OpenStreetMap has its main site at &lt;a href="http://openstreetmap.org"&gt;http://openstreetmap.org&lt;/a&gt;. This shows a rendering of the map that we like to call a "&lt;a href="http://wiki.openstreetmap.org/wiki/Slippy_Map"&gt;slippy map&lt;/a&gt;". This is an online map, that can interactively be zoomed and paned. The slippy map on the site is nothing more than a display of map tiles, static images rendered from OpenStreetMap data as an example of what you can do with it. The real power of OpenStreetMap is not this default rendering, but the possibility to actually access the data behind this map rendering.  And this is where it differs from &lt;a href="http://maps.google.co.uk/"&gt;Google maps&lt;/a&gt;, &lt;a href="http://www.bing.com/maps/"&gt;Bing Maps&lt;/a&gt; and &lt;a href="http://uk.maps.yahoo.com/"&gt;Yahoo! Maps&lt;/a&gt; and various others. OpenStreetMap is the only mapping service that allows you to do something more than just look at pretty map tiles. OpenStreetMap is a database project, with as its main purpose to have an exhaustive database of every street, city, road, building etc on the planet, and not a map display project.&lt;/p&gt;
      &lt;p&gt;Now what does that actually mean? For one thing, it means that the sample map tiles (that are displayed in the slippy map) are not meant to be used heavily from applications.  It's all right to show a map tile of your neighbourhood on your web site, but it's not okay to write a mobile application that allows you to scrape map tiles of large areas. Have a look at the general &lt;a href="http://wiki.openstreetmap.org/wiki/Tile_usage_policy"&gt;tile usage policy&lt;/a&gt; for some background.  The only reason why there is a sample rendering is to aid mappers with improving the data that is the core of OpenStreetMap.&lt;/p&gt;
      &lt;p&gt;Obviously, the OpenStreetMap project wants its data to be used. But if you're not allowed to use the map tiles en-masse, then how can you use the data?&lt;/p&gt;
      &lt;p&gt;First of all, you can query the OpenStreetMap database in various ways. In most cases, you're going to get an XML file with descriptions of nodes (points of interest, facilities such as toilets, benches, addresses), ways (roads, water ways, transport routes) and areas (buildings, lakes). There is some more info on the (very simple schema) &lt;a href="http://wiki.openstreetmap.org/wiki/Xml_schema"&gt;here&lt;/a&gt;. This data you can parse and import (as I've shown in a previous &lt;a href="http://drck.me/spat-osm-sqlite-8la"&gt;article&lt;/a&gt;) and do all kinds of cool tricks with, such as finding out the closest pub. This functionality is not available on the main OpenStreetMap site, because "OpenStreetMap does not aim to create and host every webservice possible, but to provide the data so that others can"  make awesome map-related applications.&lt;/p&gt;
      &lt;p&gt;Secondly there is a growing number of web sites that uses the OpenStreetMap data to render specialized maps.  There is &lt;a href="http://opencyclemap.org/?zoom=13&amp;lat=51.50806&amp;lon=-0.14025&amp;layers=B0"&gt;OpenCycleMap&lt;/a&gt; that renders information related to using your bicycle and &lt;a href="http://www.openbusmap.org/?zoom=13&amp;lat=51.50526&amp;lon=-0.13869&amp;layers=BT"&gt;Öpvnkarte&lt;/a&gt; for public transport. There is even a tool that uses the &lt;a href="http://milliams.dev.openstreetmap.org/postcodefinder/NW6%206TB/"&gt;postcode data&lt;/a&gt; in OpenStreetMap.  It's quite possible to render your own maps as well, but that I will get back to in a later article.&lt;/p&gt;
      &lt;img src="http://open.mapquestapi.com/staticmap/v3/getmap?size=320,260&amp;zoom=12&amp;center=51.51,-0.15" class="left" alt="getmap?size=320,260&amp;zoom=12&amp;center=51.51,-0.15"/&gt;
      &lt;p&gt;&lt;a href="http://open.mapquest.co.uk/link/2-Yq2eUVEO"&gt;MapQuest&lt;/a&gt; is a good example of a company that uses OpenStreetMap data. They have made their own rendering with its own styles for a visually different map. Their site implements &lt;a href="http://open.mapquest.co.uk/link/6-eU7PhaC7"&gt;searching&lt;/a&gt; for pubs/bars and restaurants for example and MapQuest are also &lt;a href="http://wiki.openstreetmap.org/wiki/MapQuest#Access_.2F_Downloads"&gt;happy with you using their map tiles&lt;/a&gt; for your applications. Recently, they have also announced &lt;a href="http://devblog.mapquest.com/2011/05/11/get-creative-with-the-open-static-maps-api/"&gt;a simple API&lt;/a&gt; to generate a static map based on OpenStreetMap with their rendering style.&lt;/p&gt;
      &lt;p&gt;The map above is generated by just requesting &lt;a href="http://open.mapquestapi.com/staticmap/v3/getmap?size=320,260&amp;zoom=12&amp;center=51.51,-0.15"&gt;http://open.mapquestapi.com/staticmap/v3/getmap?size=320,260&amp;zoom=12&amp;center=51.51,-0.15&lt;/a&gt;&lt;/p&gt;
      &lt;p&gt;Another major difference with established mapping providers is the ability to edit the map yourself. You can add your own business, houses, addresses, postcodes, your favourite restaurants with either &lt;a href="http://wiki.openstreetmap.org/wiki/Potlatch_2"&gt;on-line&lt;/a&gt; or &lt;a href="http://wiki.openstreetmap.org/wiki/Josm"&gt;off-line&lt;/a&gt; editors. My list of recent edits is &lt;a href="http://tinyurl.com/34l8wqu"&gt;here&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;There is also a whole plethora of special renderings of the map data to assist with debugging issues with the map.  There is the "&lt;a href="http://osm.org/go/cIrZqd@A?layers=N"&gt;no name&lt;/a&gt;" rendering which shows roads without name, &lt;a href="http://www.itoworld.com/static/index.html"&gt;ITO World's&lt;/a&gt; &lt;a href="http://www.itoworld.com/static/osm_analysis.html"&gt;osm analysis&lt;/a&gt; that shows deviations of road names from another source in the UK, &lt;a href="http://keepright.ipax.at/report_map.php?zoom=15&amp;lat=51.49309&amp;lon=-0.13344"&gt;keepright&lt;/a&gt; and &lt;a href="http://tools.geofabrik.de/osmi/?zoom=15&amp;lat=51.49309&amp;lon=-0.13344&amp;view=tagging"&gt;OSM Inspector&lt;/a&gt; that have some automated checks and a tool to &lt;a href="http://oscompare.raggedred.net/?layers=0B00FFT&amp;zoom=17&amp;lat=51.53719&amp;lon=-0.19543"&gt;overlay postcodes&lt;/a&gt; for checking.&lt;/p&gt;
      &lt;p&gt;Obviously, there are a few things missing that would make OpenStreetMap an even better resource for mapping related applications and web-applications. Feel free to suggest things that you find missing or lacking in the comments of this article. I'd be happy to hear why you would think that OpenStreetMap is not a good fit for your applications and usage.&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201105120904</guid>
      <pubDate>Thu, 12 May 2011 08:04:00 +0000</pubDate>
    </item>
    <item>
      <title>Spatial Indexes: MySQL</title>
      <link>http://derickrethans.nl/spatial-indexes-mysql.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="spatial_indexes_mysql"/&gt;Spatial Indexes: MySQL&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Tuesday, April 12th 2011, 09:04 BST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;In two previous articles I introduced &lt;a href="http://drck.me/spat-dist-8kf"&gt;The spherical Earth model&lt;/a&gt; and &lt;a href="http://drck.me/spat-osm-sqlite-8la"&gt;importing data&lt;/a&gt; into SQLite for querying geographical data. In this article we're going to have a look at importing the data into &lt;a href="http://dev.mysql.com/"&gt;MySQL&lt;/a&gt; and finding out how to best store and query spatial data in the databases.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;MySQL&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;MySQL has some support for &lt;a href="http://dev.mysql.com/doc/refman/5.1/en/spatial-extensions.html"&gt;Spatial Extensions&lt;/a&gt;, but it's not particularly useful. For example, there is no way to query anything within the radius around a specific point. Their community pages list a &lt;a href="http://forge.mysql.com/tools/tool.php?id=41"&gt;method&lt;/a&gt; of implementing it, but it only calculates for a flat Earth model.&lt;/p&gt;
      &lt;p&gt;Instead, we'll have to implement our own algorithms again. But first of all, let us import the data into MySQL. First we create the MySQL database:&lt;/p&gt;
      &lt;pre&gt;derick@whisky:~$ mysqladmin -u root -p create poi
mysql&gt; CREATE TABLE poi(id int, type int, lat float, lon float, name char(255), address char(255), cuisine char(64), phone char(18));

&lt;/pre&gt;
      &lt;p&gt;And then we take the &lt;a href="http://derickrethans.nl/files/parsepoi.php.txt"&gt;script&lt;/a&gt; from the previous article on &lt;a href="http://drck.me/spat-osm-sqlite-8la"&gt;importing data&lt;/a&gt;, and change the third line from:&lt;/p&gt;
      &lt;pre&gt;$d = ezcDbFactory::create( 'sqlite://' . dirname( __FILE__ ) . '/pois.sqlite' );

&lt;/pre&gt;
      &lt;p&gt;to:&lt;/p&gt;
      &lt;pre&gt;$d = ezcDbFactory::create( 'mysql://root:root@localhost/poi' );

&lt;/pre&gt;
      &lt;p&gt;Of course, substitute the username and password (&lt;code&gt;root&lt;/code&gt;, &lt;code&gt;root&lt;/code&gt;) and the database name (&lt;code&gt;poi&lt;/code&gt;) to one that suits yourself. We then run again:&lt;/p&gt;
      &lt;pre&gt;php parseoi.php.txt great_britain_pubs.osm

&lt;/pre&gt;
      &lt;p&gt;And check that our import is complete:&lt;/p&gt;
      &lt;pre&gt;mysql&gt; SELECT count(*) from poi\G
*************************** 1. row ***************************
count(*): 28147

&lt;/pre&gt;
      &lt;p&gt;Now that we have all the POIs imported, we can query the database. Because MySQL doesn't have working spatial extensions, nor the register-function capabilities from SQLite such as we saw in the previous article, we have to come up with a new solution. The most obvious one is writing a stored procedure for the task, another (non-explored option) would be to write a &lt;a href="http://dev.mysql.com/doc/refman/5.1/en/adding-udf.html"&gt;user defined function&lt;/a&gt;. Writing the stored procedure is simple enough; we just have to convert the &lt;code&gt;distance()&lt;/code&gt; function to SQL. On the MySQL command line you can enter:&lt;/p&gt;
      &lt;pre&gt;delimiter //

CREATE FUNCTION distance (latA double, lonA double, latB double, LonB double)
    RETURNS double DETERMINISTIC
BEGIN
    SET @RlatA = radians(latA);
    SET @RlonA = radians(lonA);
    SET @RlatB = radians(latB);
    SET @RlonB = radians(LonB);
    SET @deltaLat = @RlatA - @RlatB;
    SET @deltaLon = @RlonA - @RlonB;
    SET @d = SIN(@deltaLat/2) * SIN(@deltaLat/2) +
        COS(@RlatA) * COS(@RlatB) * SIN(@deltaLon/2)*SIN(@deltaLon/2);
    RETURN 2 * ASIN(SQRT(@d)) * 6371.01;
END//

&lt;/pre&gt;
      &lt;p&gt;Fetching all the pubs within a 250 meter radius around 51.5375°N, 0.1933°W is than as easy as running:&lt;/p&gt;
      &lt;pre&gt;mysql&gt; SELECT name, address, phone
       FROM poi
       WHERE DISTANCE(lat, lon, 51.5375, -0.1933) &lt; 0.25;

&lt;/pre&gt;
      &lt;p&gt;With the result being:&lt;/p&gt;
      &lt;pre&gt;| name            | address                        | phone           |
+-----------------+--------------------------------+-----------------+
| Mrs Betsy Smith | Kilburn High Road 77 , NW6 6HY | +44 20 76245793 |
| The Cock Tavern | Kilburn High Road 125          | NULL            |
| The Old Bell    | NULL                           | NULL            |
| The Westbury    | Kilburn High Road 34 , NW6 5UA | +44 20 76257500 |
+-----------------+--------------------------------+-----------------+
4 rows in set (0.72 sec)

&lt;/pre&gt;
      &lt;p&gt;If we want to also include the distance from the centre point in the result, we need to modify the query to:&lt;/p&gt;
      &lt;pre&gt;mysql&gt; SELECT name, DISTANCE(lat, lon, 51.5375, -0.1933) AS dist
       FROM poi
       HAVING dist &lt; 0.25
       ORDER BY dist;

&lt;/pre&gt;
      &lt;p&gt;with as result:&lt;/p&gt;
      &lt;pre&gt;| name            | dist                |
+-----------------+---------------------+
| Mrs Betsy Smith | 0.00825473345748987 |
| The Cock Tavern |     0.2420193460511 |
| The Old Bell    |   0.103123484090313 |
| The Westbury    |   0.150294300836645 |
+-----------------+---------------------+
4 rows in set (0.72 sec)

&lt;/pre&gt;
      &lt;p&gt;In both cases, the query takes 0.72 seconds. This is not overly fast, and the main reason for this is that the &lt;code&gt;distance()&lt;/code&gt; function has to be called for every row in the table. An index can not be created on this either. However, what we can do is to filter out the results roughly first, by calculating a bounding box of latitude/longitude pairs around the centre point. Calculating the latitude boundaries can be done by:&lt;/p&gt;
      &lt;pre&gt;d = (distInKm / 6371.01 * 2π) * 360
lat1 = centreLat - d
lat2 = centreLat + d

&lt;/pre&gt;
      &lt;p&gt;In our example that becomes:&lt;/p&gt;
      &lt;pre&gt;d = (0.250 / 6371.01 * 2π) * 360 = 0.0022483
lat1 = 51.5375 - 0.0022483 = 51.5352.. -&gt; 51.5351
lat2 = 51.5375 + 0.0022483 = 51.5397.. -&gt; 51.5398

&lt;/pre&gt;
      &lt;p&gt;Or in PHP:&lt;/p&gt;
      &lt;pre&gt;&lt;?php
function findLatBoundary($dist, $lat, &amp;$lat1, &amp;$lat2)
{
    $d = ($dist / 6371.01 * 2 * M_PI) * 360;
    $lat1 = $lat - $d;
    $lat2 = $lat + $d;
}
?&gt;

&lt;/pre&gt;
      &lt;p&gt;We round slightly up and down to combat inaccuracies in the calculations—it is a rough estimate after all.  After adding the index on the &lt;code&gt;lat&lt;/code&gt; column, and the index on the &lt;code&gt;lon&lt;/code&gt; column, we reissue the query:&lt;/p&gt;
      &lt;pre&gt;mysql&gt; CREATE index poi_lat ON poi(lat);
mysql&gt; CREATE index poi_lon ON poi(lon);

mysql&gt; SELECT name, DISTANCE(lat, lon, 51.5375, -0.1933) AS dist
       FROM poi
       WHERE lat BETWEEN 51.5351 AND 51.5398
       HAVING dist &lt; 0.25;

&lt;/pre&gt;
      &lt;p&gt;Which returns the same result as before, but faster:&lt;/p&gt;
      &lt;pre&gt;| name            | dist                |
+-----------------+---------------------+
| The Westbury    |   0.150294300836645 |
| The Old Bell    |   0.103123484090313 |
| Mrs Betsy Smith | 0.00825473345748987 |
| The Cock Tavern |     0.2420193460511 |
+-----------------+---------------------+
4 rows in set (0.01 sec)

&lt;/pre&gt;
      &lt;p&gt;We can pre-filter the result set even more, by also limiting on the longitude boundary. This involves a few more calculations than for the latitude boundaries. The distance in degrees longitude that belongs to a distance in km depends on the latitude of the location. So first we need to calculate the latitude boundaries as we have done above, and with that information calculate the longitude boundaries.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/sphere-distance.png" alt="sphere-distance.png"/&gt;
      &lt;p&gt;In the first step (red), we calculate the northern and southern boundaries of the circle. We then calculate the western and eastern boundaries for the northern boundary (green), and southern boundary (blue).&lt;/p&gt;
      &lt;p&gt;In PHP this algorithm becomes:&lt;/p&gt;
      &lt;pre&gt;&lt;?php
function findLonBoundary($dist, $lat, $lon, $lat1, $lat2, &amp;$lon1, &amp;$lon2)
{
    $d = $lat - $lat1;

    $d1 = $d / cos(deg2rad($lat1));
    $d2 = $d / cos(deg2rad($lat2));

    $lon1 = min($lon - $d1, $lon - $d2);
    $lon2 = max($lon + $d1, $lon + $d2);
}
?&gt;

&lt;/pre&gt;
      &lt;p&gt;If we use both functions we calculate as boundaries:&lt;/p&gt;
      &lt;pre&gt;&lt;?php
$dist = 0.25;
$lat = 51.5375;
$lon = -0.1933;

findLatBoundary($dist, $lat, $lat1, $lat2);
findLonBoundary($dist, $lat, $lon, $lat1, $lat2, $lon1, $lon2);

echo "SELECT name, DISTANCE(lat, lon, $lat, $lon) AS dist
      FROM poi
      WHERE lat BETWEEN $lat1 AND $lat2
        AND lon BETWEEN $lon1 AND $lon2
      HAVING dist &lt; $dist
      ORDER BY dist;\n";
?&gt;

&lt;/pre&gt;
      &lt;p&gt;Which returns the query to execute:&lt;/p&gt;
      &lt;pre&gt;SELECT name, DISTANCE(lat, lon, 51.5375, -0.1933) AS dist
FROM poi
WHERE lat BETWEEN 51.535251699514 AND 51.539748300486
  AND lon BETWEEN -0.19691479627963 AND -0.18968520372037
  HAVING dist &lt; 0.25
  ORDER BY dist;

&lt;/pre&gt;
      &lt;p&gt;If we run this query, we get as result:&lt;/p&gt;
      &lt;pre&gt;| name            | dist                |
+-----------------+---------------------+
| Mrs Betsy Smith | 0.00825473345748987 |
| The Old Bell    |   0.103123484090313 |
| The Westbury    |   0.150294300836645 |
| The Cock Tavern |     0.2420193460511 |
+-----------------+---------------------+
4 rows in set (0.00 sec)

&lt;/pre&gt;
      &lt;p&gt;As you can see, we are getting the result a lot faster now—0.00 sec vs 0.72 sec.  Please do note, that if we would just have used the boundaries without using the &lt;code&gt;HAVING dist &lt; 0.25&lt;/code&gt; clause, we would have gotten one extra result that is slightly too far away (0.281 km):&lt;/p&gt;
      &lt;pre&gt;| name            | dist                |
+-----------------+---------------------+
| Mrs Betsy Smith | 0.00825473345748987 |
| The Old Bell    |   0.103123484090313 |
| The Westbury    |   0.150294300836645 |
| The Cock Tavern |     0.2420193460511 |
| Bar Hemia       |   0.281138534935208 |
+-----------------+---------------------+

&lt;/pre&gt;
      &lt;p&gt;
        &lt;strong&gt;Conclusion&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;In this article we saw how we can use a MySQL stored procedure to find our pubs and bars within a certain distance from a central location.  In the next part, we will be looking on how to solve the same problem with &lt;a href="http://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt;.&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201104120904</guid>
      <pubDate>Tue, 12 Apr 2011 08:04:00 +0000</pubDate>
    </item>
    <item>
      <title>Spatial Indexes: Fetching Data/SQLite</title>
      <link>http://derickrethans.nl/spatial-indexes-data-sqlite.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="spatial_indexes_fetching_data_sqlite"/&gt;Spatial Indexes: Fetching Data/SQLite&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; London, UK&lt;/div&gt;
        &lt;div class="date"&gt;Thursday, March 31st 2011, 09:17 BST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;In a previous &lt;a href="http://drck.me/spat-dist-8kf"&gt;article&lt;/a&gt; I introduced 'The Flat Earth Model' and the 'The spherical Earth model'. In this article we're going to have a look at fetching a data set and importing them into a SQLite database to query from PHP. What better data set is there to import than all of the UK's pubs?&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Getting the Data&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;In order to get a suitable data set we are going to use the data from the &lt;a href="http://openstreetmap.org"&gt;OpenStreetMap&lt;/a&gt; project. This project is mainly concerned with making an open map of the entire globe, but it also contains a vast database of points-of-interest (POI). OpenStreetMap contributes seems to be a big fan of pubs, and hence they are mapped really well. POIs can either be stored as a node (a single point with a geographical location) such as &lt;a href="http://www.openstreetmap.org/browse/node/603112458"&gt;The Long Acre&lt;/a&gt; or as a closed way (an ordered collection of nodes where the first and last node are the same), such as &lt;a href="http://www.openstreetmap.org/browse/way/72773168"&gt;Brondes Age&lt;/a&gt;.&lt;/p&gt;
      &lt;p&gt;I will demonstrate two methods to fetch the pubs in an XML format containing nodes and ways.  The first method is with &lt;a href="http://wiki.openstreetmap.org/wiki/Xapi"&gt;XAPI&lt;/a&gt;. XAPI is an interface to the OpenStreetMap database to allow users to query and filter items. In order to fetch data through it, you specify a bounding box and a predicate. A bounding box specifies the most Eastern, Southern, Western and Northern coordinates. The UK has roughly as bounding box &lt;em&gt;9.05°W, 48.77°N, 2.19°E, 58.88°N&lt;/em&gt;, or short: &lt;em&gt;-9.05, 48.77, 2.19, 58.88&lt;/em&gt;. Fetching the data can simply be done by querying the server with wget:&lt;/p&gt;
      &lt;pre&gt;wget -O pubs.xml 'http://xapi.openstreetmap.org/api/0.6/*[amenity=pub][bbox=-9.05,48.77,2.19,58.88]'

&lt;/pre&gt;
      &lt;p&gt;This is going to take a long time; and will most likely just not work or time-out. The current XAPI server, written in an obscure programming language called &lt;a href="http://en.wikipedia.org/wiki/MUMPS"&gt;MUMPS&lt;/a&gt;, is extremely unreliable and is really slow. A new version of XAPI build in Java is on the way, but right now it's limited to 10 square degrees.&lt;/p&gt;
      &lt;p&gt;Luckily, there is an alternative in the form of parsing (an extract of) the planet file. The planet file is an enormous database dump of OpenStreetMap's data. The people at &lt;a href="http://geofabrik.de"&gt;Geofabrik&lt;/a&gt; have extracts for specific parts of the world at &lt;a href="http://download.geofabrik.de/osm/"&gt;http://download.geofabrik.de/osm/&lt;/a&gt;. From them I downloaded the &lt;code&gt;europe/great_britain.osm.pbf&lt;/code&gt; file (274 MB) for my example. With a tool called &lt;a href="http://wiki.openstreetmap.org/wiki/Osmosis"&gt;Osmosis&lt;/a&gt; we can then filter out all pubs into a similar formatted XML file. I am only giving the command to do our task at hand, but a detailed usage guide for Osmosis is &lt;a href="http://wiki.openstreetmap.org/wiki/Osmosis/Detailed_Usage"&gt;available&lt;/a&gt; too. The command runs in about four minutes and looks like:&lt;/p&gt;
      &lt;pre&gt;./osmosis-0.38/bin/osmosis -v 5 \
\
--read-pbf file=great_britain.osm.pbf \
--tf reject-relations \
--tf accept-nodes amenity=pub,bar \
--tf reject-ways \
outPipe.0=POI \
\
--read-pbf file=great_britain.osm.pbf \
--tf reject-relations \
--tf accept-ways amenity=pub,bar \
--used-node \
outPipe.0=area \
\
--merge inPipe.0=POI inPipe.1=area \
--write-xml file=great_britain_pubs.osm

&lt;/pre&gt;
      &lt;p&gt;
        &lt;strong&gt;Importing the Data&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;The resulting XML file has two important elements: nodes and ways. The XML for The Long Acre looks like:&lt;/p&gt;
      &lt;pre&gt;&lt;node id="603112458"
  version="1" timestamp="2010-01-02T16:12:57Z"
  uid="1185" user="dankarran" changeset="3519803"
  lat="51.511831" lon="-0.126789"&gt;

  &lt;tag k="amenity" v="pub"/&gt;
  &lt;tag k="name" v="The Long Acre"/&gt;
&lt;/node&gt;

&lt;/pre&gt;
      &lt;p&gt;Important here are the latitude (51.51°N) and longitude (0.12°W) and of course, the name of the pub in the &lt;em&gt;tag&lt;/em&gt; element.&lt;/p&gt;
      &lt;p&gt;And the XML for Brondes Age (a way) is a bit more complex, and looks like:&lt;/p&gt;
      &lt;pre&gt;&lt;way id="72773168"
  version="2" timestamp="2011-01-31T10:07:45Z"
  uid="346" user="Tom Chance" changeset="7143502"&gt;

  &lt;nd ref="863936779"/&gt;
  &lt;nd ref="863936774"/&gt;
  &lt;nd ref="863936777"/&gt;
  &lt;nd ref="863936796"/&gt;
  &lt;nd ref="863936791"/&gt;
  &lt;nd ref="863936768"/&gt;
  &lt;nd ref="863936779"/&gt;
  &lt;tag k="addr:housenumber" v="328"/&gt;
  &lt;tag k="addr:postcode" v="NW6 2QN"/&gt;
  &lt;tag k="addr:street" v="Kilburn High Road"/&gt;
  &lt;tag k="amenity" v="pub"/&gt;
  &lt;tag k="building" v="yes"/&gt;
  &lt;tag k="contact:email" v="brondesage@aol.com"/&gt;
  &lt;tag k="contact:phone" v="+44 20 76249010"/&gt;
  &lt;tag k="contact:website" v="http://www.brondesage.com/"/&gt;
  &lt;tag k="name" v="Brondes Age"/&gt;
  &lt;tag k="toilets" v="yes"/&gt;
  &lt;tag k="toilets:access" v="customers"/&gt;
&lt;/way&gt;

&lt;/pre&gt;
      &lt;p&gt;There is no latitude and longitude, but instead there are references to nodes (the &lt;em&gt;nd&lt;/em&gt; elements). There is also a large collection of descriptive tags, such as &lt;em&gt;addr:housenumber&lt;/em&gt; and &lt;em&gt;contact:phone&lt;/em&gt;. In order to calculate the latitude and longitude of Brondes Age we can either take the &lt;em&gt;correct&lt;/em&gt; approach by calculating the &lt;a href="http://paulbourke.net/geometry/polyarea/"&gt;centroid&lt;/a&gt; or we can simply take the average latitude and longitude of all the nodes. To make things simple, I will take the simple approach.&lt;/p&gt;
      &lt;p&gt;In order to find the latitude and longitude of all the nodes, we simply scan through the file again and find all the nodes that correspond to the &lt;em&gt;ref&lt;/em&gt; attribute of each &lt;em&gt;nd&lt;/em&gt; element. We should disregard the last one of each &lt;em&gt;way&lt;/em&gt; though, as it is the same as the first one.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;SQLite&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;To start, we will use a very simple RDBMS: &lt;a href="http://www.sqlite.org/"&gt;SQLite&lt;/a&gt;. We will still have to define a database schema. We can simply do that with:&lt;/p&gt;
      &lt;pre&gt;derick@whisky:~$ sqlite pois.sqlite
sqlite&gt; CREATE TABLE poi(id int, type int, lat float, lon float, name char, address char, cuisine char, phone char);

&lt;/pre&gt;
      &lt;p&gt;The importing of data then can be done by this "simple" &lt;a href="http://derickrethans.nl/files/parsepoi.php.txt"&gt;script&lt;/a&gt; (after adjusting the path to eZ Components/ &lt;a href="http://incubator.apache.org/zetacomponents/"&gt;Zeta Components&lt;/a&gt;) with:&lt;/p&gt;
      &lt;pre&gt;php parsepoi.php.txt great_britain_pubs.osm

&lt;/pre&gt;
      &lt;p&gt;After running the import script, there should be about 28000 POIs in the database.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Querying the Data&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;Once we have imported the POIs into our SQLite database, we are ready to query them. SQLite does not have a very extensive set of &lt;a href="http://www.sqlite.org/lang_corefunc.html"&gt;functions&lt;/a&gt; so we can not do the calculation in the query directly. Just to iterate from the &lt;a href="http://drck.me/spat-dist-8kf"&gt;previous&lt;/a&gt; article in this series, the formula for calculating the distance in a spherical Earth model is:&lt;/p&gt;
      &lt;pre&gt;&lt;?php
function distance($latA, $lonA, $latB, $lonB)
{
  // convert from degrees to radians
  $latA = deg2rad($latA); $lonA = deg2rad($lonA);
  $latB = deg2rad($latB); $lonB = deg2rad($lonB);

  // calculate absolute difference for latitude and longitude
  $dLat = ($latA - $latB);
  $dLon = ($lonA - $lonB);

  // do trigonometry magic
  $d =
    sin($dLat/2) * sin($dLat/2) +
    cos($latA) * cos($latB) * sin($dLon/2) *sin($dLon/2);
  $d = 2 * asin(sqrt($d));
  return $d * 6371;
}
?&gt;

&lt;/pre&gt;
      &lt;p&gt;One solution would be to query the database, and then use the &lt;code&gt;distance()&lt;/code&gt; function to filter out unwanted elements, like:&lt;/p&gt;
      &lt;pre&gt;&lt;?php
include 'distance.php';
require '/home/derick/dev/zetacomponents/trunk/Base/src/ezc_bootstrap.php';
$d = ezcDbFactory::create( 'sqlite://' . dirname( __FILE__ ) . '/pois.sqlite' );

// Centre point
$lat = 51.5375;
$lon = -0.1933;

// Distance (in km)
$wantedD = 0.25;

$q = $d-&gt;createSelectQuery();
$q-&gt;select( '*' )-&gt;from( 'poi' );
$s = $q-&gt;prepare();
$s-&gt;execute();

foreach ( $s as $res )
{
  $e = distance( $lat, $lon, $res['lat'], $res['lon'] );
  if ( $e &lt; $wantedD )
  {
    echo sprintf( '%.3f,%.3f %-40s %.2f km away',
      $res['lat'], $res['lon'], $res['name'],
      $e ), "\n";
  }
}
?&gt;

&lt;/pre&gt;
      &lt;p&gt;This will show all pubs in a 250 meter radius around 51.53°N, 0.19°W:&lt;/p&gt;
      &lt;pre&gt;derick@whisky:/home/httpd/html/test/maps$ php fetch-sqlite-simple.php
51.538,-0.193 Mrs Betsy Smith                          0.01 km away
51.539,-0.195 The Cock Tavern                          0.24 km away
51.537,-0.192 The Old Bell                             0.10 km away
51.537,-0.192 The Westbury                             0.15 km away

&lt;/pre&gt;
      &lt;p&gt;Of course, this is not very efficient as &lt;strong&gt;all&lt;/strong&gt; items are selected from the database, and then filtered out depending on their calculated distance. Luckily, SQLite supports user defined functions written in PHP. This would mean that we can increase performance a bit by letting PHP's internals call the distance function for every row:&lt;/p&gt;
      &lt;pre&gt;&lt;?php
include 'distance.php';
require '/home/derick/dev/zetacomponents/trunk/Base/src/ezc_bootstrap.php';
$d = ezcDbFactory::create( 'sqlite://' . dirname( __FILE__ ) . '/pois.sqlite' );

// Register SQLite function "dist" to our PHP function "distance".
$d-&gt;sqliteCreateFunction( 'dist', 'distance' );

// Centre point
$lat = 51.5375;
$lon = -0.1933;

// Distance (in km)
$wantedD = 0.25;

$q = $d-&gt;createSelectQuery();

// Use the user defined dist() function as additional column
$q-&gt;select( "*, dist($lat, $lon, lat, lon) as e" )
  -&gt;from( 'poi' )
  -&gt;where( "e &lt; $wantedD" );

$s = $q-&gt;prepare();
$s-&gt;execute();

foreach ( $s as $res )
{
  echo sprintf( '%.3f,%.3f %-40s %.2f km away',
    $res['lat'], $res['lon'], $res['name'],
    $res['e'] ), "\n";
}
?&gt;

&lt;/pre&gt;
      &lt;p&gt;The result is naturally the same as before.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Conclusion&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;In this installment we have seen how to retrieve information from &lt;a href="http://openstreetmap.org"&gt;OpenStreetMap&lt;/a&gt;'s database and import them into SQLite for querying. In the next installment we will have a look at how to import and query with MySQL and PostgreSQL.&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201103310917</guid>
      <pubDate>Thu, 31 Mar 2011 08:17:00 +0000</pubDate>
    </item>
    <item>
      <title>Spatial Indexes: Calculating Distance</title>
      <link>http://derickrethans.nl/spatial-indexes-calculating-distance.html</link>
      <description>&lt;div class="article"&gt;
  &lt;div class="body"&gt;
    &lt;div class="articleListItem"&gt;
      &lt;h1&gt;&lt;a name="spatial_indexes_calculating_distance"/&gt;Spatial Indexes: Calculating Distance&lt;/h1&gt;
      &lt;dl class="head"/&gt;
      &lt;div class="articleMetaData"&gt;
        &lt;div class="location"&gt; Montréal, Canada&lt;/div&gt;
        &lt;div class="date"&gt;Wednesday, March 9th 2011, 07:17 EST&lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;During my "Geolocation and Mapping with PHP" talk that I've given a few times I briefly touch on the subject of indexes on data-sets of spatial data. This isn't as simple as just solving &lt;a href="http://en.wikipedia.org/wiki/Pythagorean_theorem"&gt;Pythagoras theorem&lt;/a&gt; and this article is meant to clarify this.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;The flat Earth model&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;Pythagoras theorem can be used to calculate the distance between two points quite easily; you take the square root of the square of the absolute vertical distance plus the square of the absolute horizontal distance; in short:&lt;/p&gt;
      &lt;pre&gt;d = √(|x1 - x2|² + |y1 - y2|²)

&lt;/pre&gt;
      &lt;p&gt;or in PHP:&lt;/p&gt;
      &lt;pre&gt;$d = sqrt(pow(abs($x1 - $x2), 2) + pow(abs($y1 - $y2), 2));

&lt;/pre&gt;
      &lt;p&gt;If you take for example London's coordinates (51.50°N, 0.13°W) and Amsterdam's coordinates (52.37°N, 4.90°E) we can calculate the distance with:&lt;/p&gt;
      &lt;pre&gt;d = √(|-0.13 - 4.90|² + |51.50 - 52.37|²)
d = √(5.03² + 0.87²)
d = √(26.0578)
d ≅ 5.10

&lt;/pre&gt;
      &lt;p&gt;And on a map:&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/distance-amsterdam-london.png" alt="distance-amsterdam-london.png"/&gt;
      &lt;p&gt;But what does a difference of "5.10°" actually mean? How far is this in useful units, such as meters?&lt;/p&gt;
      &lt;p&gt;If we show the whole map of which the above is an extract, we come to:&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/map-flat.png" alt="map-flat.png"/&gt;
      &lt;p&gt;The distance around the equator, and through the poles is roughly the same, 40.000km (please be aware that the blue line only shows half of it, the other half is going through the anti-meridian at 180°W/E). 5.03° in East-West difference is then about &lt;code&gt;40 000 ✕ (5.03/360) = 559 km&lt;/code&gt; and the North-South difference about &lt;code&gt;20 000 ✕ (0.87/180) = 97 km&lt;/code&gt;. Using those numbers within the Pythagoras theorem we end up with a distance of &lt;code&gt;√(559² + 97²) = 567 km&lt;/code&gt;. Although the calculation is correct, the answer is still wrong. The real distance is closer to 360 km.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;The spherical Earth model&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;If we look at the Earth in its original (mostly) spherical &lt;a href="http://derickrethans.nl#_footnote_0_1" class="footnote"&gt;1&lt;/a&gt; shape, then it's clear that 10° longitude (East/West) at 60°N is going to be less of a distance than 10°E/W at the equator. It's actually fairly easy to calculate how much 1° longitude is at 60°N by using &lt;code&gt;cos(60) * 1/360 * 6371 * 2π&lt;/code&gt; &lt;a href="http://derickrethans.nl#_footnote_0_2" class="footnote"&gt;2&lt;/a&gt;. Or in PHP:&lt;/p&gt;
      &lt;pre&gt;&lt;?php
$oneDeg =
        cos(deg2rad(60)) * // adjustment for latitude and radians/degrees
        1/360 * // 1 out of 360°
        6371 * 2 * M_PI, // circumference of the Earth at the equator
        "\n";
?&gt;

&lt;/pre&gt;
      &lt;p&gt;This returns &lt;code&gt;55.597 km per °&lt;/code&gt; for 60°N. The same distance in degrees on the Equator gives &lt;code&gt;6371 * 2 * M_PI / 360 = 111.195&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;The following diagram shows ones more that latitudinal degrees always correspond with the same distance in kilometer, whereas longitudinal degrees differ.&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/sphere2.png" alt="sphere2.png"/&gt;
      &lt;p&gt;In the diagram the line A is a line from 0°N, 90°W to 10°N, 90°W. It has the same length as line B, from 30°N, 90°W to 40°N, 90°W: a 36th of the circumference of the Earth through the poles. Line C, from 0°N, 30°W to 0°N, 20°W has the same length. Line D however, from 50°N, 30°W to 50°N, 20°W is shorter by a factor of &lt;code&gt;cos(50°) ≅ 0.64&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;If we look again at the distance between London and Amsterdam, ignore the differences in latitude and instead pick the average, we see:&lt;/p&gt;
      &lt;pre&gt;&lt;?php
$d = abs(-0.13 - 4.90);
// $d = 5.03 degrees

$e = 5.03/360 * cos(deg2rad(51.935)) * 6371 * 2 * M_PI;
$e = 5.03 * 0.617 * 6371 * 2 * M_PI;
// $e ≅ 345 km
?&gt;

&lt;/pre&gt;
      &lt;p&gt;Which is a bit shorter than the expected 360km, but that's because we conveniently forgot about the difference in latitude.&lt;/p&gt;
      &lt;p&gt;Sadly, we can't use Pythagoras's theorem to calculate the real distance with the latitude difference taken account as well. This is because the theorem is meant for &lt;a href="http://en.wikipedia.org/wiki/Euclidean_geometry"&gt;Euclidean&lt;/a&gt; geometry, and a sphere does not follow the rules of this geometry. Instead we need to use a formula that is called the &lt;a href="http://en.wikipedia.org/wiki/Great-circle_distance"&gt;great-circle distance&lt;/a&gt; formula. The main concept behind it is that a circle is drawn across the whole sphere that connects both the start (point P) as well as the end (point V). Then with that circle the distance can be calculated. The following diagram shows this:&lt;/p&gt;
      &lt;img src="http://derickrethans.nl/images/content/great-circle.jpg" alt="great-circle.jpg"/&gt;
      &lt;p&gt;I will spare you how the function is derived, but the distance calculation ends up as being:&lt;/p&gt;
      &lt;pre&gt;&lt;?php
function distance($latA, $lonA, $latB, $lonB)
{
        // convert from degrees to radians
        $latA = deg2rad($latA); $lonA = deg2rad($lonA);
        $latB = deg2rad($latB); $lonB = deg2rad($lonB);

        // calculate absolute difference for latitude and longitude
        $dLat = ($latA - $latB);
        $dLon = ($lonA - $lonB);

        // do trigonometry magic
        $d =
                sin($dLat/2) * sin($dLat/2) +
                cos($latA) * cos($latB) * sin($dLon/2) *sin($dLon/2);
        $d = 2 * asin(sqrt($d));
        return $d * 6371;
}
?&gt;

&lt;/pre&gt;
      &lt;p&gt;If we punch in our original numbers form London (51.50°N, 0.13°W) and Amsterdam (52.37°N, 4.90°E), we calculate the following:&lt;/p&gt;
      &lt;pre&gt;&lt;?php
$d = distance(51.50, -0.13, 52.37, 4.90);
echo $d, " km\n";
?&gt;

&lt;/pre&gt;
      &lt;p&gt;Which gives us the expected result of &lt;code&gt;358.07 km&lt;/code&gt;.&lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Conclusion&lt;/strong&gt;
      &lt;/p&gt;
      &lt;p&gt;I hope that the above clarified the difference between 2D spatial indexing with the &lt;em&gt;flat Earth model&lt;/em&gt; and spatial indexing of geo-located data (&lt;em&gt;the spherical Earth model&lt;/em&gt;). In future articles I will go into specific implementations of spatial indexing by traditional databases such as SQLite, MySQL and PostGreSQL; NoSQL databases such as MongoDB and CouchDB; and Solr.&lt;/p&gt;
      &lt;div class="flattr"&gt;
        &lt;a class="FlattrButton" rev="flattr;button:compact;" style="display: none" href="http://derickrethans.nl"/&gt;
        &lt;noscript&gt;
          &lt;a href="http://flattr.com/thing/429095/Derick-Rethans-website"&gt;
            &lt;img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this"/&gt;
          &lt;/a&gt;
        &lt;/noscript&gt;
      &lt;/div&gt;
      
      
    &lt;/div&gt;
    &lt;ul class="footnotes"&gt;
      &lt;li&gt;
        &lt;a name="_footnote_0_1"&gt;1&lt;/a&gt;
        &lt;p&gt;The Earth is not really a sphere, but an approximation of it. However, doing the same calculations for an ellipsoid can (as far as I know) only be done by approximation. The difference would hardly matter for finding the closest pub.&lt;/p&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;a name="_footnote_0_2"&gt;2&lt;/a&gt;
        &lt;p&gt;In this article, I've used an average radius of the Earth of 6371 km.&lt;/p&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;
</description>
      <guid>201103090717</guid>
      <pubDate>Wed, 09 Mar 2011 12:17:00 +0000</pubDate>
    </item>
  </channel>
</rss>
