Getting started with OS MasterMap Highways and pgRouting
OS MasterMap Highways network is a detailed and accurate navigable road network data set for Great Britian. It can be used in a variety of routing applications and these notes help get started with the data in the open source application pgRouting.
PgRouting extends the spatial capabilities of the PostGIS extension to the PostgreSQL database to provide routing functions. These notes are based on the work of Tim Manners and Ross MacDonald which has been shared on GitHub and provide an overview of how to create a network data set for use with pgRouting.
Software Requirements
PostgreSQL with the PostGIS and pgRouting extensions (pgRouting is packaged with PostGIS from version 2.1)
Data Requirements
OS MasterMap Highways Road and Asset Management Information (RAMI) – this gives the additional features required for routing although it is possible to build a basic network without these
Tables required:
Base data
RoadLink – network line geometry and attribution describing the link.
Routing information used to define where you can and cannot travel
Accessrestriction
Restrictionforvehicles
Turnrestriction
Depending on how the data has been translated from the GML this may be one table for each of these or separate tables for the different elements of each. The instructions work best if you have separated the network references into separate tables.
Before beginning you should also consider how much data you need, the bigger the area then the longer it will take to build and perform analysis.
Building the network
These notes are for guidance, table and field names should be checked to ensure they match.
The first statement generates a node lookup using the startNode and endNode references.
Where appropriate, it introduces additional nodes using the grade separation to ensure the real-world physical separations between RoadLinks are handled accordingly.
CREATETABLEschema.node_table ASSELECTrow_number() OVER (ORDER BY foo.p) AS id, foo.p AS nodeFROM (SELECT DISTINCTCONCAT(a.startnode, a.startgradeseparation) AS p FROM schema.roadlink aUNIONSELECT DISTINCTCONCAT(a.endnode, a.endgradeseparation) AS p FROM schema.roadlink a ) fooGROUP BY foo.p;--Create an index on node tableCREATEINDEXnode_table_node_idxON schema.node_table (node);
The second statement creates the directed graph which can be used for routing.
It utilises the node lookup to generate the required ‘source’ and ‘target’ integer fields; along with adding 'cost' and 'reverse_cost' fields for one ways (the wrong way has a very high cost).
It also uses the localid field in the data to create an id for each link, if you data does not contain this you can add it as serial.
CREATETABLEschema.edge_table ASSELECTrow_number() OVER (ORDER BY a.localid) AS id, a.roadname ASname, a.alternatename AS alt_name, a.roadclassificationnumber AS ref, a.routehierarchy, a.formofway, b.id AS source, c.id AStarget,CASEWHEN directionality ='in opposite direction'THEN a.length*1000000ELSE a.lengthENDAS cost,CASEWHEN directionality ='in direction'THEN a.length*1000000ELSE a.lengthENDAS reverse_cost, a.wkb_geometry AS the_geomFROM schema.roadlink AS aJOIN schema.node_table AS b ONCONCAT(a.startnode, a.startgradeseparation) = b.nodeJOIN schema.node_table AS c ONCONCAT(a.endnode, a.endgradeseparation) = c.node;--Create indexesCREATEUNIQUE INDEXedge_table_id_idxON schema.edge_table (id);CREATEINDEXedge_table_source_idxON schema.edge_table (source);CREATEINDEXedge_table_target_idxON schema.edge_table (target);--Geometry indexCREATEINDEXedge_table_geom_idxON schema.edge_tableUSING gist (the_geom);
Adding restrictions
The previous steps create a network which can be used for routing, however this does not take into account more complex turn features. The next step is to add these to your network.
Turn restrictions
No Turns
The Highways dataset provides tables of turn restrictions with a reference to the road links, the type of restriction and a turn sequence number. The turn restriction table is joined to the road link table and is used to generate the turn restrictions in pgRouting format.
First, we create a view of the links involved in the turn restriction. We select the FROM link with a sequence of 0 and the To link with a sequence number of 1 where the restriction type is NO TURN. The restrictions are represented in the GML as ordered pairs.
N.B. When translated a sequence attribute may be added with values of 0 and 1 to represent the order of the links in the restriction. However you may have translated your link references into an array containing a pair of values, the first should be accessed as 0 and the second 1.
Add the restriction type to the network reference table (this step is required if the network references are stored in a separate table to the turn restriction).
ALTERTABLE schema.turnrestriction_networkref ADD COLUMN restriction character(20); UPDATE schema.turnrestriction_networkref AS aSET restriction = b.restriction FROMschema.turnrestriction AS bWHERE a.toid = b.toid;CREATE OR REPLACEVIEWschema.view_hw_nt_links ASSELECTrow_number() OVER () AS gid, a.roadlink_toid AS from_link, c.localid AS from_fid, b.roadlink_toid AS to_link, d.localid AS to_fid, a.applicabledirection, a.sequence AS from_seq, b.sequence AS to_seq, a.restriction, c.centrelinegeometry –- this is the name of roadlink geometry columnFROM schema.turnrestriction_networkref aINNER JOIN schema.turnrestriction_networkref b ON a.toid = b.toid INNER JOIN schema.roadlink c ON c.toid = a.roadlink_toidINNER JOIN schema.roadlink d ON d.toid = b.roadlink_toidWHERE b.sequence =1AND a.sequence =0AND a.restriction ='No Turn';
Mandatory turns
Mandatory turns are modelled in the dataset as turns which are allowed, giving an entry link and an exit link which, together with the associated node that make up the mandatory turn. To create a restriction to enforce the mandatory turn you need to select all the other links in the turn and restrict access to them.
CREATE OR REPLACEVIEWschema.view_hw_mt_link1 ASSELECT a.roadlink_toid, b.localid, a.sequence, a.applicabledirection, (CASEWHEN a.applicabledirection ='inOppositeDirection'ANDsequence=0THEN b.startnode WHEN a.applicabledirection ='inDirection'ANDsequence=1THEN b.startnodeELSE b.endnode END) AS nodetoid, b.geom FROM schema.turnrestriction_networkref aINNER JOIN schema.roadlink b ON a.roadlink_toid = b.toidWHERE a.restriction ='Mandatory Turn'AND a.sequence =0;CREATE OR REPLACEVIEWschema.view_hw_mt_link2 ASSELECT a.roadlink_toid, b.localid, a.sequence, a.applicabledirection, (CASEWHEN a.applicabledirection ='inOppositeDirection'ANDsequence=0THEN b.startnode WHEN a.applicabledirection ='inDirection'ANDsequence=1THEN b.startnodeELSE b.endnode END) AS nodetoid, b.geom FROM schema.turnrestriction_networkref aINNER JOIN schema.highways b ON a.roadlink_toid = b.toidWHERE a.restriction ='Mandatory Turn'AND a.sequence =1;CREATE OR REPLACEVIEWschema.view_hw_mt_nt_links ASSELECTrow_number() OVER () AS gid, a.roadlink_toid AS from_toid, a.localid AS from_fid, b.toid AS to_toid, b.localid AS to_fidFROM schema.view_hw_mt_link1 aINNER JOIN schema.roadlink b ON (a.nodetoid = b.startnode OR a.nodetoid = b.endnode)AND a.roadlink_toid <> b.toidAND b.toid NOTIN (SELECT roadlink_toid FROM schema.view_hw_mt_link2);
No Entry
No entries represent links where access is forbidden and are held in the Access Restrictions table.
CREATE OR REPLACEVIEWschema.view_hw_ne_link ASSELECTrow_number() OVER () AS gid, a.restriction, a.trafficsign, b.applicabledirection, b.atposition, b.roadlink_toid, c.localid, (CASE WHEN b.applicabledirection = 'inOppositeDirection' THEN c.endnode ELSE c.startnode END) AS roadnode_toid, c.geom
FROM schema.accessrestriction aINNER JOIN schema.accessrestriction_networkref b ON a.toid = b.toidINNER JOIN schema.roadlink c ON c.toid = b.roadlink_toidWHERE a.trafficsign ='No Entry';CREATE OR REPLACEVIEWschema.view_hw_ne_other_links ASSELECTrow_number() OVER () AS gid, a.toid AS from_toid, a.localid AS from_fid, b.roadlink_toid AS to_toid, b.localid AS to_fidFROM schema.roadlink a INNER JOIN schema.view_hw_ne_link b ON (b.roadnode_toid = a.startnode OR b.roadnode_toid = a.endnode) AND a.toid <> b.roadlink_toid;
Build the restrictions table
This step takes the view previous created to build one table of all restrictions.
CREATETABLEschema.restrictions ( rid integerNOT NULL, to_cost double precision, teid integer, feid integer, via text )WITH ( OIDS=FALSE ); COMMENT ON TABLE schema.restrictions IS 'Highways Turn Restrictions';INSERT INTO schema.restrictions(rid,feid,teid)SELECT gid AS rid, from_fid AS feid, to_fid AS teid FROM schema.view_hw_nt_links vWHERE v.to_fid <>0AND v.to_fid NOTIN (SELECT DISTINCT t.teid FROM schema.restrictions t WHERE t.rid = v.gid);INSERT INTO schema.restrictions(rid,feid,teid)SELECT gid AS rid, from_fid AS feid, to_fid AS teid FROM schema.view_hw_mt_nt_links vWHERE v.to_fid <>0AND v.to_fid NOTIN (SELECT DISTINCT t.teid FROM schema.restrictions t WHERE t.rid = v.gid);INSERT INTO schema.restrictions(rid,feid,teid)SELECT gid AS rid, from_fid AS feid, to_fid AS teid FROM schema.view_hw_ne_other_links vWHERE v.to_fid <>0AND v.to_fid NOTIN (SELECT DISTINCT t.teid FROM schema.restrictions t WHERE t.rid = v.gid);
Example usage of pgRouting
A Driving Distance query finds everywhere along the network that can be reached within a fixed distance, in this example 1.5km, from a chosen starting point. The query uses the cost and reverse cost attribute to manage which networks elements are included in the query. In this example we have used the distance or time cost which were specified during edge table set up. The source node id is the point on the network from which the driving distance is calculated and can either be identified by querying the data in a GIS or in the database.
createtableschema.drive_1500m asSELECT*FROM pgr_drivingDistance('SELECT id, source, target, cost, reverse_cost FROM schema.edge_table',1111, --source node id1500, --max distance in metres true --directed is true);--Join to the edge table to create a list of all roads within the drive time. This can either be listed as a table or viewed in a GIS
createtableschema.drive_1500m_edges asselectid,name,alt_name,ref,routehierarchy,directionality,osid,source,target,a.cost,reverse_cost,the_geom,from schema.edge_table as ajoin schema.drive_1500m as b on a.id = b.edge ;
Create a Polygon Isochrone for your results
The results of this query are presented as a list of roads that are reachable in the time or distance specified. However you may want to display isochrones for your output and for use in service area analysis. This query takes the output and creates a polygon that represents its boundaries using a concave hull.
createtableschema.isochrone asselect St_ConcaveHUll(St_collect(the_geom), 0.5) as geomfrom schema.drive_1500m_edges;
The results can then be displayed graphically in a GIS.