<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Mixedbredie's Ghosts]]></title><description><![CDATA[ventures into spatial]]></description><link>https://ghost.mixedbredie.net/</link><image><url>https://ghost.mixedbredie.net/favicon.png</url><title>Mixedbredie&apos;s Ghosts</title><link>https://ghost.mixedbredie.net/</link></image><generator>Ghost 4.48</generator><lastBuildDate>Thu, 09 Apr 2026 20:07:38 GMT</lastBuildDate><atom:link href="https://ghost.mixedbredie.net/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Merging DEM rasters]]></title><description><![CDATA[Using batch files and GDAL to merge rasters for performance.]]></description><link>https://ghost.mixedbredie.net/merging-os-rasters/</link><guid isPermaLink="false">6166f91546cfc09e4de63f59</guid><category><![CDATA[DEM]]></category><category><![CDATA[raster]]></category><category><![CDATA[GDAL]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Mon, 17 Sep 2018 14:56:27 GMT</pubDate><media:content url="https://ghost.mixedbredie.net/content/images/2018/09/sample5mdtm.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://ghost.mixedbredie.net/content/images/2018/09/sample5mdtm.jpg" alt="Merging DEM rasters"><p>I recently got a dataset made up of 2364 images files representing 1km x 1km tiles of 5m resolution <a href="https://en.wikipedia.org/wiki/Digital_elevation_model">Digital Terrain Model</a> (DTM). &#xA0;There are also another 2364 files of 1km x 1km 2m resolution Digital Surface Model (DSM). &#xA0;I want to serve these with <a href="http://geoserver.org">Geoserver </a>which prefers fewer, larger files over more smaller files.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ghost.mixedbredie.net/content/images/2018/09/tiles1km.JPG" class="kg-image" alt="Merging DEM rasters" loading="lazy"><figcaption>lots and lots of little files</figcaption></figure><p><a href="https://www.gdal.org/index.html">GDAL</a> is my tool of choice for batch processing images and in this case I want to use <strong><a href="https://www.gdal.org/gdalwarp.html">gdalwarp</a> </strong>or <strong><a href="https://www.gdal.org/gdal_merge.html">gdal_merge</a> </strong>to create my larger tiles. &#xA0;The files are named logically based on the National Grid - NO4321.tif - and I can use this to create 10km x 10km tiles from the 1km x 1km tiles. &#xA0;If I take the first (4) and third (2) numbers after the grid square reference (NO) I get the 10km tile reference - NO42.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ghost.mixedbredie.net/content/images/2018/09/tiles1km2.JPG" class="kg-image" alt="Merging DEM rasters" loading="lazy"><figcaption>10km x 10km tile made up from 1km x 1km tiles</figcaption></figure><p>So, how do I automate this process to create my tiles?</p><p>First up I need some text files, one for each 10km tile, with the list of image tiles making up the larger tile. &#xA0;The following batch command has a nested loop that iterates through two sets of numbers (0 to 9) and substitutes those in the DIR command to find all image filenames that match the string (the ? represents a single character in the string):</p><blockquote>FOR /L %%G IN (0,1,9) DO ( &#xA0;FOR /L %%H IN (0,1,9) DO (DIR /b /s NO_tiflzw\NO%%G?%%H?.tif &gt; NO%%G%%H.txt &#xA0;))</blockquote><p>I end up with a directory of text files like NO00.txt, NO01.txt, NO02.txt, etc. &#xA0;Each text file has a list of the 1km tiles that make up the 10km tile.</p><p><strong><a href="https://www.gdal.org/gdal_merge.html">gdal_merge</a></strong> and <strong>gdalwarp </strong>can take a text file list as an input so now we can loop through the list of text files and generate a 10km tile for each one:</p><blockquote>FOR /F %%I IN (*.txt) DO gdal_merge -o NO_tifmerged%~nI.tif -of &#xA0; GTiff -co TFW=YES -co TILED=YES -co BLOCKXSIZE=512 -co BLOCKYSIZE=512 -co COMPRESS=LZW -ot Float32 -v --optfile %%I</blockquote><p>This creates an internally tiled, LZW compressed, floating point GeoTIFF with accompanying world file. &#xA0;If you want more control over the output raster try <strong>gdalwarp </strong>instead:</p><blockquote>FOR /F %%I IN (*.txt) DO gdalwarp -s_srs EPSG:27700 -t_srs EPSG:27700 -ot Float32 -r average --config GDAL_CACHEMAX 500 -wm 500 -of GTiff -co TFW=YES -co TILED=YES -co BLOCKXSIZE=512 -co BLOCKYSIZE=512 -co COMPRESS=LZW --optfile %%I NO_tifwarped%~nI.tif</blockquote><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://ghost.mixedbredie.net/content/images/2018/09/tiles10km2.JPG" class="kg-image" alt="Merging DEM rasters" loading="lazy"><figcaption>single, merged 10km tile`</figcaption></figure><p>After this I can generate some stats for each image:</p><blockquote>FOR /F %%G IN (&apos;dir /b *.tif&apos;) DO gdalinfo %%G -stats</blockquote><p>And some overviews with <strong>gdaladdo </strong>for performance:</p><blockquote>FOR /F %%H IN (&apos;dir /b *.tif&apos;) DO gdaladdo -r average %%H 2 4 8 16</blockquote><p>*Remember, if you run this from the command line replace the &quot;%%&quot; with &quot;%&quot;</p><p>Now I have 35 tiles to serve instead of 2364 which should make Geoserver faster.</p><p>If you&apos;re running on Linux here are some simple Bash shell scripts which do the same as the Command shell loops above:</p><blockquote>for i in {0..9}<br> &#xA0;do<br> &#xA0; &#xA0;for j in {0..9}<br> &#xA0; &#xA0;do<br> &#xA0; &#xA0; &#xA0;ls -Q NO/NO$i?$j?.asc &gt; NO$i$j.txt<br> &#xA0; &#xA0;done<br> &#xA0;done<br>exit 0</blockquote><blockquote>for f in <em>.txt;</em><br> &#xA0;<em>do</em><br> &#xA0; &#xA0;<em>gdalwarp -s_srs EPSG:27700 -t_srs EPSG:27700 -ot Float32 -r average </em><br><em>--config GDAL_CACHEMAX 500 -wm 500 -of GTiff -co TFW=YES -co TILED=YES </em><br><em>-co BLOCKXSIZE=512 -co BLOCKYSIZE=512 -co COMPRESS=LZW </em><br><em>--optfile $f NO_10k/&quot;${f%.</em>}&quot;.tif<br> &#xA0;done<br>exit 0</blockquote>]]></content:encoded></item><item><title><![CDATA[Voronoi isochrones]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>How can we visualise our driving distance dataset another way?  Can we create something like this which is using <a href="https://en.wikipedia.org/wiki/Voronoi_diagram">Voronoi</a> polygons:</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="fi" dir="ltr">Palvealueanalyysi avoimella l&#xE4;hdekoodia <a href="https://twitter.com/hashtag/H%C3%A4meenlinna?src=hash">#H&#xE4;meenlinna</a> <a href="https://twitter.com/hashtag/PostGIS?src=hash">#PostGIS</a> <a href="https://twitter.com/hashtag/pgRouting?src=hash">#pgRouting</a> <a href="https://twitter.com/hashtag/sote?src=hash">#sote</a> <a href="https://twitter.com/hashtag/OSGeofi?src=hash">#OSGeofi</a> <a href="https://t.co/ssyCD0vOxN">pic.twitter.com/ssyCD0vOxN</a></p>&#x2014; Pekka Sarkola (@posiki) <a href="https://twitter.com/posiki/status/866925559892324352">May 23, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Well, yes we can.  And PostGIS</p>]]></description><link>https://ghost.mixedbredie.net/voronoi-isochrones/</link><guid isPermaLink="false">6166f91546cfc09e4de63f58</guid><category><![CDATA[pgrouting]]></category><category><![CDATA[qgis]]></category><category><![CDATA[voronoi]]></category><category><![CDATA[catchment areas]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Fri, 23 Jun 2017 12:40:21 GMT</pubDate><media:content url="https://ghost.mixedbredie.net/content/images/2017/06/voronoi.JPG" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.mixedbredie.net/content/images/2017/06/voronoi.JPG" alt="Voronoi isochrones"><p>How can we visualise our driving distance dataset another way?  Can we create something like this which is using <a href="https://en.wikipedia.org/wiki/Voronoi_diagram">Voronoi</a> polygons:</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="fi" dir="ltr">Palvealueanalyysi avoimella l&#xE4;hdekoodia <a href="https://twitter.com/hashtag/H%C3%A4meenlinna?src=hash">#H&#xE4;meenlinna</a> <a href="https://twitter.com/hashtag/PostGIS?src=hash">#PostGIS</a> <a href="https://twitter.com/hashtag/pgRouting?src=hash">#pgRouting</a> <a href="https://twitter.com/hashtag/sote?src=hash">#sote</a> <a href="https://twitter.com/hashtag/OSGeofi?src=hash">#OSGeofi</a> <a href="https://t.co/ssyCD0vOxN">pic.twitter.com/ssyCD0vOxN</a></p>&#x2014; Pekka Sarkola (@posiki) <a href="https://twitter.com/posiki/status/866925559892324352">May 23, 2017</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Well, yes we can.  And PostGIS gives us the tools to put this together very quickly.  First, create a view of the distinct nodes in your output table created by my batch function in a <a href="https://ghost.mixedbredie.net/batch-driving-distance-a-pgrouting-function/">previous post</a>.</p>
<pre><code>CREATE OR REPLACE VIEW vw_sdd_nodes AS
  SELECT DISTINCT ON (node) *
  FROM school_driving_distance
  ORDER BY node, agg_cost ASC;
</code></pre>
<p>(from this <a href="https://gis.stackexchange.com/questions/244283/combining-several-catchment-areas">StackExchange post</a>).</p>
<p>Then using a new function in PostGIS 2.3.x, ST_VoronoiPolygons, create a voronoi polygon layer based on the road nodes in the view above.  See this <a href="https://gis.stackexchange.com/questions/240224/how-to-use-st-voronoipolygons-in-postgis">StackExchange post</a> for more info.</p>
<pre><code>CREATE OR REPLACE VIEW vw_sdd_voronoi AS
  WITH voronoi AS
  (SELECT geometry FROM vw_sdd_nodes)
SELECT
 (ST_Dump(ST_SetSRID(ST_CollectionExtract(ST_VoronoiPolygons(ST_Collect(geometry)),3),27700))).geom
AS the_geom
FROM voronoi;
</code></pre>
<p>Then, link the nodes back to the voronoi polygons so that each polygon gets a node ID and a cost value in a new table.  This takes some time - in my case I had 24,000 nodes intersecting with 24,000 polygons in about 3 minutes.</p>
<pre><code>CREATE TABLE edn_sdd_voronoi AS
  SELECT row_number() OVER () AS gid, n.node, n.agg_cost, v.the_geom
  FROM vw_school_driving_distance_nodes n, vw_sdd_voronoi v
  WHERE ST_Intersects(n.geometry, v.the_geom);

CREATE INDEX edn_sdd_voronoi_geometry_ixs
  ON corporate.edn_sdd_voronoi
  USING gist(the_geom);
</code></pre>
<p>I also created a set of Voronoi polygons using the QGIS &gt; Geometry Tools &gt; Voronoi Polygons and that took several hours to create a new shapefile layer.  Using PostGIS you can be done in less than 5 minutes depending on how many points you have.  As always this could probably be done more efficiently but 4 hours to 5 minutes is good enough for me.</p>
<p>Style the data in QGIS using a graduated renderer and the aggregated cost column and you get something like this.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2017/06/voronoi2.JPG" alt="Voronoi isochrones" loading="lazy"></p>
<p>If you have any suggestions for improvements, get <a href="https://ghost.mixedbredie.net/about-and-contact/">in touch</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Processing thousands of journeys: a pgRouting function]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I have a dataset of pupils and the schools they attend.  For each pupil I have a source XY coordinate and a target XY coordinate.  I have a table of schools where the school name matches the name in the pupil table.  I also have a full featured road network</p>]]></description><link>https://ghost.mixedbredie.net/processing-thousands-of-journeys-a-pgrouting-function/</link><guid isPermaLink="false">6166f91546cfc09e4de63f54</guid><category><![CDATA[pgrouting]]></category><category><![CDATA[qgis]]></category><category><![CDATA[time manager]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Thu, 22 Jun 2017 11:00:21 GMT</pubDate><media:content url="https://ghost.mixedbredie.net/content/images/2017/06/pgrouting_function.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.mixedbredie.net/content/images/2017/06/pgrouting_function.jpg" alt="Processing thousands of journeys: a pgRouting function"><p>I have a dataset of pupils and the schools they attend.  For each pupil I have a source XY coordinate and a target XY coordinate.  I have a table of schools where the school name matches the name in the pupil table.  I also have a full featured road network complete with road routing information and turn restrictions.  All this sits in a <a href="postgresql.org">PostgreSQL</a> database with <a href="postgis.net">PostGIS</a> and <a href="pgrouting.org">pgRouting</a> installed.  I need to use all this to calculate the shortest and quickest routes to school for each pupil.  The results will then be used to inform some decision-making process about school transport and school location.</p>
<p>Two different solutions will be applied - routing from nearest node to nearest node and routing from nearest edge position to nearest edge position.</p>
<p>First, the pupil location needs to be associated with the nearest node on the road network.</p>
<pre><code>-- Find the nearest network node for each pupil
CREATE TEMP TABLE temp_pupil_nn AS 
SELECT DISTINCT ON (a.pupilid) a.pupilid, b.id AS nearest_node
FROM edn_pupilschoolroute a
  LEFT JOIN hw_roadlink_vertices_pgr b ON ST_DWithin(a.geometry, b.the_geom, 1200)
ORDER BY a.pupilid, ST_Distance(a.geometry, b.the_geom);
</code></pre>
<p>This query creates a temporary table with a row for each pupil and the nearest node ID within a specified distance.  You can find all the pupils that don&apos;t have a node within the specified distance.  Adjust the distance in the ST_DWithin function until every pupil has a node assigned.</p>
<pre><code>-- Find pupils without a node
SELECT a.pupilid
FROM edn_pupilschoolroute a
  LEFT JOIN hw_roadlink_vertices_pgr b ON ST_DWithin(a.geometry, b.the_geom, 1200)
WHERE b.id IS NULL;
</code></pre>
<p>Once the temporary table is populated update the pupil table with the node id.  You could probably do it in a wunner but I like separate steps.</p>
<pre><code>-- Update pupil data table with NN information
UPDATE edn_pupilschoolroute
  SET pupil_nn = 
  (SELECT nearest_node 
  FROM temp_pupil_nn b
  WHERE b.pupilid = edn_pupilschoolroute.pupilid);
</code></pre>
<p>Repeat the steps above for each school.</p>
<p>Using this approach works well where there is a dense road network and the road node is a suitable proxy for pupil location but most of my network is in rural areas where nodes are fewer and further apart.  This can lead to differences between the actual and modelled distances.  To get around this we can route from the point on the nearest edge closest to the pupil to the point on the nearest edge closest to the school.</p>
<p>So, find the closest edge and position of the point on the edge for each pupil by creating a view.</p>
<pre><code>-- Find the nearest network edge to each pupil
CREATE OR REPLACE VIEW view_source_edges AS
SELECT DISTINCT ON (a.pupilid) a.pupilid, b.ogc_fid AS nearest_edge, ST_Distance(a.geometry, b.centrelinegeometry) AS distance
FROM edn_pupilschoolroute a, hw_roadlink b
  WHERE ST_DWithin(a.geometry, b.centrelinegeometry, 1200)
ORDER BY a.pupilid, ST_Distance(a.geometry, b.centrelinegeometry);
</code></pre>
<p>Do the same for schools with a view of the target edges.</p>
<pre><code>-- Find the nearest network edge to each school
CREATE OR REPLACE VIEW view_target_edges AS
SELECT DISTINCT ON (a.schoolname) a.schoolname, b.ogc_fid AS nearest_edge, ST_Distance(a.geometry, b.centrelinegeometry) AS distance
FROM view_edn_dataschool a, hw_roadlink b
  WHERE ST_DWithin(a.geometry, b.centrelinegeometry, 1200)
ORDER BY a.schoolname, ST_Distance(a.geometry, b.centrelinegeometry);
</code></pre>
<p>Make sure the pupil table has the required fields for the edge calculations</p>
<pre><code>ALTER TABLE edn_pupilschoolroute
  ADD COLUMN source_eid integer,  --source edge id
  ADD COLUMN source_pos double precision,  --source edge position
  ADD COLUMN target_eid integer,  --target edge id
  ADD COLUMN target_pos double precision,  --target edge position
  ADD COLUMN p_ndist double precision,  --node to node distance
  ADD COLUMN p_ntime double precision,  --node to node time
  ADD COLUMN p_edist double precision,  --edge to edge distance
  ADD COLUMN p_etime double precision;  --edge to edge time
</code></pre>
<p>The last four columns added are those that are populated by the function.  Update the first four columns with the edge ids and positions.</p>
<pre><code>UPDATE edn_pupilschoolroute a
  SET source_eid = b.source_eid,
      source_pos = b.source_pos,
      target_eid = c.target_eid,
      target_pos = c.target_pos
  FROM view_source_edges b, view_target_edges c
  WHERE a.pupilid = b.pupilid
  AND a.schoolname = c.schoolname;
</code></pre>
<p>The function to process all this information into routes goes like this:</p>
<pre><code>	-- Function: corporate.pupil_journeys_routes()

	-- DROP FUNCTION corporate.pupil_journeys_routes();

	CREATE OR REPLACE FUNCTION corporate.pupil_journeys_routes()
	  RETURNS character varying AS
	$BODY$
	DECLARE

	  cur_pupil refcursor;
	  v_pid integer;
	  v_seid integer;
	  v_spos float8;
	  v_teid integer;
	  v_tpos float8;
	  v_geom geometry;
	  v_sql varchar(1000);

	BEGIN
	  RAISE NOTICE &apos;Processing pupil journeys...&apos;;
	  OPEN cur_pupil FOR EXECUTE format(&apos;SELECT pupilid, source_eid, source_pos, target_eid, target_pos FROM edn_pupilschoolroute WHERE pupil_nn IS NOT NULL&apos;);
	  LOOP
	  FETCH cur_pupil INTO v_pid, v_seid, v_spos, v_teid, v_tpos;
	  EXIT WHEN NOT FOUND;
	  SELECT ST_Collect(r.centrelinegeometry) AS geometry FROM pgr_trsp(&apos;
		SELECT ogc_fid AS id,
		  source::integer,
		  target::integer,
		  cost_len::double precision AS cost,
		  rcost_len::double precision AS reverse_cost
		FROM hw_roadlink&apos;::text,
		v_seid, --pupil source edge id
		v_spos, --source edge position
			v_teid, --school target edge id
			v_tpos, --target edge position
		true,
		true,
		&apos;select to_cost, teid as target_id, feid||coalesce(&apos;&apos;,&apos;&apos;||via,&apos;&apos;&apos;&apos;) as via_path from hw_nt_restrictions&apos;::text) AS d
	  INNER JOIN hw_roadlink r ON d.id2 = r.ogc_fid INTO v_geom;
	  
	  -- insert route cost into pupil data table
	  EXECUTE format(&apos;INSERT INTO %s(pupilid,geometry) VALUES ($1,$2)&apos;,&apos;corporate.edn_pupilroutes&apos;)
		USING v_pid, v_geom;
		
	  END LOOP;
	  RETURN &apos;Oooh, get in!&apos;;
	  CLOSE cur_pupil;
	END;
	$BODY$
	  LANGUAGE plpgsql VOLATILE
	  COST 100;
</code></pre>
<p>For 16,000 pupils and 57 schools this took about 2 hours to return a result.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Batch driving distance - a pgRouting function]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>A function to batch process driving distance results for a set of input points, in this case, schools.</p>
<p>The function creates a table to hold the results (dropping it if it exists) and then selects each school from the <strong>your_schools</strong> table and calculates the driving distance numbers.  For each</p>]]></description><link>https://ghost.mixedbredie.net/batch-driving-distance-a-pgrouting-function/</link><guid isPermaLink="false">6166f91546cfc09e4de63f57</guid><category><![CDATA[pgrouting]]></category><category><![CDATA[postgis]]></category><category><![CDATA[postgresql]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Mon, 19 Jun 2017 15:42:30 GMT</pubDate><media:content url="https://ghost.mixedbredie.net/content/images/2017/06/frame049.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.mixedbredie.net/content/images/2017/06/frame049.jpg" alt="Batch driving distance - a pgRouting function"><p>A function to batch process driving distance results for a set of input points, in this case, schools.</p>
<p>The function creates a table to hold the results (dropping it if it exists) and then selects each school from the <strong>your_schools</strong> table and calculates the driving distance numbers.  For each school it writes the results to a temp table (<strong>temp_dd</strong>, dropping it first if it exists) and writes those results to the output table (<strong>school_driving_distance</strong>).  In the end you have a table with a lot of rows (more or less depending on your cost) with which you can do neat things with QGIS Time Manager.</p>
<p>The schools data needs to be pre-processed to give each school an id field, school_nn, which contains the node id of the nearest network node.</p>
<p>Note: You&apos;ll need to <strong>VACUUM ANALYZE</strong> your output table to update the statistics.</p>
<pre><code>-- Function: public.school_driving_distance()
-- DROP FUNCTION public.school_driving_distance();

CREATE OR REPLACE FUNCTION public.school_driving_distance()
  RETURNS character varying AS
$BODY$
DECLARE

  cur_school refcursor;
  v_snn integer;  --variable for the school nearest node
  v_node integer;
  v_seq integer;  --variable for the sequence id
  v_cost double precision;  --variable for the cost results
  v_geom geometry;  --variable for the output geometry
  v_sql varchar(1000);  --variable to hold some SQL
  
BEGIN
-- Drop the results table first
	RAISE NOTICE &apos;Dropping table...&apos;;
	v_sql:=&apos;DROP TABLE IF EXISTS public.school_driving_distance;&apos;;
	EXECUTE v_sql;
	
-- Create a new results table next
	RAISE NOTICE &apos;Creating table...&apos;;
	v_sql:=&apos;CREATE TABLE IF NOT EXISTS public.school_driving_distance 
	(
	  gid serial NOT NULL,
	  seq integer,
      node integer,
	  school_nn integer,
	  agg_cost double precision, 
	  geometry geometry(Point,27700), 
	  CONSTRAINT school_dd_ipk PRIMARY KEY (gid)
	);

    CREATE INDEX school_driving_distance_geometry_ixs
    ON public.school_driving_distance
    USING gist(geometry);&apos;;

	EXECUTE v_sql;
	
-- Process the schools    
	RAISE NOTICE &apos;Processing school...&apos;;
	OPEN cur_school FOR EXECUTE format(&apos;SELECT school_nn FROM your_schools WHERE school_nn IS NOT NULL&apos;);
	LOOP

-- Drop the temp table
	v_sql:=&apos;DROP TABLE IF EXISTS temp_dd;&apos;;
	EXECUTE v_sql;

-- Create a temp table to hold results for each school
	v_sql:=&apos;CREATE TEMPORARY TABLE temp_dd(
	  seq integer,
      node integer,
	  school_nn integer,
	  agg_cost double precision, 
	  geometry geometry(Point,27700)
	  );&apos;;
	EXECUTE v_sql;
	
	FETCH cur_school INTO v_snn;
	EXIT WHEN NOT FOUND;
	
-- Do 30 minute driving distance calculation for each school
	INSERT INTO temp_dd(seq, node, school_nn, agg_cost, geometry)
	SELECT foo.seq AS v_seq, foo.node AS v_node, v_snn AS school_nn, foo.agg_cost AS v_cost, n.the_geom AS v_geom FROM pgr_drivingDistance(
	  &apos;SELECT ogc_fid AS id, source, target, cost, reverse_cost 
	  FROM your_network&apos;,
	  v_snn,          -- school name
	  1800) AS foo    -- change this cost to change the output
	  LEFT JOIN your_network_vertices_pgr n ON n.id = foo.node;
	  
-- Insert results into driving distance table
	EXECUTE format(&apos;INSERT INTO public.school_driving_distance(seq,node,school_nn,agg_cost,geometry) SELECT * FROM temp_dd;&apos;);
	END LOOP;
	RETURN &apos;Completed the driving distance calculations&apos;;
	CLOSE cur_school;
	
END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
COMMENT ON FUNCTION public.school_driving_distance() IS &apos;30 minute driving distance function for each school&apos;;
</code></pre>
<p>Once all the results are written to the output table you can then query the table to get the overall minimum distance to each school:</p>
<pre><code>SELECT DISTINCT ON (node) *
FROM school_driving_distance
ORDER BY node, agg_cost ASC;
</code></pre>
<p>Here&apos;s a result from the function above processed with QGIS Time Manager and stitched together with Avidemux and VLC.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/9kaLDBrfCy4" frameborder="0" allowfullscreen></iframe>
<p>And another one.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Y8LZCkLU7IQ" frameborder="0" allowfullscreen></iframe>
<p>See this <a href="https://gis.stackexchange.com/questions/244283/combining-several-catchment-areas">stackexchange post</a> for another way of doing it.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Driving Distance Animations with pgRouting - a how to]]></title><description><![CDATA[<!--kg-card-begin: markdown--><h4 id="checklist">Checklist</h4>
<ul>
<li>QGIS with the Time Manager plugin installed.</li>
<li>a PostgreSQL database with PostGIS and pgRouting extensions installed.</li>
<li>access to said database through psql or pgAdmin3/4</li>
</ul>
<p>A quick and dirty workflow to create some animations using a road network and some driving distance calculations.  I won&apos;t go through</p>]]></description><link>https://ghost.mixedbredie.net/driving-distance-animations-with-pgrouting-a-how-to/</link><guid isPermaLink="false">6166f91546cfc09e4de63f56</guid><category><![CDATA[pgrouting]]></category><category><![CDATA[qgis]]></category><category><![CDATA[time manager]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Wed, 14 Jun 2017 15:46:34 GMT</pubDate><media:content url="https://ghost.mixedbredie.net/content/images/2017/06/frame019.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h4 id="checklist">Checklist</h4>
<ul>
<li>QGIS with the Time Manager plugin installed.</li>
<li>a PostgreSQL database with PostGIS and pgRouting extensions installed.</li>
<li>access to said database through psql or pgAdmin3/4</li>
</ul>
<img src="https://ghost.mixedbredie.net/content/images/2017/06/frame019.jpg" alt="Driving Distance Animations with pgRouting - a how to"><p>A quick and dirty workflow to create some animations using a road network and some driving distance calculations.  I won&apos;t go through the process of buiding your routing topology - there are examples <a href="https://ghost.mixedbredie.net/pgrouting-and-os-open-roads/">here</a> and <a href="https://ghost.mixedbredie.net/using-pgrouting-with-os-meridian2/">here</a> to get you up and running with open data.  You should have two tables in the database - <code>your_network</code> and <code>your_network_vertices_pgr</code> - once you&apos;ve completed the load and build process.</p>
<p>pgRouting comes with a number of built in routing functions one of which is the <a href="http://docs.pgrouting.org/latest/en/pgr_drivingDistance.html#pgr-drivingdistance">Driving Distance function</a>.  This function extracts all the nodes (and edges) from the network that are within a specified distance of a specified start point.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2017/06/frame010.jpg" alt="Driving Distance Animations with pgRouting - a how to" loading="lazy"></p>
<p>For the source features you can either select a node on the network that is closest to your area of interest and use the node id in your query or you can use an existing dataset and assign each feature a node id from the nearest node on the network.  The query below creates a view of the point data in the <code>school</code> table (adjust as required) with the nearest network node id.  You can then just pick the school and the node id you want to use.</p>
<pre><code>CREATE OR REPLACE VIEW view_school_nodes AS
SELECT DISTINCT ON (a.schoolname) a.schoolname, b.id AS node, ST_Distance(a.geometry, b.the_geom) AS distance
FROM schools a, your_network_vertices_pgr b
  WHERE ST_DWithin(a.geometry, b.the_geom, 120)
ORDER BY a.schoolname, ST_Distance(a.geometry, b.the_geom);
</code></pre>
<p>The results of the nearest node query:</p>
<pre><code>schoolname ; node ; distance
Aberlemno Primary School ; 58810 ; 27.8567765543682
Airlie Primary School ; 60834 ; 80.8949936646267
Andover Primary School ; 49398 ; 45.9162068337352
Arbroath Academy ; 55679 ; 42.0475920832573
Arbroath High School ; 56431 ; 26.1166270792929
</code></pre>
<p>My network is set up with cost fields - length and time - and I can use these to model journeys across the network.  In the example below I am using time (in seconds) to show me how much of the network I can cover in 450 seconds (7 minutes 30 seconds).  This number can be set to what ever you like.</p>
<pre><code>SELECT seq, node, edge, cost, agg_cost FROM pgr_drivingDistance(
&apos;SELECT ogc_fid AS id, source, target, cost, reverse_cost 
  FROM your_network&apos;,
  58810,  -- this is the start node
  450     -- this is the maximum cost (seconds in this case)
  ); 
</code></pre>
<p>The results are presented below.</p>
<pre><code>seq  ;  node  ;  edge  ;  cost  ;  agg_cost
1 ; 58874 ; 67982 ; 4.68003566289286 ; 46.337640876984
2 ; 58727 ; 67931 ; 7.45750698693065 ; 7.45750698693065
3 ; 58728 ; 67932 ; 1.86503491582722 ; 9.32254190275786
4 ; 58793 ; 67946 ; 4.79924189436584 ; 23.1936531505147
5 ; 58794 ; 67939 ; 8.04220277283646 ; 18.3944112561489
6 ; 58808 ; 67928 ; 5.65869396139883 ; 14.9812358641567
7 ; 58809 ; 67930 ; 3.89677598786481 ; 13.2193178906227
8 ; 58810 ; -1 ; 0 ; 0
9 ; 58811 ; 67936 ; 0.800410785992549 ; 10.3522084833124
10 ; 58812 ; 67945 ; 2.09429071038919 ; 9.55179769731984
11 ; 58821 ; 67947 ; 24.9065391490445 ; 43.3009504051934
12 ; 58864 ; 67985 ; 28.4382873234685 ; 41.6576052140912
</code></pre>
<p>The field we are interested in is the <code>agg_cost</code> which can be sorted smallest to largest and we&apos;ll use this in QGIS to render them in order.  But before we get there we need some geometry to work with.  We need to join the results of the driving distance calculation to the network geometry.  Then we&apos;ll create a view to load the data into QGIS.	These views can be created using <strong>pgAdmin</strong> or through the <strong>DB Manager</strong> in QGIS.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2017/06/db_sql_views.JPG" alt="Driving Distance Animations with pgRouting - a how to" loading="lazy"></p>
<p>To get the edge or line geometry do this:</p>
<pre><code>SELECT seq, node, edge, cost, agg_cost, l.geometry FROM pgr_drivingDistance(
&apos;SELECT ogc_fid AS id, source, target, cost, reverse_cost 
  FROM your_network&apos;,
  58810,  -- this is the start node
  450     -- this is the maximum cost (seconds in this case)
  ) AS foo
  LEFT JOIN your_network l ON l.ogc_fid = foo.edge; 
</code></pre>
<p>To get the node geometry do this:</p>
<pre><code>SELECT seq, node, edge, cost, agg_cost, l.the_geom FROM pgr_drivingDistance(
&apos;SELECT ogc_fid AS id, source, target, cost, reverse_cost 
  FROM your_network&apos;,
  58810,  -- this is the start node
  450     -- this is the maximum cost (seconds in this case)
  ) AS foo
  LEFT JOIN your_network_vertices_pgr l ON l.id = foo.node; 
</code></pre>
<p>Turn these results into a view by adding the create view statement (make up your own name):</p>
<pre><code>CREATE OR REPLACE VIEW view_dd_lines_58810_450 AS
SELECT seq, node, edge, cost, agg_cost, l.geometry FROM pgr_drivingDistance(
&apos;SELECT ogc_fid AS id, source, target, cost, reverse_cost 
  FROM your_network&apos;,
  58810,  -- this is the start node
  450     -- this is the maximum cost (seconds in this case)
  ) AS foo
  LEFT JOIN your_network l ON l.ogc_fid = foo.edge; 
</code></pre>
<p>To get the node geometry do this:</p>
<pre><code>CREATE OR REPLACE VIEW view_dd_nodes_58810_450 AS
SELECT seq, node, edge, cost, agg_cost, l.the_geom FROM pgr_drivingDistance(
&apos;SELECT ogc_fid AS id, source, target, cost, reverse_cost 
  FROM your_network&apos;,
  58810,  -- this is the start node
  450     -- this is the maximum cost (seconds in this case)
  ) AS foo
  LEFT JOIN your_network_vertices_pgr l ON l.id = foo.node; 
</code></pre>
<p>OK, we&apos;ve got some data with aggregated costs and geometry.  Add the views to QGIS and let&apos;s style it up.  We can style the points and lines using a colour ramp and the aggregated cost field so that colours change with distance.  We&apos;ll use data-defined colour expressions for this.  Open the layer properties and switch to the Style tab.  Select the simple line or point symbol and click the data-defined override button to edit.</p>
<p>Copy this expression in:</p>
<pre><code>ramp_color( &apos;PiYG&apos;,  scale_linear( &quot;agg_cost&quot;, 0, 450, 0, 1))
</code></pre>
<p>This uses the <code>PiGn</code> colour ramp and scales the colour in a linear way based on the values in our data (min: 0, max: 450) to values between 0 and 1.  So as costs increase colours will change from purple through white to green.  Make sure both the point and line layers are styled like this.</p>
<p><em><strong>Note</strong>: you can use any colour ramp name and even a custom one you designed yourself. Check the Style Manager under Settings to get the list of available colour ramps.</em></p>
<p>I set the project background colour to <strong>black</strong> for that classy touch.  You can also set the feature blend in the point layer to <strong>multiply</strong>. Or not - see how you&apos;re feeling.</p>
<p>Right, fire up QGIS <a href="https://plugins.qgis.org/plugins/timemanager/">Time Manager</a> plugin.  Hear that? The sound of a quality QGIS plugin loading... Turn the plugin on using the on/off button and click <strong>Settings</strong>.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2017/06/tm_settings.jpg" alt="Driving Distance Animations with pgRouting - a how to" loading="lazy"></p>
<p><img src="https://ghost.mixedbredie.net/content/images/2017/06/tm_add_layer.jpg" alt="Driving Distance Animations with pgRouting - a how to" loading="lazy"></p>
<p><img src="https://ghost.mixedbredie.net/content/images/2017/06/tm_layer_settings.jpg" alt="Driving Distance Animations with pgRouting - a how to" loading="lazy"></p>
<p>Add the point layer.  Choose it from the drop down. Choose the cost field as your start time. Set the end time to <code>No end time - accumulate features</code>. Click OK. Repeat for the line layer.  In the animation options I set the Show Frame For option to 40 ms for a frame rate of 25.  Click OK.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2017/06/tm_time_display.jpg" alt="Driving Distance Animations with pgRouting - a how to" loading="lazy"></p>
<p>Back in the Time Manager window adjust the time frame size to the value of [max cost / 25]. This will ensure your animation plays out in a second at 25fps.  Again adjust as required.  Click the play button and see how the animation runs.</p>
<p>Before we export the video, adjust the canvas size to the size of the output video.  You can go big by hiding all the panels or smaller by expanding the panels to get the desired dimensions.  The images that are exported are the same size as the canvas.  Zoom to the extent of you layer to fill the screen. You can rotate the canvas too.</p>
<p>To export the animation click the <strong>Export Video</strong> button.  On Windows the animation is exported as a PNG image sequence.  On Mac OSX and Linux you can export directly to video.  Choose an output directory and click OK.  The Time Manager starts exporting your animation one frame at a time.  You can watch the files appearing in the directory or go and get a biscuit.  When you get back you will have a directory full of PNG and PNGW (world) files.  Delete the PNGW files.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2017/06/tm_export_video.jpg" alt="Driving Distance Animations with pgRouting - a how to" loading="lazy"></p>
<p>I use <a href="http://fixounet.free.fr/avidemux/">Avidemux</a> to create my video output (Windows Movie Maker works well too) and it needs JPG files.  Convert the PNG files to JPG.  I use the batch converter in <a href="http://www.irfanview.com/">Irfanview</a> for that.  Once you have some JPG files fire up Avidemux and open the first image in the sequence.</p>
<p><em><strong>Important</strong>: make sure you don&apos;t change the file name otherwise it work work. It needs the Name### format.</em></p>
<p>Avidemux automatically recognises the image sequence and creates the video. Save it to AVI format.  This makes for a large file as it is basically all the JPG images bundled into a container and then told to display in order.  If you want to upload your animations to the Twitter then you need to convert to MP4.  I convert the AVI to OGG or MP4 with <a href="http://www.videolan.org/index.en-GB.html">VLC</a>.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2017/06/frame025.jpg" alt="Driving Distance Animations with pgRouting - a how to" loading="lazy"></p>
<p>And a couple of Blue Peter outputs...</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Mzq4qIPDQWw" frameborder="0" allowfullscreen></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/AV_KJiwPU00" frameborder="0" allowfullscreen></iframe><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[TauDEM Toolbox in QGIS]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>You might want to use the <a href="http://hydrology.usu.edu/taudem/taudem5/index.html">TauDEM Toolbox</a> in <a href="http://qgis.org/">QGIS</a>. The <a href="http://docs.qgis.org/testing/en/docs/user_manual/processing_algs/taudem/index.html">QGIS docs</a> give you all the detail on the available algorithms but you need to install and configure the toolbox yourself.  You might get error messages about <code>mpiexec</code> not being being found on your system.</p>
<p>QGIS installs most of</p>]]></description><link>https://ghost.mixedbredie.net/taudem-toolbox-in-qgis/</link><guid isPermaLink="false">6166f91546cfc09e4de63f55</guid><category><![CDATA[qgis]]></category><category><![CDATA[Processing]]></category><category><![CDATA[TauDEM]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Tue, 23 May 2017 09:35:49 GMT</pubDate><media:content url="https://ghost.mixedbredie.net/content/images/2017/05/taudem.JPG" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.mixedbredie.net/content/images/2017/05/taudem.JPG" alt="TauDEM Toolbox in QGIS"><p>You might want to use the <a href="http://hydrology.usu.edu/taudem/taudem5/index.html">TauDEM Toolbox</a> in <a href="http://qgis.org/">QGIS</a>. The <a href="http://docs.qgis.org/testing/en/docs/user_manual/processing_algs/taudem/index.html">QGIS docs</a> give you all the detail on the available algorithms but you need to install and configure the toolbox yourself.  You might get error messages about <code>mpiexec</code> not being being found on your system.</p>
<p>QGIS installs most of the required TauDEM dependencies and so you only need to install the Microsoft MPI executable (on Windows) or Open MPI (on Linux / Mac OSX).  Get the latest MPI installer (v8 at time of writing) <a href="https://www.microsoft.com/en-us/download/details.aspx?id=54607">here</a>.  Install in the default location.</p>
<p>Register and download the TauDEM archive from <a href="http://hydrology.usu.edu/taudem/taudem5/downloads.html">here</a> and then extract to some location - e.g. C:\Program Files\TauDEM537exeWin64</p>
<p>Switch to QGIS and open the Processing toolbox option in QGIS (Ctrl + Alt + C)</p>
<ul>
<li>Expand the Providers section</li>
<li>Expand the TauDEM (hydrologic analysis) section</li>
<li>Check the Activate box - <strong>x</strong></li>
<li>Add the MPICH2/OpenMPI bin directory - <strong>C:\Program Files\Microsoft MPI\Bin</strong></li>
<li>Add the TauDEM command line tool folder - <strong>C:\Program Files\TauDEM537exeWin64</strong></li>
<li>Add the TauDEM multifile command line tool folder - <strong>C:\Program Files\TauDEM537exeWin64</strong></li>
<li>Click OK</li>
</ul>
<p>Run a TauDEM algorithm.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Blender Landscapes: Skye]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p><img src="https://ghost.mixedbredie.net/content/images/2016/12/skye_one.jpg" alt loading="lazy"></p>
<p>Another attempt at Blender landscapes using the Ordnance Survey Terrain50 DTM and Meridian2 datasets.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/12/skye_two.jpg" alt loading="lazy"></p>
<p>The Meridian2 features are draped over the DTM which is vertically exaggerated by 150%.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/12/skye_three.jpg" alt loading="lazy"></p>
<p>Water features and a sea layer are used to create an image texture. This is used in a UVmap to create the</p>]]></description><link>https://ghost.mixedbredie.net/blender-landscapes-skye/</link><guid isPermaLink="false">6166f91546cfc09e4de63f53</guid><category><![CDATA[blender]]></category><category><![CDATA[ordnance survey]]></category><category><![CDATA[opendata]]></category><category><![CDATA[FME]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Wed, 14 Dec 2016 15:47:33 GMT</pubDate><media:content url="https://ghost.mixedbredie.net/content/images/2016/12/skye_four-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.mixedbredie.net/content/images/2016/12/skye_four-1.jpg" alt="Blender Landscapes: Skye"><p><img src="https://ghost.mixedbredie.net/content/images/2016/12/skye_one.jpg" alt="Blender Landscapes: Skye" loading="lazy"></p>
<p>Another attempt at Blender landscapes using the Ordnance Survey Terrain50 DTM and Meridian2 datasets.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/12/skye_two.jpg" alt="Blender Landscapes: Skye" loading="lazy"></p>
<p>The Meridian2 features are draped over the DTM which is vertically exaggerated by 150%.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/12/skye_three.jpg" alt="Blender Landscapes: Skye" loading="lazy"></p>
<p>Water features and a sea layer are used to create an image texture. This is used in a UVmap to create the glossy looking sea and rivers in the final render.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/12/skye_four.jpg" alt="Blender Landscapes: Skye" loading="lazy"></p>
<p>Some work needs to be done on the image texture resolution and some smoothing of the DTM needs to happen to get rid of the contouring effect. But I still like them.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/12/skye_five.jpg" alt="Blender Landscapes: Skye" loading="lazy"></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Blender Landscapes]]></title><description><![CDATA[Using Blender with LiDAR data to create details landscapes with lighting, materials and UV mapping. Inspired by Owen Powell.]]></description><link>https://ghost.mixedbredie.net/blender-landscapes/</link><guid isPermaLink="false">6166f91546cfc09e4de63f52</guid><category><![CDATA[blender]]></category><category><![CDATA[3d]]></category><category><![CDATA[lidar]]></category><category><![CDATA[landscapes]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Fri, 25 Nov 2016 10:47:27 GMT</pubDate><media:content url="https://ghost.mixedbredie.net/content/images/2016/11/no7053dsm_rendered.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.mixedbredie.net/content/images/2016/11/no7053dsm_rendered.jpg" alt="Blender Landscapes"><p>Inspired by <a href="http://www.blendernation.com/2016/09/03/owen-powell-maps-terrain-models/">an image</a> created by <a href="https://owenpowell.wordpress.com/">Owen Powell</a> I thought I would have a look at <a href="http://blender.org/">Blender</a> again and see what I could get it to do with elevation and other mapping datasets.</p>
<p>Here are some first attempts getting to grips with lighting and UV maps and materials.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/11/auchmithie.jpg" alt="Blender Landscapes" loading="lazy"><br>
Auchmithie sunrise (above)</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/11/reekielinn_rendered1.jpg" alt="Blender Landscapes" loading="lazy"><br>
Reekie Linn - largest fall (24m) in the east of Scotland (above)</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/11/newtyle_paths.jpg" alt="Blender Landscapes" loading="lazy"><br>
Newtyle path network (above)</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/11/ferry_view4.jpg" alt="Blender Landscapes" loading="lazy"><br>
Line-of-sight study (above)</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Texture shading your hillshade]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>These guys do amazing things creating maps - <a href="https://adventuresinmapping.wordpress.com">John M. Nelson</a>, <a href="http://www.mapsmith.net/">Stephen &quot;Mapsmith&quot;</a>, <a href="http://mapzilla-art.co.uk/">Craig Taylor</a>, <a href="https://somethingaboutmaps.wordpress.com/2016/10/03/terrain-in-photoshop/">Daniel Huffman</a> - and their maps, visualisations and images are inspiring.  A tweet by <a href="https://twitter.com/tpstigers/status/783740795128078336">Terry Stigers</a> pointed me at the work of <a href="http://textureshading.com/Home.html">Leland Brown</a> and his texture shading algorithms.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/10/03_texture_shading.png" alt="Texture Shading" loading="lazy"></p>
<p>I do like a</p>]]></description><link>https://ghost.mixedbredie.net/texture-shading-your-hillshade/</link><guid isPermaLink="false">6166f91546cfc09e4de63f50</guid><category><![CDATA[DEM]]></category><category><![CDATA[shaded relief]]></category><category><![CDATA[texture shading]]></category><category><![CDATA[hillshade]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Sun, 09 Oct 2016 21:21:17 GMT</pubDate><media:content url="https://ghost.mixedbredie.net/content/images/2016/10/01_hillshade-1.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.mixedbredie.net/content/images/2016/10/01_hillshade-1.png" alt="Texture shading your hillshade"><p>These guys do amazing things creating maps - <a href="https://adventuresinmapping.wordpress.com">John M. Nelson</a>, <a href="http://www.mapsmith.net/">Stephen &quot;Mapsmith&quot;</a>, <a href="http://mapzilla-art.co.uk/">Craig Taylor</a>, <a href="https://somethingaboutmaps.wordpress.com/2016/10/03/terrain-in-photoshop/">Daniel Huffman</a> - and their maps, visualisations and images are inspiring.  A tweet by <a href="https://twitter.com/tpstigers/status/783740795128078336">Terry Stigers</a> pointed me at the work of <a href="http://textureshading.com/Home.html">Leland Brown</a> and his texture shading algorithms.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/10/03_texture_shading.png" alt="Texture shading your hillshade" loading="lazy"></p>
<p>I do like a nice shaded relief map. Wouldn&apos;t it be nice to be able to use those in your desktop GIS of choice and make your hill-shaded backdrops even nicer?</p>
<p>Well, you can.  The source code and some pre-built binaries for Windows and Mac OS X are available. As I don&apos;t have a Windows or Mac OS X machine and use Linux (Mint) I need the source code.  Head over to the <a href="https://app.box.com/v/textureshading">texture shading repository</a> on box.com and look in the Software folder.  Download the Source_Code folder in its entirety and save the zip file somewhere.  Extract it.  Read the User Guide.</p>
<p>The user guide gives clear instructions on how to compile the code into something usable. To compile the source code into the <code>texture</code> and <code>texture_image</code> executables you need to have a GCC compiler installed on your Linux box.  Install it if you don&apos;t:</p>
<pre><code>sudo aptitude install build-essential
</code></pre>
<p>This will install the necessary bits - <code>gcc</code>, <code>g++</code> and <code>make</code>.</p>
<p>I found the compiler commands in the User Guide failed with &quot;undefined references to sin/cos/pow/sqrt&quot; and other errors.</p>
<p>Try the commands in the User Guide - they may work for you.  I had to append a <code>-lm</code> to each command. This worked on my Linux Mint. Run the following commands in the directory where you extracted the source code</p>
<pre><code>gcc -O2 -funroll-loops -DNOMAIN -c *.c -lm
gcc &#x2013;O2 &#x2013;funroll-loops *.o texture.c -o texture -lm
gcc &#x2013;O2 &#x2013;funroll-loops *.o texture_image.c -o texture_image -lm
</code></pre>
<p>The first command creates a bunch of files with .o at the end. The second and third commands use these new files to build the <code>texture</code> and <code>texture_image</code> files that we&apos;ll use to generate our textured shading.  Once the commands have completed successfully you&apos;ll have two new files: <code>texture</code> and <code>texture_image</code>. Run each command without any arguments to print out the basic help information for each one.</p>
<pre><code>$ ./texture
Terrain texture shading program - version 1.3.1, built Oct  6 2016

USAGE:    texture detail elev_file texture_file [-options ...]
Examples: texture 0.5 rainier_elev.flt rainier_tex.flt
      texture 1/2 rainier_elev rainier_tex
      texture 2/3 rainier_elev rainier_tex -mercator -32.5 45

Normal range for detail is 0.0 to 2.0.
Typical values are 1/2 and 2/3.
(Either decimal or fraction is accepted.)

Requires both .flt and .hdr files as input  (e.g., rainier_elev.flt and rainier_elev.hdr).
Writes   both .flt and .hdr files as output (e.g., rainier_tex.flt  and rainier_tex.hdr).
Also reads &amp; writes optional .prj file if present (e.g., elev.prj to tex.prj).
Input and output filenames must not be the same.
NOTE: Output files will be overwritten if they already exist.

Available option:
-mercator lat1 lat2    input is in normal Mercator projection (not UTM)
Values lat1 and lat2 must be in decimal degrees.
</code></pre>
<p>Right, you&apos;ve got some working commands now.  Give it a go. Here are my steps in quickfire mode: I downloaded some Terrain 50, a 50m DEM available as open data from Ordnance Survey. I picked the NN tile as it has Ben Nevis and some other fantastic mountains and used QGIS to built a VRT from all the ASCII grid files*.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/10/00_dem.png" alt="Texture shading your hillshade" loading="lazy"></p>
<p>I created a hill-shaded image in QGIS using the NN tile.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/10/01_hillshade.png" alt="Texture shading your hillshade" loading="lazy"></p>
<p>From QGIS I exported this as both a RAW and a RENDERED GeoTiff (EPSG:27700). I then used gdal_translate to convert to GridFloat format:</p>
<pre><code>gdal_translate -of EHdr -ot Float32 nn_t50.tif nn_t50.flt
</code></pre>
<p>I found the texture commands didn&apos;t like the files created from the QGIS rendered GeoTiff as they were multi-band so use the RAW GeoTiff.</p>
<p>Using the <code>./texture</code> command I created the first texture shaded image as a .flt file.  Then using the <code>./texture_image</code> command I exported the results as a texture shaded GeoTiff.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/10/03_texture_shading-1.png" alt="Texture shading your hillshade" loading="lazy"></p>
<p>Load the texture shade into QGIS, add your hill-shade and coloured DEM. Play around with blend modes and transparency until you get something that works for you.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/10/04_tex_hs.png" alt="Texture shading your hillshade" loading="lazy"><br>
<em>Hill shade and texture shaded multiply blended</em><br>
<img src="https://ghost.mixedbredie.net/content/images/2016/10/05_dem_tex_hs.png" alt="Texture shading your hillshade" loading="lazy"><br>
<em>Add some tinting by blending in the 50% transparent DEM</em><br>
<img src="https://ghost.mixedbredie.net/content/images/2016/10/06_dem_tex_hs_mult.png" alt="Texture shading your hillshade" loading="lazy"><br>
<em>No transparency just multiply blended</em></p>
<p>Did I mention reading the User Guide?  It has a heap of tips on how to get the best out of your DEM.  I created a matrix of detail parameters and contrast levels to see what effect they had. Another post maybe.</p>
<pre><code>texture detail | texture contrast
0.25 | -2 -1 0 1 2 3 4 5
0.33 | -2 -1 0 1 2 3 4 5
0.50 | -2 -1 0 1 2 3 4 5
0.66 | -2 -1 0 1 2 3 4 5
0.75 | -2 -1 0 1 2 3 4 5
</code></pre>
<p>* Creating a VRT from the ASCII grid files and converting to GeoTiff introduces grid artifacts that can be seen in some of my images. See <a href="http://gis.stackexchange.com/questions/198997/hillshades-created-from-qgis-virtual-raster-tables-sometimes-have-unwanted-artif?rq=1">here</a> and <a href="http://gis.stackexchange.com/questions/140026/getting-strange-grid-artifacts-when-creating-hillshades-using-gdaldem?rq=1">here</a> for ways around it. I found that converting the ASCII grid files to GeoTiff with gdal_translate and then warping them into a single mosaic with gdalwarp (set &quot;-r average&quot; instead of default &quot;near&quot; and &quot;--config GDAL_CACHEMAX 7000&quot; to speed things up) worked. Hillshades created with SAGA, GRASS and QGIS had none of the stepping/terracing artifacts present. You might also get different results trying the default Horn and alternative ZevernbergenThorne algorithms.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[QGIS Map Tools at BCS SOC]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>I recently presented at the BCS SOC annual conference in Cheltenham on the cartographic map tools available in QGIS Essen 2.14 and N&#xF8;debo 2.16.</p>
<iframe src="//www.slideshare.net/slideshow/embed_code/key/fG5QhbOyPAZxef" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/RossMcDonald1/bcssoc2016rossmcdonaldqgismaptools" title="BCS_SOC_2016_rossmcdonald_qgis_maptools" target="_blank">BCS\_SOC\_2016\_rossmcdonald\_qgis_maptools</a> </strong> </div>
<p>Props to <a href="http://nyalldawson.net">Nyall Dawson</a> and <a href="http://nathanw.net">Nathan Woodrow</a> for the fantastic new features they&apos;ve added</p>]]></description><link>https://ghost.mixedbredie.net/qgis-map-tools-at-bcs-soc/</link><guid isPermaLink="false">6166f91546cfc09e4de63f4f</guid><category><![CDATA[qgis]]></category><category><![CDATA[bcs]]></category><category><![CDATA[soc]]></category><category><![CDATA[cartography]]></category><category><![CDATA[map]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Fri, 23 Sep 2016 10:00:52 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>I recently presented at the BCS SOC annual conference in Cheltenham on the cartographic map tools available in QGIS Essen 2.14 and N&#xF8;debo 2.16.</p>
<iframe src="//www.slideshare.net/slideshow/embed_code/key/fG5QhbOyPAZxef" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/RossMcDonald1/bcssoc2016rossmcdonaldqgismaptools" title="BCS_SOC_2016_rossmcdonald_qgis_maptools" target="_blank">BCS\_SOC\_2016\_rossmcdonald\_qgis_maptools</a> </strong> </div>
<p>Props to <a href="http://nyalldawson.net">Nyall Dawson</a> and <a href="http://nathanw.net">Nathan Woodrow</a> for the fantastic new features they&apos;ve added and the rest of the <a href="http://qgis.org">QGIS</a> development team for the amazing effort producing QGIS.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Hexperiments in QGIS]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>After seeing the <a href="https://adventuresinmapping.wordpress.com/2016/09/21/hexperiment/">post by John Nelson</a> on hex binning using ArcGIS I thought I better try it in QGIS as the effect is pretty neat.</p>
<p>Open the Processing toolbox and find the <strong>QGIS geoalgorithm &gt; Vector Creation Tools &gt; Create Grid</strong></p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/09/01_create_grid.jpg" alt="QGIS Create Grid tool" loading="lazy"></p>
<p>Select your grid type: <strong>hexagon</strong></p>
<p>I created grid</p>]]></description><link>https://ghost.mixedbredie.net/hexperiments-in-qgis/</link><guid isPermaLink="false">6166f91546cfc09e4de63f4e</guid><category><![CDATA[qgis]]></category><category><![CDATA[hex bins]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Thu, 22 Sep 2016 16:15:34 GMT</pubDate><media:content url="https://ghost.mixedbredie.net/content/images/2016/09/07_pointy_up-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.mixedbredie.net/content/images/2016/09/07_pointy_up-1.jpg" alt="Hexperiments in QGIS"><p>After seeing the <a href="https://adventuresinmapping.wordpress.com/2016/09/21/hexperiment/">post by John Nelson</a> on hex binning using ArcGIS I thought I better try it in QGIS as the effect is pretty neat.</p>
<p>Open the Processing toolbox and find the <strong>QGIS geoalgorithm &gt; Vector Creation Tools &gt; Create Grid</strong></p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/09/01_create_grid.jpg" alt="Hexperiments in QGIS" loading="lazy"></p>
<p>Select your grid type: <strong>hexagon</strong></p>
<p>I created grid using 300000,400000,700000,800000 and cell size 5000, 5000. Note the order of the coordinates for the extents!  Output as <strong>hex_grid_1.shp</strong></p>
<p>The new layer should be added to your QGIS project.  Nice.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/09/03_hex_grid.jpg" alt="Hexperiments in QGIS" loading="lazy"></p>
<p>Save this hex grid layer as a new layer - <strong>hex_grid_2.shp</strong>  Now you&apos;ll have two layers exactly the same.</p>
<p><em>QGIS creates a hex grid with flat tops and bottoms and pointy sides.  I&apos;ll rotate this later on to get the pointy bits top and bottom.</em></p>
<p>Add the QGIS <strong>Affine Transformation</strong> plugin to QGIS if not already installed.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/09/02_affine_trans.jpg" alt="Hexperiments in QGIS" loading="lazy"></p>
<p>Select all features in the layer hex_grid_2. Start an edit session.</p>
<p>Open the Affine Transformation plugin, select the hex_grid_2 layer and choose to transform the <strong>whole layer</strong>.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/09/04_shift_grid-1.jpg" alt="Hexperiments in QGIS" loading="lazy"></p>
<p>In the transformation matrix set the parameters like this:</p>
<pre><code>X&apos; = 1x + 0y + -1443.37567297
Y&apos; = 0x + 1y + 2500.0
</code></pre>
<p>How did I get these numbers?  Well, leave the four cells on the left at their default values of 1 and 0.  We only want to change the two cells on the right to shift the layer in the XY plane.  My hex grid had a cell size of 5000, 5000 and so my shift in the X direction needs to be half the distance between two parallel sides: 5000/2 = 2500.  My shift in the Y direction is 1/4 of the distance between points on a hex cell.  This shift of -1443 and 2500 will move the cells and align the vertices of the cells perfectly to create a pseudo 3D effect.</p>
<p>The attribute table of the hex grid layer has values for top, bottom, left and right for each cell.  So, if you have a different cell size to mine, just divide the difference between top and bottom by two and the difference between left and right by four to get your parameter values.</p>
<p>Your layer will shift.  Check it is in the correct place, save edits and stop editing.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/09/05_two_grids.jpg" alt="Hexperiments in QGIS" loading="lazy"></p>
<p>Now we need to intersect these two layers to create a final layer of hex bins with internal partitions.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/09/06_intersect.jpg" alt="Hexperiments in QGIS" loading="lazy"></p>
<p>Do your point in polygon count, style &apos;em up and see what you get!</p>
<h4 id="rotatingtogetthepointybitsup">Rotating to get the pointy bits up</h4>
<p>Now, if you want to rotate your hex grid layer so the pointy bits are top and bottom you need to fiddle with the Affine Transformation plugin and work out how to rotate it 90 degrees around the layer centre point.</p>
<p>After some searching I found the solution on <a href="http://hydrogeotools.blogspot.co.uk/2015/03/scaling-and-rotating-vector-layers-in.html?view=sidebar">hydrogeotools</a>.</p>
<p>This is what I used:</p>
<pre><code>x&apos; = cos(&#x3B8;)*x -sin(&#x3B8;)*y + (x0 - cos(&#x3B8;) * x0 + sin(&#x3B8;) * y0)
y&apos; = -sin(&#x3B8;)*x + cos(&#x3B8;)*y + (y0 - sin(&#x3B8;) * x0 - cos(&#x3B8;) * y0)
</code></pre>
<p><strong>Caveat:</strong> Theta (&#x3B8;) needs to be in radians and not degrees and that had me scratching my head for a bit. To rotate 90 degrees, convert to radians with <code>pi * 90 / 180</code> and you&apos;ll get <code>1.570796</code>. And <code>x0</code> and <code>y0</code> are the coordinates of the point you want to rotate around.</p>
<p>Thus, the parameters to be fed into the Affine plugin are:</p>
<pre><code>x&apos; = a, b, c = cos(&#x3B8;), -sin(&#x3B8;), (x0 - cos(&#x3B8;) * x0 + sin(&#x3B8;) * y0)
y&apos; = d, e, f = -sin(&#x3B8;), cos(&#x3B8;), (y0 - sin(&#x3B8;) * x0 - cos(&#x3B8;) * y0)
</code></pre>
<p>which in my case resulted in</p>
<pre><code>x&apos; = 0, -1, 1100000
y&apos; = 1, 0, 400000
</code></pre>
<p>Bung those in the transformer and the layer should rotate 90 degrees around 350000, 750000.</p>
<p>I also did it in PostGIS with the ST_Rotate function (a lot easier but then I did have a handy PostGIS DB nearby):</p>
<pre><code>CREATE OR REPLACE VIEW hexgrid1_rotate AS
SELECT id, ST_Rotate(geometry, -pi()/2, 350000, 750000) FROM hexgrid1;
</code></pre>
<p>I loaded my shapefile layer into the database and created a rotated view of it.  The SQL above takes the geometry, rotates it 90 degrees (converting to radians) clockwise around a point (350000,750000).  I added this into QGIS, saved is as a shapefile, saved it again and went through the affining process (same as above but remember your X and Y shifts are now swapped because you&apos;ve rotated your layer) to shift the layer into the correct position.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/09/07_pointy_up.jpg" alt="Hexperiments in QGIS" loading="lazy"></p>
<p>As ever, there may well be a better way of doing this (maybe I should learn some Python...) and thank you to the giants with big shoulders...</p>
<h4 id="otherusefulresources">Other useful resources</h4>
<ul>
<li><a href="http://gis.stackexchange.com/questions/13433/moving-vectors-to-specified-coordinates-in-qgis">http://gis.stackexchange.com/questions/13433/moving-vectors-to-specified-coordinates-in-qgis</a></li>
<li><a href="http://postgis.net/docs/ST_Rotate.html">http://postgis.net/docs/ST_Rotate.html</a></li>
<li><a href="http://gis.stackexchange.com/questions/21696/rotating-a-vector-layer-in-qgis-with-qgsaffine-or-other-method?rq=1">http://gis.stackexchange.com/questions/21696/rotating-a-vector-layer-in-qgis-with-qgsaffine-or-other-method?rq=1</a></li>
<li><a href="http://gis.stackexchange.com/questions/30559/how-to-compute-parameters-for-qgis-affine-transformation?noredirect=1&amp;lq=1">http://gis.stackexchange.com/questions/30559/how-to-compute-parameters-for-qgis-affine-transformation?noredirect=1&amp;lq=1</a></li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[FOSS4G UK 2016]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>This is my talk on pgRouting from FOSS4G UK 2016.</p>
<iframe src="//www.slideshare.net/slideshow/embed_code/key/inPr8ifOrTZC0D" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/RossMcDonald1/pgroutingfoss4gukrossmcdonald" title="Pgrouting_foss4guk_ross_mcdonald" target="_blank">Pgrouting_foss4guk_ross_mcdonald</a> </strong> from <strong><a href="//www.slideshare.net/RossMcDonald1" target="_blank">Ross McDonald</a></strong> </div>
<p>My workshop notes and data are available <a href="http://mixedbredie.github.io/pgrouting-workshop">here</a>.  The workshop was designed around using the OSGeo Live DVD so that everyone could work in a controlled environment.  If you&apos;ve got QGIS</p>]]></description><link>https://ghost.mixedbredie.net/foss4g-uk-2016/</link><guid isPermaLink="false">6166f91546cfc09e4de63f4d</guid><category><![CDATA[pgrouting]]></category><category><![CDATA[foss4guk]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Fri, 17 Jun 2016 09:40:23 GMT</pubDate><media:content url="https://ghost.mixedbredie.net/content/images/2016/06/ross_pgrouting_talk.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://ghost.mixedbredie.net/content/images/2016/06/ross_pgrouting_talk.jpg" alt="FOSS4G UK 2016"><p>This is my talk on pgRouting from FOSS4G UK 2016.</p>
<iframe src="//www.slideshare.net/slideshow/embed_code/key/inPr8ifOrTZC0D" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/RossMcDonald1/pgroutingfoss4gukrossmcdonald" title="Pgrouting_foss4guk_ross_mcdonald" target="_blank">Pgrouting_foss4guk_ross_mcdonald</a> </strong> from <strong><a href="//www.slideshare.net/RossMcDonald1" target="_blank">Ross McDonald</a></strong> </div>
<p>My workshop notes and data are available <a href="http://mixedbredie.github.io/pgrouting-workshop">here</a>.  The workshop was designed around using the OSGeo Live DVD so that everyone could work in a controlled environment.  If you&apos;ve got QGIS and PostgreSQL (with PostGIS and pgRouting) installed you should be able to follow along quite easily making the small changes to database connections where required.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/06/ross_pgrouting_workshop.jpg" alt="FOSS4G UK 2016" loading="lazy"></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Building Highways for pgRouting]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>A copy-and-paste post to help you build the new OS Highways for pgRouting.  It is still a work in progress so things may change and break.  Requires PostgreSQL with PostGIS and pgRouting and a licensed copy of Safe Software&apos;s FME Desktop.</p>
<h2 id="1loadthedata">1. Load the data</h2>
<p>If you have</p>]]></description><link>https://ghost.mixedbredie.net/building-highways-for-pgrouting/</link><guid isPermaLink="false">6166f91546cfc09e4de63f4c</guid><category><![CDATA[pgrouting]]></category><category><![CDATA[ordnance survey]]></category><category><![CDATA[highways]]></category><category><![CDATA[routing]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Thu, 09 Jun 2016 15:20:47 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>A copy-and-paste post to help you build the new OS Highways for pgRouting.  It is still a work in progress so things may change and break.  Requires PostgreSQL with PostGIS and pgRouting and a licensed copy of Safe Software&apos;s FME Desktop.</p>
<h2 id="1loadthedata">1. Load the data</h2>
<p>If you have Safe Software&#x2019;s FME you can download the workbenches to create the database tables here: <a href="https://github.com/OrdnanceSurvey/OSMM-Highways-Network-Support">https://github.com/OrdnanceSurvey/OSMM-Highways-Network-Support</a> but note that you will have to edit the workbenches to write to PostGIS instead of GeoPackage.  Not hard if you&#x2019;ve used FME before.  Other data loaders will support loading Highways data once the data structure is finalised.</p>
<p>The following tables are created by FME process:</p>
<ul>
<li>hw_accessrestriction*</li>
<li>hw_accessrestriction_inclusion</li>
<li>hw_accessrestriction_exemption</li>
<li>hw_accessrestriction_networkref*</li>
<li>hw_restrictionforvehicles</li>
<li>hw_restrictionsforvehicles_networkref</li>
<li>hw_road</li>
<li>hw_road_networkref</li>
<li>hw_roadlink*</li>
<li>hw_roadlink_formspartof</li>
<li>hw_roadlink_relatedroadarea</li>
<li>hw_roadnode*</li>
<li>hw_roadnode_relatedroadarea</li>
<li>hw_turnrestriction*</li>
<li>hw_turnrestriction_networkref*</li>
</ul>
<p>* Tables used in this exercise</p>
<h2 id="2addthefieldsrequiredforpgrouting">2. Add the fields required for pgRouting</h2>
<pre><code>    ALTER TABLE my_schema.hw_roadlink
      ADD COLUMN source integer,
      ADD COLUMN target integer,
      ADD COLUMN one_way varchar(2),
      ADD COLUMN speed_km integer,
      ADD COLUMN cost_len double precision,
      ADD COLUMN rcost_len double precision,
      ADD COLUMN cost_time double precision,
      ADD COLUMN rcost_time double precision,
      ADD COLUMN x1 double precision,
      ADD COLUMN y1 double precision,
      ADD COLUMN x2 double precision,
      ADD COLUMN y2 double precision,
      ADD COLUMN to_cost double precision,
      ADD COLUMN rule text,
      ADD COLUMN isolated integer;
</code></pre>
<h2 id="3createindexesforspeed">3. Create indexes for speed</h2>
<pre><code>    CREATE INDEX hw_roadlink_source_idx ON my_schema.hw_roadlink USING btree(source);
    CREATE INDEX hw_roadlink_target_idx ON my_schema.hw_roadlink USING btree(target);
</code></pre>
<h2 id="4updatenewfieldswithvaluesrequiredforpgrouting">4. Update new fields with values required for pgRouting</h2>
<p>The start and end coordinates of the links are used in the Astar shortest path algorithmto determine the links closest to the target shortest path solution.</p>
<pre><code>    UPDATE my_schema.hw_roadlink 
      SET x1 = st_x(st_startpoint(centrelinegeometry)),
        y1 = st_y(st_startpoint(centrelinegeometry)),
        x2 = st_x(st_endpoint(centrelinegeometry)),
        y2 = st_y(st_endpoint(centrelinegeometry));
</code></pre>
<p>Setting the one_way flags helps pgRouting analyse the road network graph for errors.</p>
<pre><code>    UPDATE hw_roadlink SET one_way = &apos;B&apos; WHERE directionality = &apos;bothDirections&apos;;
    UPDATE hw_roadlink SET one_way = &apos;TF&apos; WHERE directionality = &apos;inOppositeDirection&apos;;
    UPDATE hw_roadlink SET one_way = &apos;FT&apos; WHERE directionality = &apos;inDirection&apos;;
</code></pre>
<p>Costs are what the routing engine uses to calculate the best route across the network.  In this example we are using <strong>distance</strong>, based on link length, and <strong>time</strong>, based on average speed for particular road classification and link length.  For links with one way directionality we set the reverse cost very high to discourage use.  pgRouting uses the digitised direction of the line to help with routing and the Highways layer helpfully has a field which tells us, for one way streets, which way the line has been drawn and which way the traffic is expected to flow.  For one way streets with traffic flow in the same direction as digitised direction the directionality flag is set to <strong>inDirection</strong>. For traffic flow contra to line direction it is set to <strong>inOppositeDirection</strong>. For links with two way flow of traffic it is set to <strong>bothDirections</strong>.</p>
<p>Forward and reverse cost is the same for two way streets</p>
<pre><code>    UPDATE my_schema.hw_roadlink
      SET cost_len = ST_Length(centrelinegeometry),
          rcost_len = ST_Length(centrelinegeometry)
      WHERE directionality IN (&apos;bothDirections&apos;);
</code></pre>
<p>Reverse costs are increased for one way streets</p>
<pre><code>    UPDATE my_schema.hw_roadlink
      SET cost_len = ST_Length(centrelinegeometry),
          rcost_len = ST_Length(centrelinegeometry)*100
      WHERE directionality IN (&apos;inDirection&apos;);
    
    UPDATE my_schema.hw_roadlink
      SET rcost_len = ST_Length(centrelinegeometry),
          cost_len = ST_Length(centrelinegeometry)*100
      WHERE directionality IN (&apos;inOppositeDirection&apos;);
</code></pre>
<p>To set the time cost I set an average speed based on the road classifiation and form of the road.  A tip here is to remember to set the speed for slip roads, traffic island links to be the same as the speed for the carriageway of the same class.  This prevents odd routing through complex junctions.</p>
<p>Check what type of roads you are using with:</p>
<pre><code>    SELECT DISTINCT roadclassification, formofway
      FROM my_schema.hw_roadlink
      ORDER BY roadclassification;
</code></pre>
<p>Set the road speed by classification and form. Adjust as required</p>
<pre><code>    UPDATE my_schema.hw_roadlink
    SET speed_km = 
    CASE
    	WHEN roadclassification = &apos;Motorway&apos; AND formofway = &apos;Dual Carriageway&apos; THEN 110
    	WHEN roadclassification = &apos;Motorway&apos; AND formofway = &apos;Slip Road&apos; THEN 110
    	WHEN roadclassification = &apos;A Road&apos; AND formofway = &apos;Roundabout&apos; THEN 40
    	WHEN roadclassification = &apos;A Road&apos; AND formofway = &apos;Dual Carriageway&apos; THEN 100
    	WHEN roadclassification = &apos;A Road&apos; AND formofway = &apos;Traffic Island Link&apos; THEN 100
    	WHEN roadclassification = &apos;A Road&apos; AND formofway = &apos;Slip Road&apos; THEN 100
    	WHEN roadclassification = &apos;A Road&apos; AND formofway = &apos;Traffic Island Link At Junction&apos; THEN 100
    	WHEN roadclassification = &apos;A Road&apos; AND formofway = &apos;Single Carriageway&apos; THEN 100
    	WHEN roadclassification = &apos;B Road&apos; AND formofway = &apos;Dual Carriageway&apos; THEN 100
    	WHEN roadclassification = &apos;B Road&apos; AND formofway = &apos;Single Carriageway&apos; THEN 80
    	WHEN roadclassification = &apos;B Road&apos; AND formofway = &apos;Slip Road&apos; THEN 80
    	WHEN roadclassification = &apos;B Road&apos; AND formofway = &apos;Roundabout&apos; THEN 40
    	WHEN roadclassification = &apos;B Road&apos; AND formofway = &apos;Traffic Island Link&apos; THEN 80
    	WHEN roadclassification = &apos;B Road&apos; AND formofway = &apos;Traffic Island Link At Junction&apos; THEN 80
    	WHEN roadclassification = &apos;Not Classified&apos; AND formofway = &apos;Traffic Island Link&apos; THEN 60
    	WHEN roadclassification = &apos;Not Classified&apos; AND formofway = &apos;Single Carriageway&apos; THEN 60
    	WHEN roadclassification = &apos;Not Classified&apos; AND formofway = &apos;Roundabout&apos; THEN 40
    	WHEN roadclassification = &apos;Not Classified&apos; AND formofway = &apos;Dual Carriageway&apos; THEN 100
    	WHEN roadclassification = &apos;Not Classified&apos; AND formofway = &apos;Enclosed Traffic Area&apos; THEN 40 
    	WHEN roadclassification = &apos;Not Classified&apos; AND formofway = &apos;Traffic Island Link At Junction&apos; THEN 60
    	WHEN roadclassification = &apos;Not Classified&apos; AND formofway = &apos;Slip Road&apos; THEN 60
    	WHEN roadclassification = &apos;Unclassified&apos; AND formofway = &apos;Slip Road&apos; THEN 40
    	WHEN roadclassification = &apos;Unclassified&apos; AND formofway = &apos;Enclosed Traffic Area&apos; THEN 40
    	WHEN roadclassification = &apos;Unclassified&apos; AND formofway = &apos;Single Carriageway&apos; THEN 40
    	WHEN roadclassification = &apos;Unclassified&apos; AND formofway = &apos;Layby&apos; THEN 10
    	WHEN roadclassification = &apos;Unclassified&apos; AND formofway = &apos;Traffic Island Link At Junction&apos; THEN 40
    	WHEN roadclassification = &apos;Unclassified&apos; AND formofway = &apos;Traffic Island Link&apos; THEN 40
    	WHEN roadclassification = &apos;Unclassified&apos; AND formofway = &apos;Dual Carriageway&apos; THEN 100
    	WHEN roadclassification = &apos;Unclassified&apos; AND formofway = &apos;Roundabout&apos; THEN 40
    	ELSE 1
    END;
</code></pre>
<p>Update the travel time costs for two way roads.</p>
<pre><code>    UPDATE my_schema.hw_roadlink
      SET cost_time = ST_Length(centrelinegeometry)/1000.0/speed_km::numeric*3600.0,
          rcost_time = ST_Length(centrelinegeometry)/1000.0/speed_km::numeric*3600.0
      WHERE directionality IN (&apos;bothDirections&apos;);
</code></pre>
<p>Update the travel time costs for one way streets digitised the same way as traffic flow.</p>
<pre><code>    UPDATE my_schema.hw_roadlink
      SET cost_time = ST_Length(centrelinegeometry)/1000.0/speed_km::numeric*3600.0,
          rcost_time = ST_Length(centrelinegeometry)*100/1000.0/speed_km::numeric*3600.0
      WHERE directionality IN (&apos;inDirection&apos;);
</code></pre>
<p>Set the travel time costs for one way streets digitised against the traffic flow.</p>
<pre><code>    UPDATE my_schema.hw_roadlink
      SET rcost_time = ST_Length(centrelinegeometry)/1000.0/speed_km::numeric*3600.0,
          cost_time = ST_Length(centrelinegeometry)*100/1000.0/speed_km::numeric*3600.0
      WHERE directionality IN (&apos;inOppositeDirection&apos;);
</code></pre>
<h2 id="5initialbuildofroutingnetwork">5. Initial build of routing network</h2>
<p>Build network topology</p>
<pre><code>    SELECT pgr_createTopology(&apos;my_schema.hw_roadlink&apos;, 0.001, &apos;centrelinegeometry&apos;, &apos;ogc_fid&apos;, &apos;source&apos;, &apos;target&apos;);
</code></pre>
<p>Check topology for errors</p>
<pre><code>    SELECT pgr_analyzeGraph(&apos;my_schema.hw_roadlink&apos;, 0.001, &apos; centrelinegeometry &apos;, &apos;ogc_fid&apos;, &apos;source&apos;, &apos;target&apos;); 
</code></pre>
<p>Check for one-way errors</p>
<pre><code>    SELECT pgr_analyzeOneway(&apos;my_schema.hw_roadlink&apos;,
        ARRAY[&apos;&apos;, &apos;B&apos;, &apos;TF&apos;],
        ARRAY[&apos;&apos;, &apos;B&apos;, &apos;FT&apos;],
        ARRAY[&apos;&apos;, &apos;B&apos;, &apos;FT&apos;],
        ARRAY[&apos;&apos;, &apos;B&apos;, &apos;TF&apos;],
        oneway:=&apos;one_way&apos;
        );
</code></pre>
<p>Identify links with problems</p>
<pre><code>    SELECT * FROM hw_roadlink_vertices_pgr WHERE chk = 1;
</code></pre>
<p>Identify links with deadends</p>
<pre><code>    SELECT * FROM hw_roadlink_vertices_pgr WHERE cnt = 1;
</code></pre>
<p>Identify isolated segments</p>
<pre><code>    SELECT * FROM hw_roadlink a, hw_roadlink_vertices_pgr b, hw_roadlink_vertices_pgr c 
    WHERE a.source = b.id AND b.cnt = 1 AND a.target = c.id AND c.cnt = 1;
</code></pre>
<p>Identify nodes with problems</p>
<pre><code>    SELECT * FROM hw_roadlink_vertices_pgr WHERE ein = 0 OR eout = 0;
</code></pre>
<p>Identify the links at nodes with problems</p>
<pre><code>    SELECT gid FROM hw_roadlink a, hw_roadlink_vertices_pgr b WHERE a.source=b.id AND ein=0 OR eout=0
      UNION
    SELECT gid FROM hw_roadlink a, hw_roadlink_vertices_pgr b WHERE a.target=b.id AND ein=0 OR eout=0;
</code></pre>
<h2 id="6addinginturnrestrictions">6. Adding in turn restrictions</h2>
<h3 id="noturnturnrestrictions">&#x201C;No Turn&#x201D; turn restrictions</h3>
<p>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.</p>
<p>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.</p>
<pre><code>    CREATE OR REPLACE VIEW view_hw_nt_links AS
    SELECT row_number() OVER () AS gid, 
    	a.roadlink_toid AS from_link, 
    	c.ogc_fid AS from_fid, 
    	b.roadlink_toid AS to_link, 
    	d.ogc_fid AS to_fid, 
    	a.applicabledirection, 
    	a.sequence AS from_seq, 
    	b.sequence AS to_seq, 
    	a.restriction, 
    	c.centrelinegeometry
    FROM hw_turnrestriction_networkref a
    INNER JOIN hw_turnrestriction_networkref b ON a.toid = b.toid 
    INNER JOIN hw_roadlink c ON c.toid = a.roadlink_toid
    INNER JOIN hw_roadlink d ON d.toid = b.roadlink_toid
    WHERE b.sequence = 1 
    AND a.sequence = 0
    AND a.restriction = &apos;No Turn&apos;;
</code></pre>
<h3 id="mandatoryturnturnrestrictions">&#x201C;Mandatory Turn&#x201D; turn restrictions</h3>
<p>Mandatory turns are modelled in the dataset as turns which are allowed. So there is the entry link and the exit link which, together with the associated node make up the mandatory turn.  To create a restriction here to enforce the mandatory turn you need to select all the other links in the turn and restrict access to them so that the only way is Essex.</p>
<p>Create a view of the entry link and associated node</p>
<pre><code>    CREATE OR REPLACE VIEW view_hw_mt_link1 AS
    SELECT a.roadlink_toid,
      b.ogc_fid, 
      a.sequence,
      a.applicabledirection, 
      (CASE WHEN a.applicabledirection = &apos;inOppositeDirection&apos; AND sequence = 0 THEN b.startnode 
        WHEN a.applicabledirection = &apos;inDirection&apos; AND sequence = 1 THEN b.startnode
        ELSE b.endnode END) AS nodetoid,
      b.centrelinegeometry 
    FROM hw_turnrestriction_networkref a
    INNER JOIN hw_roadlink b ON a.roadlink_toid = b.toid
    WHERE a.restriction = &apos;Mandatory Turn&apos;
    AND a.sequence = 0;
</code></pre>
<p>Then create a view of the exit link.</p>
<pre><code>    CREATE OR REPLACE VIEW view_hw_mt_link2 AS
    SELECT a.roadlink_toid,
      b.ogc_fid, 
      a.sequence,
      a.applicabledirection, 
      (CASE WHEN a.applicabledirection = &apos;inOppositeDirection&apos; AND sequence = 0 THEN b.startnode 
        WHEN a.applicabledirection = &apos;inDirection&apos; AND sequence = 1 THEN b.startnode
        ELSE b.endnode END) AS nodetoid,
      b.centrelinegeometry  
    FROM hw_turnrestriction_networkref a
    INNER JOIN hw_roadlink b ON a.roadlink_toid = b.toid
    WHERE restriction = &apos;Mandatory Turn&apos;
    AND sequence = 1;
</code></pre>
<p>Combine these to select the other links in the turn, the links you cannot turn into.</p>
<pre><code>    CREATE OR REPLACE VIEW view_hw_mt_nt_links AS
    SELECT row_number() OVER () AS gid,
      a.roadlink_toid AS from_toid,
      a.ogc_fid AS from_fid,
      b.toid AS to_toid,
      b.ogc_fid AS to_fid
    FROM view_hw_mt_link1 a
    INNER JOIN hw_roadlink b ON (a.nodetoid = b.startnode OR a.nodetoid = b.endnode)
    AND a.roadlink_toid &lt;&gt; b.toid
    AND b.toid NOT IN (SELECT roadlink_toid FROM view_hw_mt_link2);
</code></pre>
<h3 id="noentryturnrestrictions">&#x201C;No Entry&#x201D; turn restrictions</h3>
<p>No entry restrictions are those links where it is legally forbidden to enter the link travelling the wrong way. The information is held in the AccessRestriction tables.</p>
<p>First, select the node entry links and the associated road node into a view.</p>
<pre><code>    CREATE OR REPLACE VIEW my_schema.view_hw_ne_link AS
    SELECT row_number() OVER () AS gid,
      a.restriction, 
      a.trafficsign, 
      b.applicabledirection, 
      b.atposition, 
      b.roadlink_toid, 
      c.ogc_fid,
      (CASE WHEN b.applicabledirection = &apos;inOppositeDirection&apos; THEN c.endnode ELSE c.startnode END) AS roadnode_toid,
      c.centrelinegeometry
    FROM hw_accessrestriction a
    INNER JOIN hw_accessrestriction_networkref b ON a.toid = b.toid
    INNER JOIN hw_roadlink c ON c.toid = b.roadlink_toid
    WHERE a.trafficsign = &apos;No Entry&apos;;
</code></pre>
<p>Then, select all the other links at that node and restrict turning into the no entry link.</p>
<pre><code>    CREATE OR REPLACE VIEW my_schema.view_hw_ne_other_links AS
    SELECT row_number() OVER () AS gid, 
      a.toid AS from_toid, 
      a.ogc_fid AS from_fid, 
      b.roadlink_toid AS to_toid, 
      b.ogc_fid AS to_fid
    FROM hw_roadlink a
    INNER JOIN view_hw_ne_link b ON (b.roadnode_toid = a.startnode OR b.roadnode_toid = a.endnode)
    AND a.toid &lt;&gt; b.roadlink_toid;
</code></pre>
<h3 id="gradeseparationturnrestrictions">&#x201C;Grade Separation&#x201D; turn restrictions</h3>
<p><em>This section needs some more thinking to work with more complex grade separated examples.</em></p>
<p>These restrictions are built to stop your route dropping off the bridge onto the road below to find the shortest path between source and target.  They are created from the road nodes with a classification of &#x201C;Grade Separation&#x201D; and the associated road links.  Normally, there are four links associated to every node in an over- and underpass situation. So, for example, in the Angus network there are 41 nodes and 164 associated links.  First, create a view of all the nodes with associated links.</p>
<pre><code>    CREATE OR REPLACE VIEW my_schema.view_hw_gs_nodes AS 
     SELECT row_number() OVER () AS id, 
        rl.ogc_fid, rl.toid AS linktoid, 
        rn.toid AS nodetoid, 
        rl.startnode AS startnodetoid, 
        rl.startgradeseparation, 
        rl.endnode AS endnodetoid, 
        rl.endgradeseparation,
        rl.directionality, 
        rn.classification, 
        rn.geometry
       FROM hw_roadlink rl, hw_roadnode rn
      WHERE (rn.toid::text = rl.startnode::text OR rn.toid::text = rl.endnode::text) 
      AND rn.classification::text = &apos;Grade Separation&apos;::text;
</code></pre>
<p>Now use the view of nodes to build the restrictions at the grade separated node for each link</p>
<pre><code>    CREATE OR REPLACE VIEW view_hw_gs_nt_links AS
    SELECT row_number() OVER () AS gid,
      a.linktoid AS from_link,
      a.ogc_fid AS from_fid,
      b.linktoid AS to_link,
      b.ogc_fid AS to_fid
    FROM view_hw_gs_nodes a
    JOIN view_hw_gs_nodes b ON a.nodetoid = b.nodetoid
    WHERE a.linktoid &lt;&gt; b.linktoid
    AND a.directionality &lt;&gt; b.directionality;
</code></pre>
<p><em>NOTE: This may not be 100% correct but it works for the network in Angus as we have relatively simple network topologies.</em></p>
<h3 id="creatingtheturnrestrictiontableforpgrouting">Creating the turn restriction table for pgRouting</h3>
<p>We&#x2019;ll create a table to hold the turn restriction data.  This is in the format FROM ID, TO ID, COST where the IDs are the road link IDs.</p>
<pre><code>    CREATE TABLE my_schema.hw_nt_restrictions
    (  rid integer NOT NULL,
      to_cost double precision,
      teid integer,
      feid integer,
      via text )
    WITH (
      OIDS=FALSE
    );
    COMMENT ON TABLE my_schema.hw_nt_restrictions   IS &apos;Highways Turn Restrictions&apos;;
</code></pre>
<p>Once we have the table we can fill it with the turn restriction records and then calculate the restriction cost.</p>
<p>Insert the GRADE SEPARATION TURN restrictions first</p>
<pre><code>    INSERT INTO my_schema.hw_nt_restrictions(rid,feid,teid)
      SELECT gid AS rid,
      from_fid AS feid,
      to_fid AS teid 
      FROM view_hw_gs_nt_links v
      WHERE v.to_fid &lt;&gt; 0
      AND v.to_fid NOT IN (SELECT DISTINCT t.teid FROM my_schema.hw_nt_restrictions t WHERE t.rid = v.gid);
</code></pre>
<p>Then the NO TURN restrictions</p>
<pre><code>    INSERT INTO my_schema.hw_nt_restrictions(rid,feid,teid)
      SELECT gid AS rid,
      from_fid AS feid,
      to_fid AS teid 
      FROM view_hw_nt_links v
      WHERE v.to_fid &lt;&gt; 0
      AND v.to_fid NOT IN (SELECT DISTINCT t.teid FROM my_schema.hw_nt_restrictions t WHERE t.rid = v.gid);
</code></pre>
<p>Then the MANDATORY TURN restrictions</p>
<pre><code>    INSERT INTO my_schema.hw_nt_restrictions(rid,feid,teid)
      SELECT gid AS rid,
      from_fid AS feid,
      to_fid AS teid 
      FROM view_hw_mt_nt_links v
      WHERE v.to_fid &lt;&gt; 0
      AND v.to_fid NOT IN (SELECT DISTINCT t.teid FROM my_schema.hw_nt_restrictions t WHERE t.rid = v.gid);
</code></pre>
<p>Lastly, the NO ENTRY turn restrictions</p>
<pre><code>    INSERT INTO my_schema.hw_nt_restrictions(rid,feid,teid)
      SELECT gid AS rid,
      from_fid AS feid,
      to_fid AS teid 
      FROM view_hw_ne_other_links v
      WHERE v.to_fid &lt;&gt; 0
      AND v.to_fid NOT IN (SELECT DISTINCT t.teid FROM my_schema.hw_nt_restrictions t WHERE t.rid = v.gid);
</code></pre>
<p>Then we update the cost field to some high value.</p>
<pre><code>    UPDATE my_schema.hw_nt_restrictions SET to_cost = 9999;
</code></pre>
<p>Use the following in the TRSP function in the pgRouting Layer plugin in QGIS</p>
<pre><code>    &apos;select to_cost, teid as target_id, feid||coalesce(&apos;&apos;,&apos;&apos;||via,&apos;&apos;&apos;&apos;) as via_path from hw_nt_restrictions&apos;
</code></pre>
<p>At this point you will have a functional network topology that pgRouting will be able to use to generate reasonably accurate routes.  Paths will respect road restrictions and grade separations and the direction of traffic flow.</p>
<h2 id="7futurework">7. Future work</h2>
<p>As this is the first release of Highways there is still work to be done on dataset attributes and adding in additional features.</p>
<p>Things to considers:</p>
<ul>
<li>Adding in vehicle restrictions</li>
<li>Road width</li>
<li>Bridge height</li>
<li>Bridge capacity</li>
<li>Hazards</li>
<li>Time restrictions</li>
<li>Better speed estimates and additional cost fields</li>
<li>Network subsets for public transport and safe routes</li>
<li>Integrating with rail networks and foot path networks</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Merging CSV files]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Let&apos;s say you want to merge a whole directory of CSV files into one large file to make data management easier.  For example, the Ordnance Survey <a href="https://www.ordnancesurvey.co.uk/business-and-government/products/code-point-open.html">Code-Point Open</a> dataset comes as multiple nested CSV files (121 of them) in one large zip archive.  In addition to merging the</p>]]></description><link>https://ghost.mixedbredie.net/merging-csv-files/</link><guid isPermaLink="false">6166f91546cfc09e4de63f4b</guid><category><![CDATA[data]]></category><category><![CDATA[opendata]]></category><category><![CDATA[command line]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Fri, 27 May 2016 14:31:59 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Let&apos;s say you want to merge a whole directory of CSV files into one large file to make data management easier.  For example, the Ordnance Survey <a href="https://www.ordnancesurvey.co.uk/business-and-government/products/code-point-open.html">Code-Point Open</a> dataset comes as multiple nested CSV files (121 of them) in one large zip archive.  In addition to merging the files into one, you might also only want certain fields from the individual files like <code>POSTCODE</code>, <code>EASTING</code> and <code>NORTHING</code>.</p>
<p>Using the Code-Point Open example the following command line will, on Windows, do what you want it to do:</p>
<pre><code>for /f %a in (&apos;dir /b *.csv&apos;) do for /f &quot;tokens=1,11,12 delims=,&quot; %b in (&apos;dir /b *.csv&apos;) do echo %b %c %d &gt;&gt; alluk.csv
</code></pre>
<p>To translate this into English: For all the CSV files in the directory select the records in the first (postcode), eleventh (easting) and twelfth (northing) columns from each file and merge them into one file called alluk.csv.</p>
<p>This file can then be loaded into QGIS and displayed very quickly.</p>
<p><img src="https://ghost.mixedbredie.net/content/images/2016/05/codepointopen.jpg" alt="OS Code-Point Open" loading="lazy"></p>
<p>Contains OS Open Data. Crown copyright and database right 2016.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[pgRouting webinar with OS]]></title><description><![CDATA[<!--kg-card-begin: markdown--><iframe width="420" height="315" src="https://www.youtube.com/embed/WBUpBXeDJVc" frameborder="0" allowfullscreen></iframe>
<p>I recorded a webinar with the <a href="http://os.uk/">Ordnance Survey</a> about the different ways we&apos;ve been using <a href="http://pgrouting.org/">pgRouting</a> with OS datasets. If you&apos;ve got 30 minutes spare you can watch it or just read my other blog posts about pgRouting.</p>
<!--kg-card-end: markdown-->]]></description><link>https://ghost.mixedbredie.net/pgrouting-webinar-with-os/</link><guid isPermaLink="false">6166f91546cfc09e4de63f4a</guid><category><![CDATA[pgrouting]]></category><category><![CDATA[ordnance survey]]></category><dc:creator><![CDATA[Ross McDonald]]></dc:creator><pubDate>Fri, 27 May 2016 14:08:19 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><iframe width="420" height="315" src="https://www.youtube.com/embed/WBUpBXeDJVc" frameborder="0" allowfullscreen></iframe>
<p>I recorded a webinar with the <a href="http://os.uk/">Ordnance Survey</a> about the different ways we&apos;ve been using <a href="http://pgrouting.org/">pgRouting</a> with OS datasets. If you&apos;ve got 30 minutes spare you can watch it or just read my other blog posts about pgRouting.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>