Wednesday, 16 April 2014

Images to map overlays

I have been working on a project that needs maps to make sense of it, more of that in a later post. It is a history project for my village so I wanted to overlay maps from the 19th century and early 20th century with the modern map. The modern map is easy, I know a good contemporary map I can use. For the historical map layers I need maps laid out as tiles so I can use Leaflet to display them.

I was given a scanned map of the village dated 1824 and found another set of maps dated 1910. All of these are out of copyright, so I can comfortably use them. Scans of the 1910 maps and a lot of fiddling and joining gives me a .jpg file for the village. Now the two scans need aligning to be the same projection as the OSM map.

I chose to use Mapwarper to rectify the scans to match OSM. The process is straightforward. I uploaded the .jpg file and the site overlays it on the OSM map. You can add control points on the uploaded image and matching ones on the OSM map. The more control points you add, the better the final alignment. I used road junctions mostly as the control points, though the 1824, pre-enclosure map has far fewer roads and I had to make the most of what I could find. The image is then rectified and a GeoTIFF is available to download. A GeoTIFF is a bitmap with georeferencing information added. Once this has been downloaded it can be turned into tiles.

GDAL has a set of utilities to work with geo-data. One of these is gdal2tiles.py which is a python program to turn a GeoTIFF into a set of tiles. It creates TMS tiles, TMS stands for Tile Map Service which I think was intended to be a standard. The numbering of the Y-axis tiles is inverted compared to OSM tiles. It is easy to rename the tiles to match the OSM convention, but Leaflet (and OpenLayers) supports TMS and none-TMS layers and can use them interchangeably. 

Running gdal2tiles (e.g. gdal2tiles.py -z13-19 xxxx.tif tiledir) gives set of tiles from the GeoTIFF (xxxx.tif) for the zoom levels specified (13-19) and stores them in the directory specified (tiledir). These are now ready for use with leaflet.

I want to overlay the older maps on the modern map. All of these layers are opaque, so if the three layers are just stacked then only the last one will display as it will hide the other two. Leaflet lets you specify the opacity of a layer, so by altering that the details of each layer can be visible simultaneously.  These can then be used as the base to show an extra layer of detail, but more that another time.

I have created a simple page to show these layers. There are sliders to control the opacity. I spent a bit of time aligning the 1910 map and I'm fairly happy with the result. The 1824 map was a bit crude so I used fewer control points and the result is not as good. It is still interesting. I'm looking for any more maps of this era for my village.

Tuesday, 25 February 2014

Signed at last

I went out to buy some seeds for the allotment today. On the way home deliberately drove home down Hawthorn Avenue in Hull to see the point where Woodcock Street joins it.

I have written to Hull City Council traffic department a few times in the past about that junction. Woodcock Street runs from Hawthorn Avenue to St Georges Road. Hawthorn Avenue has a 30 mph and St Georges Road has a 20 mph limit. There was no speed limit sign at either end of Woodcock Street nor at any point along it, so from St Georges Road you would assume it to be a 20 mph road but from Hawthorn Avenue you would assume it to be a 30 mph road and if you drove onto St Georges Road you would also expect it to be 30 mph until you see a repeater for 20 mph.

Woodcock Street has been part of the substantial redevelopment in the Hawthorn Avenue area, much of which is still under way though Woodcock Street looks to be pretty complete. The council have put a cherry on it by erecting a 20 mph sign at the Hawthorn Avenue end to remove any doubt.

Well done Hull City Council, eventually.

Saturday, 1 February 2014

Visualising changes

When someone edits OSM their changes get rendered quickly so they can see their handiwork. That's really good feedback and is valuable in attracting new mappers. When it comes to checking what has changed in an area, just looking at the rendered map is rarely enough to spot any changes. Looking at changesets is the next option.

Changesets were introduced with the API version 0.6. They group together edits by a mapper made over a short period of time. They can be open for up to 24 hours, but changesets are normally closed within an hour of being created and often changesets are opened, edits uploaded to the API and the changeset closed within seconds. Changesets have a bounding box that covers the area the edits are made in. You can request a list of changesets from the API that cover a specific bounding box. Edits by a mapper are normally wrapped in a changeset that covers a small area, but some edits range across very large areas even across the whole world. These are known as big edits. These are usually some sort of mass-edit or bot edit. These will show up in a request for changesets even though no actual changes are made in the requested area.

Looking at changesets can be useful, but apart from trying to work out which of the so-called big edits to ignore, there's the bigger problem of knowing what has actually changed. If you see an added node or way that's easy, but the modified nodes or ways is a bit harder to understand and looking at some relations can be a long job to work out the changes. Has a node been moved? Has a way's nodes been moved? Has a way had nodes removed or added? Has a node or way had its tags changed? Is there some combination of all this?  Deleted nodes and ways are also a bit of a problem as they no longer render and seeing what has gone can be hard to visualise, especially as some people delete a way and add a new one to replace it, losing the way's history. What would be nice is to see a before and after view of a changeset.

I looked at the problem, initially for nodes and ways, and quickly realised that the way the API delivers information about ways makes things harder than you'd want. When you request the details of a way from the API it returns the way with attributes like ID, version, changeset etc, a list of tags and a list of nodes as node ids. This fine for the current state of the way. The nodes in the list are the most recent version of the node. You need the nodes to find the geometry of the way as longitude and latitude are only stored on nodes. As soon as you look at an older version of a way the list of nodes is suddenly not clear at all. Which version of each node does it refer to?

I think the timestamp on each API object can be used to work out which version of a node matches each version of a way. That assumes that I have, or download, all of the versions of every node in the area I'm interested in. Another idea is to store the data for the area I'm interested in from a snapshot, such as Geofabrik's, and apply every changeset for the area on from there. In that way I could store the geometry of the way with the way rather than in nodes, which sounds much more practical. I can then show the before and after view of every changeset, but only from the date of my snapshot and only if I apply every changeset. I'll think about this some more.

Monday, 9 December 2013

Storm surge

Last week the geography, planetary alignment and weather combined to cause misery to hundreds of families around Britain.

Tides around Britain naturally have a big range, the second highest tidal range in the world is in the Bristol Channel in Britain's south west. Tides vary depending on the alignment of the sun and moon, when the Sun and Moon are at the same side of the Earth (new moon) or opposite sides of the Earth (full moon) the tides are higher than when they are not in alignment. The highest tides are called spring tides, whatever time of year they occur.

The North Sea is roughly V-shaped, getting much narrower at the southern end. Last week a storm swept across the country, driving very strong winds from the north east down the North Sea coast forcing water in the sea towards the narrow part. The storm was, as usual, a deep low pressure system. With the low pressure over the sea, the water level will rise in a so-called storm surge.

The spring tide, the storm surge and the extra water blown down the North Sea created a lot of water pushing up against sea defences along the North Sea coast, particularly the southern end. In addition the waves thrown up by the stormy winds made topping the sea walls inevitable. Flooding followed.

Earlier this year I surveyed a new, small road close to the Humber Bridge, Wintersgill Place. Sadly the road was flooded. The houses now look finished, but there are three for-sale signs and one sold sign for the six houses. I wonder just how planning permission was granted for these houses when the area is clearly a flood risk. Now the small field next to these new houses is also set to be developed.

I tried to look at the local council's forward planning map to see if they agree that the area is a flood risk. The map was curiously off-line over the period of the storm and just after. Now it has lost the most detailed zoom level and the newly built houses are not on the map at all. I hope the planners have better information available, but since they have allowed houses to be built that have flooded before they were even sold, maybe they don't.

Wednesday, 27 November 2013

Using Leaflet with a database

The previous two posts created a map with markers. The marker information was stored in a fixed geojson file. For the few markers that don't change much this is fine, but it would be much more flexible if the markers were in a database. If there are a large number of markers, say thousands, browsers might slow down showing them, even though many might not actually be visible. One way to help with this is to work out which markers would be visible in the current view and only show them. To do this we need to use some features of Leaflet and introduce Ajax. We will also need to store the marker information in a database, write some code to extract it and format it into the geojson format that we know works so well.

Ajax is a means of exchanging data between the client browser and the server without forcing a page reload. I tend to use jQuery to simplify the process of using Ajax and jQuery ensures that the process works on a wide range of browsers. We will request some data from the server with Ajax which can return data in a json format, which works with geojson too.

In the examples so far the files from the server have been simple files, not needing scripting or a database. In my examples I'm using PHP for script and MySQL for the database as this is a very common combination available from many hosts. In the GitHub repository there is a SQL file, plaques.sql, you can use to create a table called plaques in a MySQL database and import the same data that we have seen already.

To extract the data from the database we'll use a PHP script. It needs to receive a request for the bounding box and it will extract that, format the geojson result and return it to the client. The client then can display the markers. If the user scrolls the map or changes the zoom then a new Ajax request will get the markers that are in the new view and display them. This isn't really needed for the seventy or so markers in this example but it is very useful for a large number of markers.

Let's start with the PHP script to extract the data:


// uncomment below to turn error reporting on
ini_set('display_errors', 1);
error_reporting(E_ALL);

/*
 * ajxplaque.php
 * returns plaque points as geojson
 */

// get the server credentials from a shared import file
$idb= $_SERVER['DOCUMENT_ROOT']."/include/db.php";
include $idb;

if (isset($_GET['bbox'])) {
    $bbox=$_GET['bbox'];
} else {
    // invalid request
    $ajxres=array();
    $ajxres['resp']=4;
    $ajxres['dberror']=0;
    $ajxres['msg']='missing bounding box';
    sendajax($ajxres);
}
// split the bbox into it's parts
list($left,$bottom,$right,$top)=explode(",",$bbox);

// open the database
try {
    $db = new PDO('mysql:host=localhost;dbname='.$dbname.';charset=utf8', $dbuser, $dbpass);
} catch(PDOException $e) {
    // send the PDOException message
    $ajxres=array();
    $ajxres['resp']=40;
    $ajxres['dberror']=$e->getCode();
    $ajxres['msg']=$e->getMessage();
    sendajax($ajxres);
}

//$stmt = $db->prepare("SELECT * FROM hbtarget WHERE lon>=:left AND lon<=:right AND lat>=:bottom AND lat<=:top ORDER BY targetind");
//$stmt->bindParam(':left', $left, PDO::PARAM_STR);
//$stmt->bindParam(':right', $right, PDO::PARAM_STR);
//$stmt->bindParam(':bottom', $bottom, PDO::PARAM_STR);
//$stmt->bindParam(':top', $top, PDO::PARAM_STR);
//$stmt->execute();


try {
    $sql="SELECT plaqueid,lat,lon,plaquedesc,colour,imageid FROM plaques WHERE lon>=:left AND lon<=:right AND lat>=:bottom AND lat<=:top";
    $stmt = $db->prepare($sql);
    $stmt->bindParam(':left', $left, PDO::PARAM_STR);
    $stmt->bindParam(':right', $right, PDO::PARAM_STR);
    $stmt->bindParam(':bottom', $bottom, PDO::PARAM_STR);
    $stmt->bindParam(':top', $top, PDO::PARAM_STR);
    $stmt->execute();
} catch(PDOException $e) {
    print "db error ".$e->getCode()." ".$e->getMessage();
}
   
$ajxres=array(); // place to store the geojson result
$features=array(); // array to build up the feature collection
$ajxres['type']='FeatureCollection';

// go through the list adding each one to the array to be returned   
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    $lat=$row['lat'];
    $lon=$row['lon'];
    $prop=array();
    $prop['plaqueid']=$row['plaqueid'];
    $prop['plaquedesc']=$row['plaquedesc'];
    $prop['colour']=$row['colour'];
    $prop['imageid']=$row['imageid'];
    $f=array();
    $geom=array();
    $coords=array();
   
    $geom['type']='Point';
    $coords[0]=floatval($lon);
    $coords[1]=floatval($lat);
   
    $geom['coordinates']=$coords;
    $f['type']='Feature';
    $f['geometry']=$geom;
    $f['properties']=$prop;

    $features[]=$f;
   
}
   
// add the features array to the end of the ajxres array
$ajxres['features']=$features;
// tidy up the DB
$db = null;
sendajax($ajxres); // no return from there

function sendajax($ajx) {
    // encode the ajx array as json and return it.
    $encoded = json_encode($ajx);
    exit($encoded);
}
?>



This is called ajxplaques.php in the folder ajax on the server, available in the GitHub repository.  The script needs a query string with bbox= in it. This defines the west,south,east and north longitude and latitude that bounds the current view of the map. It then queries the database for these items and returns the geojson of these limited markers. If the bounding box (BBOX) is big enough then all the markers will be returned and if the BBOX contains no markers then none are returned and that is fine too. I'm using MySQL and ignoring GIS functions as selecting points is quick and easy. If I was extracting polygons and using a powerful GIS database such as PostrgreSQL with the PostGIS extension then I would consider using a GIS function to find the polygons that intersect the BBOX.

To call the script from the JavaScript (example3.js) I use the ajax functions that are part of jQuery:

function askForPlaques() {
    var data='bbox=' + map.getBounds().toBBoxString();
    $.ajax({
        url: 'ajax/ajxplaques.php',
        dataType: 'json',
        data: data,
        success: showPlaques
    });
}


This creates the query string by using map.bounds() and formats into the format we need with toBBoxString(). The $.ajax() function uses the query string, requests json (of which geojson is just a special case) and will call the function showPlaques() when the data is returned.

function showPlaques(ajxresponse) {
    lyrPlq.clearLayers();
    lyrPlq.addData(ajxresponse);
}


The showPlaques() function is called when the data is returned from the script. The geojson data is in the ajxresponse. We delete all of the existing markers with clearLayers() and add the new data to the geojson layer. To trigger this process we need to call askForPlaques() every time the view of the map changes. We can ask the map object to trigger an event whenever this occurs. So after the map is displayed we add

map.on('moveend', whenMapMoves);

This calls the function whenMapMoves() when the event is triggered. That function simply calls  askForPlaques() to get the correct data for the view.

Two more things have changed. Firstly, when the geojson layer is created no data is added - it is called with null - so the plaques.js is not used at all. When the map is first displayed we need to call askForPlaques() once to get the first set of markers before the map is moved.

Now we have a much more dynamic map, using data from a database and potentially using a part of thousands of markers without overloading the browser.

Thursday, 21 November 2013

Using Leaflet part 2

In the last post I described how to create a map with markers on it. I'm going to build on this to make some improvements. Firstly, the default markers are useful but it would be good to have some alternatives. I copied the example1.htm, .js and .css files to example2.x and made some changes there. I created three images which are blue, green and white disks and saved them in the images folder. To use these images we need to create a Leaflet Icon for each marker type. The variables used to store the Icon objects are declared in the global area and then the Icon is created before anything else is created so it is available whenever we need it.

    blueicon=L.icon({
        iconUrl: 'images/blueplaque.png',
        iconSize:[24, 24], // size of the icon
        iconAnchor:[12, 23] // point of the icon which will correspond to marker's location
    });
    greenicon=L.icon({
        iconUrl: 'images/greenplaque.png',
        iconSize:[24, 24], // size of the icon
        iconAnchor:[12, 23] // point of the icon which will correspond to marker's location
    });
    whiteicon=L.icon({
        iconUrl: 'images/whiteplaque.png',
        iconSize: [24, 24], // size of the icon
        iconAnchor:[12, 23] // point of the icon which will correspond to marker's location
    });


The icons use the images from the images folder. The icon anchor is important to make a marker work well especially when zooming. If your marker seems to slide around as you zoom in or out you probably don't have the anchor set correctly. It defaults to [0,0] which is top left and rarely what you want.

In the last example we used events to add popups to each markers, here we want to substitute the markers, so we need to change the behaviour, but not much. When we create the geojson layer we need to remove the onEachFeature and replace it with pointToLayer:

  lyrPlq = L.geoJson(plaques, {
        pointToLayer: setIcon
        }
    );


The pointToLayer event allows you to provide the marker you want to display for each feature. The function you define, setIcon in this case, gets passed the feature and the position that the marker need to be as a leaflet LngLat object. In the function you create a marker or some other Leaflet object and return that. Leaflet adds what you return to the layer.

function setIcon(feature,ll) {
    var plq;
    if (feature.properties.colour=='green') {
        plq=L.marker(ll, {icon: greenicon});
    }
    else if (feature.properties.colour=='white') {
        plq=L.marker(ll, {icon: whiteicon});
    }
    else {
        plq=L.marker(ll, {icon: blueicon});
    }
    plq.bindPopup(feature.properties.plaquedesc);
    return p;
}


In this case we use one of the properties, colour, to choose the marker to display. We now need to add the popup to our newly created marker and return the marker for Leaflet to use.

You can see an example here. The source code is on GitHub.

More examples to come ...

Using Leaflet v0.7

The Leaflet JavaScript library has changed the way OpenStreetMap is being used, making it easy to use and offering all kinds of additional features and functions as plug-ins. I blogged about Leaflet soon after it was first released and that post has been read by a lot of people and has generated more comments than any other. Leaflet version 0.7 has just been released and when I was asked about using it I realised that my original post was badly out of date. I decided to use some local data to describe using Leaflet, including some plug-ins. I decided to use jQuery for a few features. It is widely used and is cross-platform, just like Leaflet. The jQuery files are in a folder called jquery, and the Leaflet files are all in a folder called leaflet.

All the example files are in a GitHub repository: http://github.com/chillly/plaques

Leaflet displays a slippy map in an HTML div. It uses JavaScript to control the way the map behaves. The style, as you would expect, is controlled by CSS. Our first example displays a base map with an overlay of markers on it to show where blue plaques are around the UK city of Hull.  Take a look here. The HTML is really straightforward, take a look in the GitHub repository above.

In the head section there is a style sheet for leaflet (leaflet.css) and a script (leaflet.js). These are used in every example. I have also included leaflet-hash.js which is an example of a Leaflet plug-in. I like to store CSS and JavaScript in separate files, not in the HTML file, so I have also included example1.css and example1.js. The JavaScript names plaques.js holds the locations of the plaques to display, formatted as a geojson file. The CSS simply makes the div, with the id “mapdiv”, fill the page. The real work is in the javascript:


/* 
* global variables 
*/ 
var map; // global map object 
var lyrOsm; // the Mapnik base layer of the map 
var lyrPlq; // the geoJson layer to display plaques with 

// when the whole document has loaded call the init function 
$(document).ready(init);

function init() { 
  // map stuff 
  // base layer 
  var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
  var osmAttrib='Map data © OpenStreetMap contributors';
  lyrOsm = new L.TileLayer(osmUrl, { minZoom: 9, maxZoom: 19, attribution: osmAttrib });

  // a geojson 
  layer lyrPlq = L.geoJson(plaques,{ 
    onEachFeature: makePopup
    } 
  ); 

  // set the starting location for the centre of the map 
  var start = new L.LatLng(53.7610,-0.3529); 
  
  // create the map 
  map = new L.Map('mapdiv', { // use the div called mapdiv 
    center: start, // centre the map as above 
    zoom: 12, // start up zoom level 
    layers: [lyrOsm,lyrPlq] // layers to add
   }); 

  // create a layer control 
  // add the base layers 
  var baseLayers = { "OpenStreetMap": lyrOsm }; 

  // add the overlays 
  var overlays = { "Plaques": lyrPlq }; 

  // add the layers to a layer control 
  L.control.layers(baseLayers, overlays).addTo(map); 

  // create the hash url on the browser address line 
  var hash = new L.Hash(map);


function makePopup(feature, layer) { 
  // create a popup for each point 
  if (feature.properties && feature.properties.plaquedesc) {
    layer.bindPopup(feature.properties.plaquedesc);
  } 
}

The file starts with global variables. The map variable is the core of Leaflet, any name would do, but it is called map by convention. The map has two layers, one to display the base map and one to show the markers. These are defined by two variables lyrOsm and lyrPlq.

The first use for jQuery is:

    $(document).ready(init);

This means that when the document is completely loaded and ready call the function init. Doing this is very useful as on a slow link, such as some mobile connections. It makes sure that all of the elements of the page are available before trying to use them.

The init function needs to create the layers, create the map itself and add the layers to the map. We need two different layers one is the base layer which is a set of square tiles  that Leaflet requests from the provider and arranges in the right place. We need to tell the layer where to get the tiles from, in this case the main OSM tile server.

    var osmUrl='http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; 
We also need to add attribution – all OSM-based maps need attribution as part of the licence to use the data.

    var osmAttrib='Map data © OpenStreetMap contributors';

The tile provider may also require or request attribution. In one of the later examples will will use a different tile provider with a different attribution. We can now make a layer:

lyrOsm = new L.TileLayer(osmUrl, {
    minZoom: 9,
    maxZoom: 19,
    attribution: osmAttrib
});


This creates the layer of tiles, from the tile provider, which will zoom out only to zoom level 9 and in to level 19 and add the attribution to the bottom right of the map, by default.

We also need a layer to display the plaque markers. In the HTML a file called plaques.js was loaded. That contains a geojson format file which we will use in a geojson layer. Geojson is a very useful format that I use frequently and is supported well by Leaflet. Geojson allows a variety of objects to be passed to Leaflet for displaying, including points, lines, multiple lines, polygons and multipolygons and these can be mixed together as needed. With a simple set of points the loaded file can be simply used as a layer. The file plaques.js creates a variable called plaques that can be used directly:

    lyrPlq = L.geoJson(plaques, {
        onEachFeature: makePopup
        }
    );


The onEachFeature is an example of responding to an event. In this case as each feature of the geojson file is added the function makePopup is called. This allows us to use one of the properties in each feature in the geojson file to be used to make a popup if the marker is clicked.

We now create a LatLng object to use to centre the map and then the map object is created:

map = new L.Map('mapdiv', {        // use the div called mapdiv
    center: start,                   // centre the map as above
    zoom: 12,            // start up zoom level
    layers: [lyrOsm,lyrPlq]        // layers to add
});


This creates the map, centres it, zooms to level 12 and adds the two layers we created above.

That would be enough to create a slippy map, but I added a couple of extra features which are often useful. The first is a layers control which allows the layers on the map to be selected and hidden. There are two types of layer a base layer and an overlay and we have one of each. The two layers are created, with names that will appear in the layer control, and the control is then created with the layers added and then added to the map.

The last feature is a leaflet plug-in. I added the leaflet-hash.js file to the leaflet folder and loaded it in the HTML. The hash plug-in changes the URL displayed in the browser address line as the map is scrolled and zoomed so the address can always be used as a bookmark. It replaces the permalink used on earlier versions. A simple line adds the plug-in to your map.

Following posts will show how to change the icons that appear, customise the popup, use database data to display the markers, deal with a large number of markers including clustering them and how to respond to click or tap in other ways than just displaying a popup.