All the files needed to generate PDFs for the Gun Run using QGIS (and Inkscape for certain features).
Go to file
Timothy Allen e82cf4ce26 Update scripts 2023-09-15 10:02:53 +02:00
GIS Base Map Update scripts 2023-09-15 10:02:53 +02:00
fonts 2016 routes. 2018-08-23 10:33:18 +02:00
images Update Ask Me map. 2018-10-08 22:56:45 +02:00
.gitattributes Add large file support. 2018-09-04 23:55:43 +02:00
.gitignore Update gitingore 2021-03-12 07:09:08 +02:00 Remove Fun Run, new 10KM route, and marshal updates 2023-09-07 12:34:32 +02:00
S34E018.HGT Add initial elevation data, and road closures for residents. 2018-09-26 16:09:06 +02:00
capetownelevation.vrt Add initial elevation data, and road closures for residents. 2018-09-26 16:09:06 +02:00 Update scripts 2023-09-15 10:02:53 +02:00 Update scripts 2023-09-15 10:02:53 +02:00 Final updates for 2019. 2019-10-25 09:40:29 +02:00

Welcome to the Gun Run Maps.

To edit route maps, use QGIS, which is freely available from

To edit finish/start/fencing/cone maps, I tend to create an area map, print the base graphic as a vector image, and then open and edit in a vector editing programme, such as the freely-available Inkscape:

In QGIS, you start by using an existing map (the file Gun Run.qgs in the directory "GIS Base Map"), and either manually modifying the existing route or importing a new route (add layer -> add vector layer, and select a GPX file with the routes) as the layer all_routes.shp.

Each of the three routes should be a separate feature in the layer. Each feature should have a column called Distance, with the value "21KM" for the 21.1KM route, "10KM" for the 10KM route, and "5KM" for the 5KM route, and a column named Source with the same value (this is used to display the correct routes when printing different versions of the map).

In general, a layer is a type of map element that contains features. So a water table layer will store each water table as a feature in the form of a polygon; a route layer will store each route as a feature in the form of a line, or a list of marshal positions will store each position as a feature in the form of a point. Road data will be stored in a layer in a mix of points, lines and polygons, depending on the type of feature.

Other layers should be:

  • marshal_positions.shp: either create a new ShapeFile with a point for each marshal position, or edit the existing file. This layer should have the columns:
    • position (the numeric position number, which should match the marshal spreadsheet information with marshal names/phone numbers/email addresses, and the instructions for each position);
    • type (this column should always have the value "marshal" for each position; again, this is used to display the correct routes when printing the marshal versions of the map -- so that when printing marshal maps, all three routes are displayed, and not only one. This is manipulated by using the Symbology rules for the route layer)
  • water_tables.shp: a ShapeFile containing a set of polygons, one polygon for each water table. The layer should have the column label with each feature labelled as "W" (or no label if the table is one of two placed together).
  • osm_roads.shp and osm_places.shp: imports of OpenStreetMap data for the Green Point - Camps Bay area, modified to remove street names that are not useful on our routes (usually because they are minor streets that then automatically show up on our route maps in place of more useful street names). If you wish to renew the information, go to the OpenStreetMap home page, choose the Export option, and then select the Camps Bay - Green Point area and export the information, then import the resulting files as ShapeFiles into the map. Then you'll need to delete minor roads that are not important on our route, or labels for major roads that occur too frequently. You can also move labels if need be. (At present, I've added the columns show, always_show, label_x, label_y, and label_rotation, in order to manually place labels or control which labels should always be shown, never be shown, or shown if convenient. This turned out to be far less efficient than simply deleting features that weren't relevant.).
  • marshal_overviews.shp: a ShapeFile that contains five or so slightly-overlapping polygons that cover the entire race route. This allows you to print a map based on the layer, where each polygon is a page. This means you can get higher resolution prints, splitting the race route up into five maps (or however many polygons you have covering the race area).
  • fencing_areas.shp, start_area.shp, finish_area.shp: these allow you to print specific maps covering just the fencing areas (one map per fence, where each fence is a polygon feature) or start and finish areas.

Lastly, the actual background images are provided by two layers:

  • GDAL - City of Cape Town 2015 Aerial: these are high-quality aerial photographs of the City of Cape Town taken in 2015, and of significantly better quality than those used by Google and Bing.
  • Outlines: a custom MapBox style that only shows feature outlines (roads, parks, water, etc.) and no labels. The labels are provided by our own subset of OSM data, which allows us much greater flexibility in labelling features in which we are interested.


Setting labelling styles and symbology is left to your preference.

Note that there are some fiddly bits that involve giving every feature in a particular layer the same value in a particular column in order to ensure that when a particular map is printed, the correct routes show.

When printing maps, it is also important to be sure that all the layers you want to display are in fact displayed (you'll have to choose either the Outlines background or the Photograph background for different maps, or whether to display route markers, marshal positions, or water tables). In some printed maps, like the individual marshal maps, you'll want two different displays for the same general area: one with a photo background for the close-up area, and a smaller, contextual map with a simple outline background.

More specific instructions:

  • Integrate the marshal spreadsheet into the map:

First, create a CSV with the following fields: position: numeric position, the same as the position column for each feature; name: full name of marshal in that position, if we have one club: club filling the position phone: phone number of the marshal, if we have one email: email address of the marshal (this is used to send each marshal an email with a PDF and JPG map of their position; see the script "") captain: the name of the captain for the position captain_phone: phone number for their captain first_runner: the time they can expect the first runner to pass them last_runner: the time they can expect the last runner to pass them instructions: specific instructions for their position, so they know what to do on the day.

Add this CSV file as a "Delimited Text" layer. Under record/field options, set "First record has field names", and give the layer the name marshals_by_position.

In the marshal positions layer, select properties, and go the Joins section. Add a vector join, select the new marshals_by_positions layer, set the join and target field to "positions" (or the CSV and marshal_positions.shp equivalent if you've renamed them). You can choose which fields are joined if you don't need all the CSV columns, and you can set the custom field name prefix csv\_, so you know which fields come from the CSV in the joined layer.

Now you'll be able to use labels such as [% csv_club %] (to print the name of the club) when generating individual marshal maps.

  • Calculate route markers:

In order to generate the route markers at the correct locations:

Generate distances, using the QGIS plugin Locate points along lines. The polyline layer should all_routes.shp, and the output layer name should be route_markers. Leave the offset at 0 (unless you want to start counting some distance after the start!), and set the Interval to 0,01 (this may change depending on your configured units, but it should be one point per kilometre; you may need to play around with the settings). Lastly, check Keep Attributes (this allows us to keep the Source field for each line, which will connect the point to the route for which it is a marker), and check Add endpoints.

Then, in the field calculator for the new route_markers row, set the field Name to the calculation:

if (
	"distance" > 0,
	if (
		"distance" = round( "distance", 2 ),
		round("distance" * 100, 0),

To place the route markers on the appropriate side of the route, use something like the following in the field calculator to create a new text field named quadrant (used by the label's position priority in the placement section):

WHEN "Source" = '21KM' THEN
		WHEN regexp_match( "Name", '^([136]|20|1[45678])$') THEN 'L,TL'
		WHEN regexp_match( "Name", '^([489]|10|1[123])$') THEN 'R,BR'
		WHEN regexp_match( "Name", '^([25]|19)$') THEN 'T'
		WHEN regexp_match( "Name", '^([7])$') THEN 'B'
		WHEN regexp_match( "Name", '^Finish$') THEN 'BL,L'
		WHEN regexp_match( "Name", '^Start$') THEN 'L,BL'
WHEN "Source" = '10KM' THEN
		WHEN regexp_match( "Name", '^[789]$') THEN 'L,TL'
		WHEN regexp_match( "Name", '^[2456]$') THEN 'R,BR'
		WHEN regexp_match( "Name", '^[1]$') THEN 'T'
		WHEN regexp_match( "Name", '^[3]$') THEN 'B'
		WHEN regexp_match( "Name", '^Finish$') THEN 'BR,R'
		WHEN regexp_match( "Name", '^Start$') THEN 'BR,R'
WHEN "Source" ILIKE '%5KM%' THEN
		WHEN regexp_match( "Name", '^[12]$') THEN 'L,TL'
		WHEN regexp_match( "Name", '^[0]$') THEN 'R,BR'
		WHEN regexp_match( "Name", '^[4]$') THEN 'T'
		WHEN regexp_match( "Name", '^[3]$') THEN 'B'
		WHEN regexp_match( "Name", '^Finish$') THEN 'BL,R'
		WHEN regexp_match( "Name", '^Start$') THEN 'BR,R'
WHEN "Source" = 'Trail' THEN
		WHEN regexp_match( "Name", '^14$') THEN 'L,TL'
		WHEN regexp_match( "Name", '^8$') THEN 'L,BL'
		WHEN regexp_match( "Name", '^9$') THEN 'R,TR'
		WHEN regexp_match( "Name", '^Finish$') THEN 'BL,L'
		WHEN regexp_match( "Name", '^Start$') THEN 'BR,R'

(Note that you may wish to allocate particular markers different positional quadrant; edit the above as needed.)

Once this is done, you may choose to manually relocate the route markers based on the measured route, or remove duplicate finish labels if the endpoint is slightly longer than the final kilometre marking.

You'll want to copy the style of the existing router_markers and paste it on to your new layer in order to display the route markers in an appropriate style (but feel free to tweak the style if you prefer).

  • Manually correct the extent of each map in order to optimise the visible area of the map: