Metadata-Version: 2.4
Name: papermap
Version: 2026.3.1
Summary: papermap is a Python library and CLI tool for creating ready-to-print paper maps.
Keywords: map,maps,paper,cartography,openstreetmap,osm,topography,geodesy,utm,topography,mgrs
Author: Steven van de Graaf
Author-email: Steven van de Graaf <steven@vandegraaf.xyz>
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Dist: click
Requires-Dist: click-default-group
Requires-Dist: fpdf2
Requires-Dist: httpx2
Requires-Dist: pillow
Requires-Dist: gpx>=2026.3.0 ; extra == 'gpx'
Requires-Python: >=3.11, <4
Project-URL: Documentation, https://papermap.readthedocs.io/en/stable/
Project-URL: Changelog, https://papermap.readthedocs.io/en/stable/changelog.html
Project-URL: Source code, https://github.com/sgraaf/papermap
Project-URL: Issues, https://github.com/sgraaf/papermap/issues
Provides-Extra: gpx
Description-Content-Type: text/markdown

<!-- start docs-include-index -->

# papermap

[![PyPI](https://img.shields.io/pypi/v/papermap)](https://img.shields.io/pypi/v/papermap)
[![Supported Python Versions](https://img.shields.io/pypi/pyversions/papermap)](https://pypi.org/project/papermap/)
[![CI](https://github.com/sgraaf/papermap/actions/workflows/ci.yml/badge.svg)](https://github.com/sgraaf/papermap/actions/workflows/ci.yml)
[![Test](https://github.com/sgraaf/papermap/actions/workflows/test.yml/badge.svg)](https://github.com/sgraaf/papermap/actions/workflows/test.yml)
[![Documentation Status](https://readthedocs.org/projects/papermap/badge/?version=latest)](https://papermap.readthedocs.io/en/latest/?badge=latest)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/11822/badge)](https://www.bestpractices.dev/projects/11822)

*papermap* is a Python library and CLI tool for creating ready-to-print paper maps.

<!-- end docs-include-index -->

## Installation

<!-- start docs-include-installation -->

*papermap* is available on [PyPI](https://pypi.org/project/papermap/). Install with [uv](https://docs.astral.sh/uv/) or your package manager of choice:

```sh
uv add papermap
```

<!-- end docs-include-installation -->

## Documentation

Check out the [*papermap* documentation](https://papermap.readthedocs.io/en/stable/) for the [User's Guide](https://papermap.readthedocs.io/en/stable/usage.html), [API Reference](https://papermap.readthedocs.io/en/stable/api.html) and [CLI Reference](https://papermap.readthedocs.io/en/stable/cli.html).

## Usage

<!-- start docs-include-usage -->

*papermap* can be used both in your own applications as a package, as well as a CLI tool.

### As a Library

#### Basic Usage

Create a simple portrait-oriented, A4-sized map at scale 1:25000:

```python
>>> from papermap import PaperMap
>>> pm = PaperMap(13.75889, 100.49722)  # Bangkok, Thailand
>>> pm.render()
>>> pm.save("Bangkok.pdf")
```

#### Custom Size and Orientation

Create a landscape-oriented, A3-sized map with grid overlay:

```python
>>> from papermap import PaperMap
>>> pm = PaperMap(
...     lat=40.416775,
...     lon=-3.703790,  # Madrid, Spain
...     tile_provider_key="esri-worldtopomap",
...     paper_size="a3",
...     use_landscape=True,
...     scale=50_000,
...     add_grid=True,
... )
>>> pm.render()
>>> pm.save("Madrid.pdf")
```

#### Satellite Imagery

Create a map using satellite imagery:

```python
>>> from papermap import PaperMap
>>> pm = PaperMap(
...     lat=51.5074,
...     lon=-0.1278,  # London, UK
...     tile_provider_key="esri-worldimagery",
...     paper_size="a4",
...     scale=10_000,
... )
>>> pm.render()
>>> pm.save("London_Satellite.pdf")
```

#### Topographic Maps

Create a topographic map for hiking:

```python
>>> from papermap import PaperMap
>>> pm = PaperMap(
...     lat=46.5197,
...     lon=7.9577,  # Mürren, Switzerland
...     tile_provider_key="opentopomap",
...     paper_size="a3",
...     use_landscape=True,
...     scale=25_000,
...     add_grid=True,
...     grid_size=500,  # 500m grid for easier navigation
... )
>>> pm.render()
>>> pm.save("Murren_Topo.pdf")
```

#### High-Resolution Printing

Create a high-resolution map for professional printing:

```python
>>> from papermap import PaperMap
>>> pm = PaperMap(
...     lat=35.6762,
...     lon=139.6503,  # Tokyo, Japan
...     tile_provider_key="openstreetmap",
...     paper_size="a0",  # Large format
...     use_landscape=True,
...     scale=15_000,
...     dpi=600,  # High resolution
...     add_grid=True,
... )
>>> pm.render()
>>> pm.save("Tokyo_HighRes.pdf")
```

#### Using UTM Coordinates

Create a map using UTM coordinates instead of latitude/longitude:

```python
>>> from papermap import PaperMap
>>> from papermap.geodesy import UTMCoordinate
>>> pm = PaperMap.from_utm(
...     UTMCoordinate(
...         easting=500000,
...         northing=4649776,
...         zone=30,
...         hemisphere="N",  # Northern hemisphere
...     ),
...     tile_provider_key="openstreetmap",
...     paper_size="a4",
...     scale=25_000,
...     add_grid=True,
... )
>>> pm.render()
>>> pm.save("UTM_Map.pdf")
```

#### Custom Margins

Create a map with custom margins for binding:

```python
>>> from papermap import PaperMap
>>> pm = PaperMap(
...     lat=-33.8688,
...     lon=151.2093,  # Sydney, Australia
...     tile_provider_key="openstreetmap",
...     paper_size="letter",
...     margin_left=20,  # Extra margin for binding
...     margin_top=10,
...     margin_right=10,
...     margin_bottom=10,
...     scale=20_000,
... )
>>> pm.render()
>>> pm.save("Sydney_Binding.pdf")
```

#### Using API Keys

Some tile providers require API keys. Here's how to use them:

```python
>>> from papermap import PaperMap
>>> pm = PaperMap(
...     lat=37.7749,
...     lon=-122.4194,  # San Francisco, USA
...     tile_provider_key="thunderforest-outdoors",
...     api_key="your_api_key_here",  # Get from thunderforest.com
...     paper_size="a4",
...     use_landscape=True,
...     scale=25_000,
...     add_grid=True,
... )
>>> pm.render()
>>> pm.save("SF_Outdoors.pdf")
```

#### Adding Features (i.e., Markers, Lines and Polygons)

Overlay GeoJSON-style geometries (points, lines, polygons) on top of the base map. Features are styled per call, rendered above the base map but below the grid, and clipped to the map area. You can also add a raw GeoJSON dict (or any object implementing the `__geo_interface__` protocol) via `add_geojson()`.

```python
>>> from papermap import PaperMap
>>> pm = PaperMap(lat=40.7128, lon=-74.0060, scale=25_000)
>>> # A red dot at the map centre.
>>> pm.add_circle_marker(40.7128, -74.0060, radius=3, fill_color="#f00")
>>> # A blue route.
>>> pm.add_line(
...     [(40.7100, -74.0100), (40.7150, -74.0050), (40.7200, -74.0000)],
...     stroke_color="#00f",
...     stroke_width=1.0
... )
>>> # A semi-transparent green region from a GeoJSON Polygon.
>>> pm.add_geojson(
...     {
...         "type": "Polygon",
...         "coordinates": [
...             [
...                 [-74.020, 40.710],
...                 [-74.000, 40.710],
...                 [-74.000, 40.720],
...                 [-74.020, 40.720],
...                 [-74.020, 40.710]
...             ]
...         ]
...     },
...     style={"fill_color": "#0f0", "opacity": 0.3}
... )
>>> pm.render()
>>> pm.save("NYC_Annotated.pdf")
```

#### Creating a Map From Features

To fit the map around features you already have, use `PaperMap.from_features()`. It centres the map on the features' bounding box and pre-populates them so they will be drawn on `render()`. `PaperMap.from_geojson()` and `PaperMap.from_gpx()` do the same for a GeoJSON file/dict or a GPX file (or any object exposing `__geo_interface__`, such as a [`gpx.GPX`](https://pypi.org/project/gpx/) instance). GeoJSON `Point`/`MultiPoint` become `CircleMarker`, `LineString`/`MultiLineString` become `Line`, and `Polygon`/`MultiPolygon` become `Polygon`; GPX waypoints become circle markers, routes become lines, and each segment of each track becomes its own line. An optional `style` dict provides default styling for every parsed feature, overridden by per-feature [`simplestyle-spec`](https://github.com/mapbox/simplestyle-spec) properties.

Reading GPX files and/or parsing GPX objects requires the optional `gpx` package. Install it with `uv add --extra gpx papermap`.

```python
>>> from papermap import PaperMap
>>> from papermap.features import CircleMarker, Line
>>> pm = PaperMap.from_features(
...     CircleMarker(40.7484, -73.9857, fill_color="#f00"),  # Empire State Building
...     CircleMarker(40.7128, -74.0060, fill_color="#f00"),  # NYC City Hall
...     Line(
...         [(40.7484, -73.9857), (40.7128, -74.0060)],
...         stroke_color="#00f",
...         stroke_width=1.0,
...     ),
...     scale=25_000,
... )
>>> pm.render()
>>> pm.save("NYC_Landmarks.pdf")
```

#### Auto-scaling to Features

Pass `auto_scale=True` to `from_features()`, `from_geojson()`, or `from_gpx()` to skip picking a scale by hand: the scale is computed so the features fit within the printable area, then snapped up to the nearest common cartographic scale (1:1 000, 1:2 500, 1:5 000, 1:10 000, 1:25 000, 1:50 000, …). Use `padding` (mm per side, default `5.0`) to control how much breathing room is left around the features:

```python
>>> from papermap import PaperMap
>>> pm = PaperMap.from_gpx("hike.gpx", auto_scale=True, padding=10)
>>> pm.render()
>>> pm.save("Hike.pdf")
```

For more options and details, see the [API Reference](https://papermap.readthedocs.io/en/stable/api.html#papermap.papermap.PaperMap).

### As a CLI Tool

#### Basic Usage

Create a simple portrait-oriented, A4-sized map:

```shell
$ papermap latlon -- 13.75889 100.49722 Bangkok.pdf
```

#### Custom Size and Orientation

Create a landscape-oriented, A3-sized map with grid overlay:

```shell
$ papermap latlon \
    --tile-provider esri-worldtopomap \
    --paper-size a3 \
    --landscape \
    --scale 50000 \
    --grid \
    -- 40.416775 -3.703790 Madrid.pdf
```

#### Satellite Imagery

Create a map using satellite imagery:

```shell
$ papermap latlon \
    --tile-provider esri-worldimagery \
    --scale 10000 \
    -- 51.5074 -0.1278 London_Satellite.pdf
```

#### Topographic Maps

Create a topographic map for hiking:

```shell
$ papermap latlon \
    --tile-provider opentopomap \
    --paper-size a3 \
    --landscape \
    --scale 25000 \
    --grid \
    --grid-size 500 \
    -- 46.5197 7.9577 Murren_Topo.pdf
```

#### High-Resolution Printing

Create a high-resolution map for professional printing:

```shell
$ papermap latlon \
    --tile-provider openstreetmap \
    --paper-size a0 \
    --landscape \
    --scale 15000 \
    --dpi 600 \
    --grid \
    -- 35.6762 139.6503 Tokyo_HighRes.pdf
```

#### Using UTM Coordinates

Create a map using UTM coordinates:

```shell
$ papermap utm \
    --tile-provider openstreetmap \
    --paper-size a4 \
    --scale 25000 \
    --grid \
    -- 500000 4649776 30 N UTM_Map.pdf
```

#### From a GPX File

Render a paper map from a `.gpx` file, auto-scaled to fit the waypoints, routes, and tracks (requires `uv add --extra gpx papermap`):

```shell
$ papermap gpx --auto-scale --padding 10 -- hike.gpx Hike.pdf
```

Apply default styling to every parsed feature with shared `--stroke`, `--fill`, `--opacity`, and `--marker-radius` flags (also available on the `geojson` sub-command). Per-feature GeoJSON [`simplestyle-spec`](https://github.com/mapbox/simplestyle-spec) properties still take precedence:

```shell
$ papermap gpx \
    --auto-scale \
    --stroke '#c00' \
    --stroke-width 1.5 \
    --marker-radius 3 \
    -- hike.gpx Hike.pdf
```

#### Custom Margins

Create a map with custom margins for binding:

```shell
$ papermap latlon \
    --tile-provider openstreetmap \
    --paper-size letter \
    --margin-left 20 \
    --margin-top 10 \
    --margin-right 10 \
    --margin-bottom 10 \
    --scale 20000 \
    -- -33.8688 151.2093 Sydney_Binding.pdf
```

#### Using API Keys

Use tile providers that require API keys:

```shell
$ papermap latlon \
    --tile-provider thunderforest-outdoors \
    --api-key "your_api_key_here" \
    --paper-size a4 \
    --landscape \
    --scale 25000 \
    --grid \
    -- 37.7749 -122.4194 SF_Outdoors.pdf
```

For more options and details, see the [CLI Reference](https://papermap.readthedocs.io/en/stable/cli.html).

<!-- end docs-include-usage -->
