Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pnpm-lock.yaml
**/manifest.json
**/translations.json
**/translations.json
**/contrib/pmtiles-server/wwwroot/
7 changes: 7 additions & 0 deletions dev/docker/csp.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
directives:
child-src:
- "'self'"
- 'blob:'
worker-src:
- "'self'"
- 'blob:'
connect-src:
- "'self'"
- 'blob:'
- 'https://tile.openstreetmap.org/'
- 'https://protomaps.github.io/'
- 'http://localhost:9205/'
default-src:
- "'none'"
font-src:
Expand Down
66 changes: 61 additions & 5 deletions packages/web-app-maps/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,79 @@ OpenCloud Maps app can display `.gpx` files and show geo location data for singl

In `apps.yaml` you can override configuration like this:

### Raster tiles (default)

```yaml
maps:
config:
tileLayerUrlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
tileLayerAttribution: '<a href="https://openstreetmap.org">OpenStreetMap</a>'
tileLayerOptions:
maxZoom: 19
attribution: '© OpenStreetMap'
```

`tileLayerUrlTemplate` and `tileLayerOptions` have the above as default values, you can override it if you want to use another tile layer provider.
`tileLayerUrlTemplate` defaults to OpenStreetMap. `tileLayerAttribution` defaults to OpenStreetMap when using raster tiles.

To enable seamless integration of traffic to the OpenStreetMap servers, the Content Security Policy of OpenCloud has to be adopted.
### PMTiles (vector tiles)

In the file `csp.yaml`, add the entry `- 'https://tile.openstreetmap.org/'` in the `img-src:` section.
For vector tile maps using [PMTiles](https://protomaps.com/docs/pmtiles), point `tileLayerUrlTemplate` to a `.pmtiles` file:

Please respect the work of [OpenStreetMap](https://openstreetmap.org) and read the [Tile Usage Policy](https://operations.osmfoundation.org/policies/tiles/).
```yaml
maps:
config:
tileLayerUrlTemplate: 'https://example.com/tiles/region.pmtiles'
tileLayerAttribution: '<a href="https://protomaps.com">Protomaps</a> | <a href="https://openstreetmap.org">OpenStreetMap</a>'
```

Vector tile labels require font glyphs. By default, fonts are loaded from `protomaps.github.io`. To use self-hosted fonts instead, set `tileLayerGlyphs`:

```yaml
maps:
config:
tileLayerUrlTemplate: 'https://example.com/tiles/region.pmtiles'
tileLayerGlyphs: 'https://example.com/fonts/{fontstack}/{range}.pbf'
```

#### Self-hosting tiles

A self-contained tile server is available in [`contrib/pmtiles-server/`](contrib/pmtiles-server/). A single `docker compose up -d` downloads a world map (~120 GB), fonts, and starts serving them.

### Full style override

For complete control over the map style, provide a URL to a [MapLibre Style JSON](https://maplibre.org/maplibre-style-spec/):

```yaml
maps:
config:
mapStyle: 'https://your-server.example.com/style.json'
```

### Content Security Policy

To enable seamless integration of traffic to tile servers, the Content Security Policy of OpenCloud has to be adopted.

In the file `csp.yaml`, add the tile server URL(s) to the `connect-src:` section. MapLibre also requires web workers, so `worker-src` and `child-src` must allow `blob:`:

```yaml
directives:
worker-src:
- "'self'"
- 'blob:'
child-src:
- "'self'"
- 'blob:'
connect-src:
- "'self'"
- 'blob:'
- 'https://tile.openstreetmap.org/'
```

When using PMTiles with the default font configuration, also add `https://protomaps.github.io/` to `connect-src`.

## Privacy Notice

The rendered maps are loaded from OpenStreetMap (by default). This allows them to do at least some basic kind of tracking, simply because files are loaded from their servers by your browser.

When using PMTiles with the default font configuration, font glyphs are loaded from `protomaps.github.io`.

Please respect the work of [OpenStreetMap](https://openstreetmap.org) and read the [Tile Usage Policy](https://operations.osmfoundation.org/policies/tiles/).
65 changes: 65 additions & 0 deletions packages/web-app-maps/contrib/pmtiles-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# PMTiles tile server

A self-contained tile server that serves [PMTiles](https://protomaps.com/docs/pmtiles) vector tiles and the font glyphs needed for label rendering.

## Quick start

```bash
docker compose up -d
```

On first run an init container downloads a world map from [Protomaps](https://maps.protomaps.com/builds/) and fonts into `wwwroot/`, then a static file server starts at `http://localhost:9205`.

### What gets downloaded

| Asset | Size | Source |
| ----------------------------- | ------- | ------------------------------------------------------------------------- |
| World PMTiles (today's build) | ~120 GB | [protomaps.com/builds](https://maps.protomaps.com/builds/) |
| Font glyphs (Noto Sans) | ~14 MB | [protomaps/basemaps-assets](https://github.com/protomaps/basemaps-assets) |

The download uses [aria2](https://aria2.github.io/) with 16 connections. If the container is stopped mid-download, the partial `.tmp` file is kept and the download **resumes automatically** on the next `docker compose up -d`.

You can also skip the download entirely by placing your own `.pmtiles` file into `wwwroot/` before starting — the script detects it and only creates the `latest.pmtiles` symlink.

## What gets served

- **PMTiles**: `http://localhost:9205/latest.pmtiles`
- **Fonts**: `http://localhost:9205/fonts/{fontstack}/{range}.pbf`

## OpenCloud configuration

### apps.yaml

Configure the maps extension to use the local tile server:

```yaml
maps:
config:
tileLayerUrlTemplate: 'http://localhost:9205/latest.pmtiles'
tileLayerAttribution: '<a href="https://protomaps.com">Protomaps</a> | <a href="https://openstreetmap.org">OpenStreetMap</a>'
tileLayerGlyphs: 'http://localhost:9205/fonts/{fontstack}/{range}.pbf'
```

### csp.yaml

Add `http://localhost:9205/` to the `connect-src` directive:

```yaml
directives:
connect-src:
- "'self'"
- 'http://localhost:9205/'
```

After changing either file, restart OpenCloud for the new configuration to take effect.

## Updating tiles

Remove the existing tiles and restart:

```bash
rm wwwroot/*.pmtiles wwwroot/latest.pmtiles
docker compose up -d
```

This downloads a fresh build for the current day.
21 changes: 21 additions & 0 deletions packages/web-app-maps/contrib/pmtiles-server/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
services:
download:
image: alpine:3.21
command: ['/bin/sh', '/download.sh']
volumes:
- ./wwwroot:/wwwroot
- ./download.sh:/download.sh:ro

server:
image: joseluisq/static-web-server:2
depends_on:
download:
condition: service_completed_successfully
environment:
SERVER_ROOT: /public
SERVER_CORS_ALLOW_ORIGINS: '*'
SERVER_CORS_ALLOW_HEADERS: 'range, if-range, origin, content-type'
volumes:
- ./wwwroot:/public:ro
ports:
- '9205:80'
66 changes: 66 additions & 0 deletions packages/web-app-maps/contrib/pmtiles-server/download.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env sh
set -eu

DIR="/wwwroot"

# --- Check if everything is already present ---

NEED_PMTILES=true
NEED_FONTS=true

existing_tmp=$(find "$DIR" -maxdepth 1 -name "*.pmtiles.tmp" -print -quit 2>/dev/null || true)
existing=$(find "$DIR" -maxdepth 1 -name "*.pmtiles" ! -name "latest.pmtiles" ! -name "*.tmp" -print -quit 2>/dev/null || true)

if [ -z "$existing_tmp" ] && [ -n "$existing" ]; then
echo "PMTiles file already exists: $(basename "$existing")"
ln -sf "$(basename "$existing")" "$DIR/latest.pmtiles"
NEED_PMTILES=false
fi

if [ -d "$DIR/fonts/Noto Sans Regular" ]; then
echo "Font assets already exist."
NEED_FONTS=false
fi

if [ "$NEED_PMTILES" = false ] && [ "$NEED_FONTS" = false ]; then
echo "Nothing to download."
exit 0
fi

# --- Install tools only when we actually need to download ---

apk add --no-cache aria2 curl tar

# --- PMTiles ---

if [ "$NEED_PMTILES" = true ]; then
if [ -n "$existing_tmp" ]; then
# Resume a previous interrupted download
base="$(basename "$existing_tmp" .tmp)"
echo "Resuming PMTiles download: $base"
aria2c -c -x 16 -s 16 -d "$DIR" -o "${base}.tmp" \
"https://build.protomaps.com/${base}"
mv "$existing_tmp" "$DIR/$base"
ln -sf "$base" "$DIR/latest.pmtiles"
else
# Start a fresh download for today's build
TODAY="$(date +%Y%m%d)"
TARGET="$DIR/$TODAY.pmtiles"
TMP="$TARGET.tmp"
echo "Downloading $TODAY.pmtiles (~120 GB)..."
aria2c -c -x 16 -s 16 -d "$DIR" -o "$TODAY.pmtiles.tmp" \
"https://build.protomaps.com/$TODAY.pmtiles"
mv "$TMP" "$TARGET"
ln -sf "$TODAY.pmtiles" "$DIR/latest.pmtiles"
echo "Done: $TARGET"
fi
fi

# --- Fonts ---

if [ "$NEED_FONTS" = true ]; then
echo "Downloading font assets (~14 MB)..."
curl -sL https://github.com/protomaps/basemaps-assets/archive/refs/heads/main.tar.gz \
| tar xz -C "$DIR" --strip-components=1 basemaps-assets-main/fonts
echo "Done: fonts extracted to $DIR/fonts/"
fi
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
21 changes: 0 additions & 21 deletions packages/web-app-maps/leaflet.d.ts

This file was deleted.

8 changes: 4 additions & 4 deletions packages/web-app-maps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
"test:unit": "NODE_OPTIONS=--unhandled-rejections=throw vitest"
},
"devDependencies": {
"@types/leaflet": "^1.9.21",
"@types/leaflet-gpx": "^1.3.8",
"vue": "^3.4.21",
"vue3-gettext": "^2.4.0"
},
"dependencies": {
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"@protomaps/basemaps": "^5.7.0",
"@tmcw/togeojson": "^6.0.0",
"maplibre-gl": "^5.1.1",
"pmtiles": "^4.0.1",
"zod": "^4.0.17"
},
"peerDependencies": {
Expand Down
Loading