About Mapterhorn
Have you heard of the geospatial project Mapterhorn?
Mapterhorn is an open data project that publishes terrain data. It creates terrain tiles from various open data sources, such as ESA’s Copernicus DEM and Switzerland’s swissALTI3D, and distributes them in the PMTiles format. The project is led by Oliver (formerly at MapLibre).
I also introduced Mapterhorn as a project to watch in my presentations at FOSS4G Hokkaido 2025 and FOSS4G Japan 2025.
Dataset Overview
Mapterhorn creates terrain tiles by combining multiple open data sources.
Global Data
| Data Source | Resolution | Zoom Level | Notes |
|---|---|---|---|
| Copernicus GLO-30 | 30m | z0-z12 | ESA Global DEM |
The global data is based on ESA’s Copernicus GLO-30 model, covering the entire world up to z12.
High-Resolution Data
In addition to global data, Mapterhorn also provides high-resolution data primarily using open DEM/LiDAR from European countries. For Switzerland specifically, swisstopo's swissALTI3D is used, which offers terrain data at a resolution of 0.5m.
Japan Data
Update (Dec 2025)): High-resolution data for Japan has been added.
✅ Japan, country-wide, 1 m, 5 m, 10 m
Add sources jp*: Japan, 1m, 5m, and 10m
Advance Preparation
Execution environment
- node v24.4.1
- npm v11.4.2
MapLibre GL JS Starter
Fork or download the MapLibre GL JS starter to your local environment and run it.
https://github.com/mug-jp/maplibregljs-starter
maplibregljs-starter
├── dist
│ └── index.html
├── img
├── src
│ ├── main.ts
│ ├── style.css
│ └── vite-env.d.ts
├── README.md
├── LICENSE
├── index.html
├── package-lock.json
├── package.json
├── tsconfig.json
└── vite.config.ts
Install the package
npm install
Install PMTiles as well
npm install pmtiles
package.json
{
"name": "maplibregljs-starter",
"version": "4.5.0",
"description": "",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"keywords": [],
"author": "MapLibre User Group Japan",
"license": "ISC",
"devDependencies": {
"typescript": "^5.5.2",
"vite": "^5.3.2"
},
"dependencies": {
"maplibre-gl": "^4.5.0",
"pmtiles": "^4.3.0"
}
}
Creating the Map Application
Display the Mapterhorn terrain data. Modify src/main.ts.
import './style.css';
import 'maplibre-gl/dist/maplibre-gl.css';
import maplibregl from 'maplibre-gl';
import { Protocol } from 'pmtiles';
const protocol = new Protocol({ metadata: true });
maplibregl.addProtocol('mapterhorn', async (params, abortController) => {
const [z, x, y] = params.url.replace('mapterhorn://', '').split('/').map(Number);
const name = z <= 12 ? 'planet' : `6-${x >> (z - 6)}-${y >> (z - 6)}`;
const url = `pmtiles://https://download.mapterhorn.com/${name}.pmtiles/${z}/${x}/${y}.webp`;
const response = await protocol.tile({ ...params, url }, abortController);
if (response['data'] === null) throw new Error(`Tile z=${z} x=${x} y=${y} not found.`);
return response;
});
const map = new maplibregl.Map({
container: 'map',
hash: 'map',
style: {
version: 8,
sources: {
hillshadeSource: {
type: 'raster-dem',
tiles: ['mapterhorn://{z}/{x}/{y}'],
encoding: 'terrarium',
tileSize: 512,
attribution: '<a href="https://mapterhorn.com/attribution">© Mapterhorn</a>'
}
},
layers: [
{
id: 'hillshade',
type: 'hillshade',
source: 'hillshadeSource'
}
]
},
center: [138.7782, 35.3019],
zoom: 10
});
map.addControl(
new maplibregl.NavigationControl({
visualizePitch: true
})
);
Start the local server
npm run dev
Finally, add 3D terrain rendering. Using MapLibre GL JS’s terrain feature enables 3D terrain visualization.
import './style.css';
import 'maplibre-gl/dist/maplibre-gl.css';
import maplibregl from 'maplibre-gl';
import { Protocol } from 'pmtiles';
const protocol = new Protocol({ metadata: true });
maplibregl.addProtocol('mapterhorn', async (params, abortController) => {
const [z, x, y] = params.url.replace('mapterhorn://', '').split('/').map(Number);
const name = z <= 12 ? 'planet' : `6-${x >> (z - 6)}-${y >> (z - 6)}`;
const url = `pmtiles://https://download.mapterhorn.com/${name}.pmtiles/${z}/${x}/${y}.webp`;
const response = await protocol.tile({ ...params, url }, abortController);
if (response['data'] === null) throw new Error(`Tile z=${z} x=${x} y=${y} not found.`);
return response;
});
const map = new maplibregl.Map({
container: 'map',
hash: 'map',
style: {
version: 8,
sources: {
MIERUNEMAP: {
type: 'raster',
tiles: ['https://tile.mierune.co.jp/mierune/{z}/{x}/{y}.png'],
tileSize: 256,
attribution:
"Maptiles by <a href='http://mierune.co.jp/' target='_blank'>MIERUNE</a>, under CC BY. Data by <a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors, under ODbL.",
},
terrainSource: {
type: 'raster-dem',
tiles: ['mapterhorn://{z}/{x}/{y}'],
encoding: 'terrarium',
tileSize: 512,
attribution: '<a href="https://mapterhorn.com/attribution">© Mapterhorn</a>'
}
},
layers: [
{
id: 'MIERUNEMAP',
type: 'raster',
source: 'MIERUNEMAP'
},
{
id: 'hillshade',
type: 'hillshade',
source: 'terrainSource'
}
],
terrain: {
source: 'terrainSource',
exaggeration: 1.5
}
},
center: [138.8016, 35.2395],
zoom: 11,
pitch: 60,
bearing: -20
});
map.addControl(
new maplibregl.NavigationControl({
visualizePitch: true
})
);




Top comments (0)