Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MaUR-O

Crates.io CI

This is a Map Utils project written in Rust for Orienteering.

Side by side comparison of a map rendered by MaUR-O against the ground-truth renderer The ground-truth renderer's output (left) side by side with MaUR-O's own rendering of the same map (right). Map: Valserena, owned by Polisportiva Masi and made by Samuele Curzio and Remo Madella.

It renders OpenOrienteering Mapper .omap/.xmap files to raster images (PNG, BMP, TIFF, JPEG) from the command line, without needing Mapper itself, Qt, or a graphical environment installed. Alongside the renderer, it ships a small pair of benchmarking tools that build and run suites of maps to measure how closely the renderer's output matches a ground-truth reference — useful both for tracking rendering fidelity over time and for spotting real regressions among the noise two rasterizers naturally disagree on.

Table of Contents

Installation

Dependencies:

  • A working cargo/Rust toolchain, version 1.88 or newer (edition 2021). 1.88 is the floor set by the image crate; everything else in the dependency tree asks for less.

That is all it takes to build: no C toolchain, no pkg-config, no -dev package. Drawing text correctly wants one ordinary runtime library on top — see Fonts and fontconfig.

MacOS and Windows have not been tested; this project has only been built and run on Linux so far.

Build everything with:

cargo build --release

The three executables are then at:

Fonts and fontconfig

To render text correctly, MaUR-O wants a fontconfig runtime library — already present on essentially every desktop Linux install:

  • Debian/Ubuntu: apt install libfontconfig1
  • Fedora: dnf install fontconfig
  • Arch Linux: pacman -S fontconfig

It is opened by name when the first map with text is drawn, never linked, so it is a requirement for correct output rather than for building. If it is missing, MaUR-O prints a warning once and carries on.

That fallback matters more than it sounds. fontconfig is what resolves a family name the same way Qt and Mapper do. Without it the job falls to fontdb alone, which picks a different font for a generic family like "Sans Serif" — and the substituted font has different glyph widths and line heights. So the text is not merely set in another typeface, it is drawn in different positions: labels shift, and successive lines of a block drift further apart as the line-height error accumulates. Nothing about the image looks broken, because the geometry is untouched and only the text is wrong.

Concretely, rendering maps/city_sample.omap with and without fontconfig moves 0.66% of all pixels, at a mean error of 499 out of 765 — the scale of black glyphs landing on white paper, not of soft edges. A benchmark run counts every one of those as a real difference rather than as antialiasing (see Antialiasing), so a suite run on a machine without fontconfig reports spurious failures on every map carrying text.

On MacOS, on Windows, and on any other platform without fontconfig, this fallback is what runs.

Usage

map_to_image

Renders a single map to a raster image.

map_to_image [-r px-per-meter] [-f meters] <map-file> [image-file]
Argument Meaning
<map-file> The .omap/.xmap file to render.
[image-file] Where to write the image. The file suffix (.png, .bmp, .tif, .jpg) selects the format; defaults to the map file's name with a .png suffix.
-r, --resolution <N> Pixels per meter on the ground (default 3).
-f, --frame <N> Width of the white frame added on every side, in meters on the ground (default 50).

This is the tool to reach for to just look at a map:

cargo build --release
./target/release/map_to_image maps/forest_sample.omap

A forest orienteering map rendered by map_to_image maps/forest_sample.omap rendered by map_to_image.

It prints the image size, the ground size, and the map scale it read, and exits non-zero if the map cannot be read (2), its geometry cannot be built (3), or the image cannot be saved (4).

create_benchmark

Builds a benchmark archive: it takes a set of maps and a ground-truth renderer, has that renderer draw each map, and packages both together following the naming rules benchmark expects.

create_benchmark [OPTIONS] <renderer> <source>
Option Meaning
<renderer> Path to the ground-truth renderer executable — see below.
<source> Either a folder, searched recursively for .omap/.xmap files, or a single map file, which is instead split into one generated map per symbol on a grid of test objects.
-o, --output <FILE> Archive path to write. Defaults to benchmarks/benchmark_<source name>_<resolution>_px_m.zip; the containing folder is created if needed.
--force Overwrite the output archive if it already exists.
-r, --resolution <N> Resolution of the reference images, in pixels per meter on the ground (default 3).
-f, --frame <N> Width of the white frame added on every side, in meters on the ground (default 50).
--filter <TEXT> Only include maps whose name contains this text.
cargo build --release
./target/release/create_benchmark /path/to/map_to_image maps/ISOM.omap

Generated per-symbol test grids from a create_benchmark run: area fill patterns, line dash groups, rotated text samples, and point symbol rotations A handful of the maps create_benchmark generates when <source> is a single map file: one per symbol, each a grid of test objects at every size and rotation that symbol needs checked.

Ground-truth renderer

This tool needs an external, ground-truth OMap renderer — it does not render the reference images itself, since the whole point is to compare this project's own renderer against an independent one. Any command-line tool that reads a map file and two options (resolution, frame) and writes an image works; the mapper_cmd_rederer fork of OpenOrienteering Mapper's own command line renderer, built from source, is the one this project was benchmarked against.

benchmark

Runs a whole benchmark suite — a zip archive of maps plus reference images — in one pass: renders every map, compares it against its reference, and writes a difference report.

benchmark [OPTIONS] <archive.zip>
Option Meaning
<archive.zip> The benchmark archive, as produced by create_benchmark.
--results <DIR> Where the run's output folder is created (default Results).
--names-only Check and, if needed, correct the archive's file naming without rendering anything.
--tolerance <N> Per-pixel difference (summed over R+G+B, 0–765) below which a pixel does not count as wrong (default 3).
--keep-antialiasing Report every differing pixel as real, instead of separating out ones two rasterizers can legitimately disagree about (see Antialiasing).
--crops <N> How many worst-region crops to save per differing map, 0 for as many as it takes to cover every difference (default 0).
--crop-size <N> Side length of a cropped region, in pixels (default 128).
--zoom <N> Width a cropped region is enlarged to, in pixels (default 512).
--overview <N> Width the whole side-by-side image is scaled down to, in pixels, 0 for full size (default 2000).
--filter <TEXT> Only run maps whose name contains this text.
cargo build --release
./target/release/benchmark benchmarks/benchmark_ISOM_3_px_m.zip

Resolution and frame are not options here: they are always read back out of the archive's own info.txt, written by create_benchmark when the archive was made, so a run can't silently drift from the resolution and frame the reference images were actually drawn at.

Each run gets its own timestamped folder under Results/, holding results.txt (a table of how much each map differs, worst first), predictions/ (the rendered maps), and differences/ (side-by-side images and crops for every map with a real, non-antialiasing difference). Exit codes: 0 success, 1 a usage or archive problem, 2 a map failed to render.

Below are the first entries of the results.txt of a run produced against the ISSprOM 2019 symbol set at 6 pixels per meter: the worst maps are reported at the top, so this is where to look first. For what each column means, see Running a benchmark archive.

   real  antialiasing    wrong  largest  mean error of wrong px  map
-------  ------------  -------  -------  ----------------------  --------------------------------------------------
0.0156%       1.5679%  1.5836%       35             7.56 ± 4.15  079__area_404_Rough_open_land_with_scattered_trees
0.0148%       2.7276%  2.7424%       70            11.05 ± 7.59  076__area_402_Open_land_with_scattered_trees
0.0147%       1.9598%  1.9745%       40             7.93 ± 4.36  107__area_501.3_Paved_area_with_scattered_trees
0.0022%       0.8021%  0.8042%      765           45.77 ± 50.07  131__line_512.1_Bridge__minimum_width
0.0007%       4.4276%  4.4283%      127            17.91 ± 8.16  067__area_308_Marsh

Expected, predicted and diff crop from a real map, showing antialiasing in orange and a real error in red A crop from differences/ on a real map: expected (left), predicted (middle), and the diff (right), where orange is just antialiasing — not a bug — and the red cluster is a real error, a pixel one renderer drew and the other did not.

Implementation Details

MaUR-O reads the OpenOrienteering Mapper .omap/.xmap format and renders it in pure Rust, with a dedicated crate for each concern (XML parsing, path stroking and rasterization, font shaping...). How closely its output matches a ground-truth renderer, how the source is organized, and how the benchmarking tools tell a real rendering bug from the noise two different rasterizers' antialiasing produces, are documented in ImplementationDetails.md.

Known Limitations

No known bugs on the sample maps (maps/city_sample.omap and maps/forest_sample.omap). There are a handful of minor, mostly cosmetic differences on the ISOM/ISSprOM per-symbol benchmarks, and a few genuine placement/shape bugs found on a private dataset of real maps — see bug.md for all of them, with pictures.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages