Mapkern GL JS is a map library for the web. It draws vector tiles, raster tiles and GeoJSON with WebGL2, as a flat map, in 3D over real terrain or as a globe, and it doesn't depend on any other package. You can try it on the website.
Map data © OpenStreetMap contributors, OpenFreeMap and OpenMapTiles
Save this as map.html and open it in a browser. There's nothing to install, no tile server to run and no API key to get:
<!doctype html>
<meta charset="utf-8" />
<title>My map</title>
<style>
html, body, #map { height: 100%; margin: 0 }
</style>
<div id="map"></div>
<script type="module">
import * as mapkern from "https://grangedevgroup-code.github.io/mapkern-gl-js/dist/mapkern.js";
const map = new mapkern.Map({ container: "map", center: [-122.33, 47.61], zoom: 11 });
map.addControl(new mapkern.NavigationControl());
</script>The map comes from the free OpenFreeMap Liberty style. The credit for OpenFreeMap, OpenMapTiles and OpenStreetMap sits behind the (i) button in the corner, and their licenses ask you to leave it there.
This page loads the newest build from the project website, which is handy for trying things out. For a real app, install it.
npm install https://github.com/grangedevgroup-code/mapkern-gl-js/releases/download/v0.1.0/mapkern-gl-0.1.0.tgzimport * as mapkern from "mapkern-gl";
const map = new mapkern.Map({ container: "map", center: [-122.33, 47.61], zoom: 11 });TypeScript types come with it, and Vite, webpack and esbuild need no setup. The container needs a height, or you won't see anything. Without a bundler, copy node_modules/mapkern-gl/dist/mapkern.js next to your page, import it from there and open the page through a local web server, because browsers won't load module files from a file:// address.
In React, create the map in an effect and remove it when the component goes away:
function MapView({ center, zoom }) {
const container = useRef(null);
useEffect(() => {
const map = new mapkern.Map({ container: container.current, center, zoom });
return () => map.remove();
}, []);
return <div ref={container} style={{ width: "100%", height: "100%" }} />;
}Add sources and layers once the map has loaded:
const places = {
type: "FeatureCollection",
features: [
{ type: "Feature", properties: { name: "Pike Place Market" }, geometry: { type: "Point", coordinates: [-122.3422, 47.6097] } },
{ type: "Feature", properties: { name: "Space Needle" }, geometry: { type: "Point", coordinates: [-122.3493, 47.6205] } },
],
};
map.on("load", () => {
map.addSource("places", { type: "geojson", data: places });
map.addLayer({ id: "dots", type: "circle", source: "places", paint: { radius: 7, color: "#246a50" } });
map.flyTo({ center: [-122.345, 47.615], zoom: 13 });
});Clicks on a layer tell you which feature was hit, and popups and markers take a line each:
map.on("click", "dots", (event) => {
new mapkern.Popup().setLngLat(event.lngLat).setText(event.features[0].properties.name).addTo(map);
});
new mapkern.Marker().setLngLat([-122.3321, 47.6062]).addTo(map);To change the data while the map runs, give the source the new GeoJSON. What's on screen stays until the new tiles are ready:
map.getSource("places").setData(latestPlaces);TerrainControl adds one button that steps through a flat map, 3D terrain from above, tilted 3D and the globe. It needs elevation data, and Mapterhorn has it for free:
const terrain = new mapkern.TerrainControl({
source: {
type: "dem",
tiles: ["https://tiles.mapterhorn.com/{z}/{x}/{y}.webp"],
tileSize: 512,
maxZoom: 12,
encoding: "terrarium",
attribution: '<a href="https://mapterhorn.com/attribution">© Mapterhorn</a>',
},
});
map.addControl(terrain);
terrain.setView("tilted");If you only want the globe, pass projection: "globe" when you create the map.
Any style written for MapLibre or Mapbox works as it is. Give the map its URL, or the style object itself:
const map = new mapkern.Map({ container: "map", style: "https://tiles.openfreemap.org/styles/positron" });OpenFreeMap also has bright, dark and fiord. If a style uses something Mapkern can't draw yet, that part is left out and the rest still shows; the styleload event lists what was left out in event.skipped. Pass style: null for a blank map you fill yourself.
If you already have an app written for MapLibre GL JS or Mapbox GL JS, you don't need to rewrite it. Change the import and keep the rest:
import maplibregl from "mapkern-gl/maplibre";
// or, for Mapbox code: import mapboxgl from "mapkern-gl/mapbox";
const map = new maplibregl.Map({ container: "map", style: "https://tiles.openfreemap.org/styles/liberty" });
map.on("load", () => {
map.addSource("points", { type: "geojson", data: "points.geojson" });
map.addLayer({ id: "points", type: "circle", source: "points", paint: { "circle-color": "#e55e5e" } });
});Layers, sources and paint properties use the names you already know, events carry LngLat and Point objects, and markers, popups and controls keep the .maplibregl-* classes your CSS targets. Switching from MapLibre or Mapbox covers bundler aliases, script tags and the few things that work differently.
Mapkern can make its own street map tiles from OpenStreetMap data, so you don't need a map provider. Download an extract for your region from Geofabrik, then cut out the area you want:
npx mapkern-tiles --input washington-latest.osm.pbf --bbox=-122.45,47.48,-122.22,47.74 --output seattle.pmtilesPut seattle.pmtiles on any web server that handles range requests, and point the built-in style at it:
import * as mapkern from "mapkern-gl";
const map = new mapkern.Map({
container: "map",
style: mapkern.lightStyle({ url: "seattle.pmtiles" }),
center: [-122.335, 47.608],
zoom: 12,
});The OpenStreetMap license asks you to credit the data. The style shows "© OpenStreetMap contributors" in the corner, so leave that visible.
Vector and raster sources read PMTiles and MBTiles files, from a web server or from the user's own computer. GeoJSON sources also read KML, KMZ, GPX, CSV, TopoJSON, zipped Shapefiles, FlatGeobuf, GeoPackage, GML, Esri JSON, OpenStreetMap XML and PBF, WKT, Cursor on Target and GPS tracks in TCX, IGC and NMEA, from a URL or straight from a file the user picks, and data in UTM, state plane or Web Mercator coordinates is converted for you. Features can be written back out as KML, GPX, CSV, WKT or CoT. You can replace the data with setData() while the map is running, move the camera with easeTo and flyTo, tilt and rotate the map, show the whole world as a globe that flattens into the normal map as you zoom in, and style features from their own data with expressions like match and interpolate. Buildings can rise into 3D, and detailed ones like the Space Needle keep their shape instead of turning into one plain block. The map can be lit by the sun at any date and time, with night and twilight on any map, satellite included. Labels follow streets and keep out of each other's way, and queryRenderedFeatures() tells you what's under the pointer. Elevation tiles, or DTED, SRTM and GeoTIFF elevation files opened from the user's computer, shade the hills and can raise the ground into 3D terrain, with the map, labels and buildings on top of it, and one button steps between a flat map, terrain seen from above and tilted terrain. A search button finds places, addresses, streets and businesses, and tells you what is at a spot, from an index built from the same OpenStreetMap file, with no search service behind it. Units draw as MIL-STD-2525 symbols straight from their codes, places convert to and from MGRS grid references, and features can be sent to TAK as CoT. A basemap button swaps styles, the scale shows metric and imperial on one line, and tiles a session has seen are kept on disk, so the map keeps working when the network or a server drops.
To try it on an Android phone or tablet, npm run android -- --install builds a small app from android/ that shows the map full screen and installs it on the connected device. It needs the Android SDK and a JDK, and --run starts it too. Reinstalling keeps the app's saved tiles and settings; add --clear to start fresh. The app also carries this site and its examples.
While you pan and zoom, Mapkern fills gaps with tiles it already has from other zoom levels, so the map doesn't flash empty, and when it has time to spare it loads a ring of tiles just outside the view, so a pan shows them at once. Imagery that runs out of detail, or answers with a "no data" picture, falls back to the nearest real tile above it. Tiles are decoded in Web Workers, and the whole library, worker included, is about 160 KB gzipped.
Mapkern needs WebGL 2. It runs in Chrome, Edge and other Chromium browsers from version 96, which includes the Android WebView and the one on Amazon Fire tablets, in Safari from 16.4 on iPhone, iPad and Mac, and in Firefox from 115.
The API may still change before 1.0.
- Styles: sources, layers, properties and expressions
- API: map options, methods and events
- Tiles: building tiles and what's in them
- Switching from MapLibre or Mapbox: running existing map code on Mapkern
You'll need Node.js 22.18 or newer.
npm install
npm run devnpm run dev serves the examples at http://127.0.0.1:8080/examples/ and rebuilds whenever you change something in src. Before sending a change, run npm run verify for lint, types and unit tests, and npm run test:browser, which draws test maps in Chrome and checks the pixels.
MIT

