Translating QCT (Quick Chart) Map Files

Convert Memory Map QCT files into PNG and GeoTIFF so old Ordnance Survey maps can be reused outside proprietary software.
spatial
Published

30 Dec 2021 12:00

I’ve got a stash of old (2004 vintage) UK Ordnance Survey maps. They are really works of art and the folk at the Ordnance Survey should be commended on the level of detail embedded in these maps. There’s just one small snag: the maps are in a rather obscure format. The proprietary Quick Chart (.qct extension) format is intended for use with Memory Map navigation software. If you want to use these maps for other purposes then you are stuck.

Existing Options

If you want to open these files then you can get yourself a copy of the Memory Map software. I also found that the GPXSee utility for mapping GPS paths would open these files. There are probably other options, but I could not find one that would allow me to convert these files as a batch and (importantly) do so for free (or at a nominal cost).

DIY

It looked like I’d have to roll my own solution. I found the details of the Quick Chart format which were reverse-engineered by Craig Shelley. The draft specification is detailed but complicated.

With a bit more research I found a Python script (presumably based on the format specification), qct.py, published as a Gist by @mgd020 which reads the QCT files and then exported them as PNG files. I also found something similar implemented in C++. This would probably be a lot faster, but I couldn’t get it to compile (admittedly I didn’t try very hard), so I focused on the Python script.

Updates & Improvements

The qct.py script was written for Python 2, so I updated it to work with Python 3. I also added the capability of exporting GeoTIFF files. The updated script can be found here.

Example

Let’s try out the resulting script on the Marlborough & Savernake Forest (ID 157, 1:25000) map.

qctconvert 157-marlborough-savernake-forest.qct

This results in two new files, a PNG and a GeoTIFF.

27M 157-marlborough-savernake-forest.qct
48M 157-marlborough-savernake-forest.png
47M 157-marlborough-savernake-forest.tif

The GeoTIFF file uses deflate lossless compression with a horizontal differencing predictor, which results in a file size similar to that of the PNG file.

Below is a small region of the PNG. Click on the image to open it in a new tab. Lots of interesting features in this very small piece of England:

Topographic map of vicinity of Avebury

The GeoTIFF uses the OSGB36 datum (EPSG:4277), which is tailored specifically for maps of the UK.

Portable Docker Image

I came back to this project a few years later and had Python dependency problems when running the script. I was in a rush, leaving on a camping trip and wanting to take along a cropped PNG image of the area I was going to be in. Fighting with Python dependencies at the last minute was a buzz-kill.

Now, safely back from the trip, I want to create a future-proof solution. Docker FTW! 🚀 My initial implementation used a ubuntu:24.04 base image.

docker image ls datawookie/qctconvert:latest
IMAGE                          ID             DISK USAGE   CONTENT SIZE
datawookie/qctconvert:latest   c04463abc898        338MB             0B

Chunky! Functionality is fine but that image is a beast. Why’s it so damn big? The ubuntu:24.04 base image is around 80 MB unpacked (less than 30 MB compressed), but it drags in lots of extra baggage. The main culprit for the image bloat is GDAL (C library for geospatial data). Add to that the GDAL Python bindings and other dependencies that come along for the ride.

The only way to make a meaningful dent in the image size is to use a smaller base image. We could use an earlier version of Ubuntu, but that would hardly move the needle. These are the other options I considered:

  • python:3.12-alpine — This pins the Python version to 3.12. The version I was working with when I first wrote this post was more likely 3.10, but this seems like a reasonable compromise. The alpine suffix indicates that the image is built on top of Alpine Linux, so it should be fairly minimal. Not locked to a specific Alpine release though. It would rely on the GDAL dependencies playing nicely on Alpine.
  • python:3.12-slim — Also pins the Python version, but is based on a trimmed-down Debian image. Less likely to have compatibility issues with GDAL, but larger than the Alpine-based image. Not locked to a specific Debian release either.
  • alpine:3.21 — Pinned to Alpine 3.21 release. This is a smaller base image, but it doesn’t include Python. It would give more granular control over what goes into the final image, but probably more work to set up.

Ultimately I settled on the third option because, despite expectations, it actually ended up being a relatively straightforward solution. The first option, which seemed to be the simplest, pulled in the Alpine Python package while installing the py3-gdal package, so the image ended up with two Python interpreters. 🤮

The Dockerfile based on alpine:3.21 installs Python and the required dependencies on top of the Alpine base. The resulting image is substantially smaller.

docker image ls datawookie/qctconvert:latest
IMAGE                          ID             DISK USAGE   CONTENT SIZE
datawookie/qctconvert:latest   989fb0de7824        239MB             0B

It’s still not what I’d consider a “slim” image, but it’s a lot better. And it works.

The syntax is a little awkward compared to the example above, but it reliably does the job right now and will work in the future on any machine with Docker installed.

docker run --rm -v $(pwd):/data datawookie/qctconvert:latest 009-exmoor.qct

The command assumes that the .qct file is in the current working directory. The -v argument mounts the current working directory as /data on the container so that the process can read the .qct file as well as write the .png and .tif files. The --rm argument ensures that the container is deleted after the job finishes.