Vector maps built on OpenStreetMap open data: 3D buildings, globe view, terrain, light and dark themes. Embed them on a website with a single line or plug them into an app as a MapLibre style. No API key required.
1. Iframe: the easiest way
Move the map in the preview and the code below updates automatically.
/embed URL parameters
| Parameter | Value |
|---|---|
lat, lon, z | Center and zoom level (0–20) |
bearing, pitch | Bearing in degrees and pitch (0–75) |
theme | light, dark or auto (default: follows the system theme) |
lang | Language of labels and buttons: en or ru. Without it, the viewer's browser language is used |
marker | 1 for a marker in the center, or lat,lon |
title | Marker label |
3d, controls, scroll, link | 1/0: tilt, buttons, scroll zoom without Ctrl, the “Open map” link |
Controlling the map from your page (postMessage)
const frame = document.querySelector('iframe').contentWindow
frame.postMessage({ type: 'vmaps:flyTo', center: [30.3158, 59.9391], zoom: 15 }, '*')
frame.postMessage({ type: 'vmaps:setTheme', theme: 'dark' }, '*')
frame.postMessage({ type: 'vmaps:addMarker', lngLat: [30.3158, 59.9391], title: 'Hermitage' }, '*')
frame.postMessage({ type: 'vmaps:clearMarkers' }, '*')
// The map reports moves and clicks
window.addEventListener('message', (e) => {
if (e.data?.type === 'vmaps:click') console.log('click', e.data.lngLat)
if (e.data?.type === 'vmaps:moveend') console.log('center', e.data.center, 'zoom', e.data.zoom)
})
2. JS SDK
A thin wrapper around MapLibre GL JS that sets up styles, theme and 3D. It returns a regular maplibregl.Map.
<div id="map" style="height: 480px"></div>
<script src="https://map.vulpine.email/sdk.js"></script>
<script>
VulpineMaps.createMap('map', {
theme: 'auto', // 'light' | 'dark' | 'auto'
lang: 'auto', // 'en' | 'ru' | 'auto' (browser language)
center: [37.6175, 55.752], // [longitude, latitude]
zoom: 15,
pitch: 55,
}).then(function (map) {
VulpineMaps.addMarker(map, [37.6175, 55.752], { popup: 'Red Square' })
// map.setTheme('dark')
})
</script>
As an ES module
<script type="module">
import { createMap, addMarker, maplibregl } from 'https://map.vulpine.email/sdk.mjs'
const map = createMap('map', { theme: 'dark', center: [30.3158, 59.9391], zoom: 14 })
addMarker(map, [30.3158, 59.9391], { popup: 'Hermitage' })
</script>
3. Your own MapLibre: styles
If you already use MapLibre (plain web, React, Vue), all you need is a style URL.
| Style | URL |
|---|---|
| Light, Russian labels | https://map.vulpine.email/styles/light.json |
| Dark, Russian labels | https://map.vulpine.email/styles/dark.json |
| Light, English labels | https://map.vulpine.email/styles/light-en.json |
| Dark, English labels | https://map.vulpine.email/styles/dark-en.json |
import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
const map = new maplibregl.Map({
container: 'map',
style: 'https://map.vulpine.email/styles/light-en.json',
center: [37.6175, 55.752],
zoom: 14,
})
// Switch the theme without reloading
map.setStyle('https://map.vulpine.email/styles/dark-en.json', { diff: true })
// 3D terrain
map.setTerrain({ source: 'dem', exaggeration: 1.4 })
4. Mobile apps
The styles work with MapLibre Native: Android, iOS, Flutter, React Native. Not every Native version supports the globe and sky; the map still works without them.
Android (Kotlin)
// build.gradle: implementation("org.maplibre.gl:android-sdk:<version>")
mapView.getMapAsync { map ->
map.setStyle("https://map.vulpine.email/styles/light-en.json")
map.cameraPosition = CameraPosition.Builder()
.target(LatLng(55.752, 37.6175)).zoom(14.0).tilt(45.0).build()
}
iOS (Swift)
import MapLibre
let mapView = MLNMapView(frame: view.bounds,
styleURL: URL(string: "https://map.vulpine.email/styles/dark-en.json"))
mapView.setCenter(CLLocationCoordinate2D(latitude: 55.752, longitude: 37.6175),
zoomLevel: 14, animated: false)
view.addSubview(mapView)
Flutter / React Native
// Flutter (maplibre_gl)
MapLibreMap(styleString: 'https://map.vulpine.email/styles/light-en.json', initialCameraPosition: ...)
// React Native (@maplibre/maplibre-react-native)
<MapView mapStyle="https://map.vulpine.email/styles/light-en.json" />
In a WebView, the iframe page is the simplest option: https://map.vulpine.email/embed?lat=55.752&lon=37.6175&z=14&lang=en.
5. Direct data access
| What | URL |
|---|---|
| Vector tiles (MVT, Protomaps schema) | https://map.vulpine.email/tiles/planet/{z}/{x}/{y}.mvt |
| TileJSON | https://map.vulpine.email/tiles/planet.json |
| Terrain (Terrarium PNG) | https://map.vulpine.email/dem/{z}/{x}/{y}.png |
| Search | https://map.vulpine.email/api/search?q=Hermitage&lat=59.94&lon=30.31&limit=5&lang=en |
| Reverse geocoding | https://map.vulpine.email/api/reverse?lat=55.752&lon=37.6175&lang=en |
Tiles and styles are served with CORS *. Search is limited to 5 requests per second per IP. With lang=en, search returns English names where OpenStreetMap has them.
Data and licenses
Everything is built on open data and open-source software. Keep the attribution on the map when embedding it; the iframe and the SDK show it automatically.
| Map data | © OpenStreetMap contributors, ODbL |
| Tile schema and build | Protomaps Basemap (Natural Earth, OSM) |
| Terrain | Mapzen Terrain Tiles (SRTM, GMTED, ETOPO1 and others) |
| Search | Photon on OpenStreetMap data |
| Engine | MapLibre GL JS, BSD-3-Clause |
| Fonts | Noto Sans, SIL Open Font License |