Martin Yeo

How I made across-Devon postbox orienteering

Why?

It is always possible to plan your own course on an A-Z or Ordnance Survey map, using the honesty system to check course completion; but we all know it’s much more enjoyable when someone hands us a specialised map, with a prepared course, and we get the reassurance of visiting actual controls on our run.

Technology makes this authentic experience increasingly accessible so that low-key events can now have some of the quality previously reserved for high-calibre events:

Removing the need for specific course setting would bring the authentic experience to even the most informal orienteering - like individual training or challenges between friends - and also make more frequent orienteering possible without upping the resources needed.

How?

Create a geographically massive score event in MapRun, with the Start Anywhere feature enabled, so that it would take many runs to experience the whole area - an orienteer could enjoy this single course many times before getting bored.

Of course, there are likely to be only a few start locations that will yield high scores, but that’s only one of the ways to enjoy such a course (see the Ideas on the main page).

Positioning controls

Don’t want to be positioning manually, as that defeats the reduced-effort philosophy. Need to use some existing location data…

OpenStreetMap

OpenSteetMap includes precise point locations for many municipal objects such as postboxes, bus stops and benches. The large international community means it is relatively easy to query OSM for this data, such as this example for all postboxes in Devon.

Postboxes

Postboxes have long been used as controls in street orienteering, which is why OpenOrienteeringMap includes an option for automatically creating controls at every postbox location within the mapped area (read more). This is ideal because it means orienteers can use OpenOrienteeringMap to both produce a map of their chosen sub-area, and also populate it with the controls.

OpenOrienteeringMap’s postbox data comes from Matthew Somerville’s postbox finder, which itself feeds from OpenStreetMap, but also other sources which unfortunately means occasional discrepancies with the information I’m fetching via OpenStreetMap queries.

Alternative features

E.g. bus stops. Don’t get the convenience of OpenOrienteeringMap’s automation, but orienteers would still have several other options for producing their map:

Creating the MapRun

MapRun needs a KML file listing the controls and their coordinates. While a KML file can be downloaded from OpenStreetMap quering services (such as Overpass), this isn’t enough since the controls need to be numbered using a <name> element, and that isn’t present.

So I used the script below to extract what I needed from an Overpass query output, and place it in a correctly formatted KML file, including S1 and F1 controls (positioned according to the Start Anywhere notes).

Then it was a case of following the MapRun course creation guidelines, uploading the just-created KML file. A KMZ file was also needed, so Matt Atkins produced a ‘token’ one direct from OpenOrienteeringMap, which covered a blank area of Dartmoor; MapRun uses OpenStreetMap as the background for the area outside the KMZ map (i.e. most of Devon). Specific MapRun settings:

KML creation script

from copy import deepcopy
from urllib import parse as url_parse
from xml.etree import ElementTree

import requests

# 57538 = Devon.
# Use https://www.openstreetmap.org/relation/57538 to search for desired area.
relation_id = 57538

query_lines = [
    f'area(id:36000{relation_id})',
    'node["amenity"="post_box"](area)',
    'out',
    '',
]
query = url_parse.quote(";".join(query_lines))
url = "https://overpass-api.de/api/interpreter?data=" + query
response = requests.get(url)

source_xml = ElementTree.fromstring(response.content)

output_kml = ElementTree.Element(
    "kml",
    attrib=dict(xmlns="http://earth.google.com/kml/2.0")
)
output_content = ElementTree.SubElement(output_kml, "Document")

postboxes = source_xml.findall("node")
for control_number, control_details in enumerate(postboxes, start=1):
    placemark = ElementTree.SubElement(output_content, "Placemark")

    name_ = ElementTree.SubElement(placemark, "name")
    name_.text = str(control_number)

    point = ElementTree.SubElement(placemark, "Point")

    lon = control_details.attrib["lon"]
    lat = control_details.attrib["lat"]
    coordinates = ElementTree.SubElement(point, "coordinates")
    coordinates.text = f"{lon},{lat}"

# Need arbitrary start-finish controls, even for the start-anywhere format.
#  Create by duplicating first control.
for new_name, position in [("F1", len(output_content)), ("S1", 0)]:
    new_control = deepcopy(output_content[0])
    name_element = new_control.find("name")
    name_element.text = new_name
    output_content.insert(position, new_control)

ElementTree.ElementTree(output_kml).write(f"postboxes_in_{relation_id}.kml")