/ From Leaflet

Coming from Leaflet

Your Leaflet code on the 3D globe
npm i @ortho-earth/leaflet @ortho-earth/globe

@ortho-earth/leaflet gives you an L namespace shaped like Leaflet 1.9's. Its maps, tile layers, markers, popups, vector layers and GeoJSON draw through MapLibre GL: either on the Ortho Earth globe (@ortho-earth/globe/maplibre) or on the real maplibre-gl. Code written for Leaflet keeps its shape; only the import changes. Leaflet's stylesheet is not needed.

// on the globe
import L from "@ortho-earth/leaflet/globe";

const map = L.map("map").setView([35.6812, 139.7671], 15);
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19, attribution: "© OpenStreetMap" }).addTo(map);
L.marker([35.6812, 139.7671]).addTo(map).bindPopup("<b>Tokyo Station</b>").openPopup();
L.geoJSON(parks, { style: f => ({ fillColor: f.properties.kind === "park" ? "green" : "gray" }) }).addTo(map);

@ortho-earth/leaflet/globe also has Leaflet's named exports (map, tileLayer, marker, …), so a bundler alias ports a whole app without touching its imports:

// vite.config.js
export default { resolve: { alias: { leaflet: "@ortho-earth/leaflet/globe" } } };

To keep a flat map, give it maplibre-gl instead: createLeaflet(maplibregl, { style }) from @ortho-earth/leaflet, and installGlobal(L) for scripts and plugins that read window.L.

What is translated

LeafletOn the globe
L.map and its options, setView, flyTo, fitBounds, panTo, point conversions, handlers (map.dragging.disable() …)the camera. Zoom is shifted by one (Leaflet zoom z = MapLibre zoom z − 1, 256 px vs 512 px tiles). fitBounds uses Leaflet's own arithmetic, so a port lands on the same zoom. Rotation and tilt stay off unless you ask for them (rotate: true)
Map events (click, move, zoomend, popupopen …)MapLibre events with Leaflet's event object (latlng, containerPoint, layerPoint). A path's mouse event reaches its groups and then the map, as in Leaflet
L.tileLayer ({s}, {r}, tms, your own {keys}, maxNativeZoom, bounds, opacity), L.tileLayer.wms, L.GridLayer with createTileraster sources; a GridLayer's canvas or <img> tiles are drawn through a protocol
L.marker, L.icon, L.divIcon, popups and tooltips (bindPopup, bindTooltip, permanent, sticky, direction)MapLibre markers and popups, placed where Leaflet places them. The default icon is Leaflet's blue pin as SVG, no image files to serve
L.polyline, L.polygon, L.rectangle, L.circle (metres), L.circleMarker (pixels), setStyle, toGeoJSONone GeoJSON source per pane, like Leaflet's one renderer per pane, so 10,000 features cost one source
L.geoJSON (style, pointToLayer, onEachFeature, filter), L.layerGroup, L.featureGroupone layer per feature, as in Leaflet
L.imageOverlay, L.videoOverlayimage and video sources
L.control.zoom, attribution, scale, layers, L.Control.extend; panes (createPane, zIndex)controls in MapLibre's corners; panes become the order of the layers
L.Class, L.Evented, L.Util, L.DomUtil, L.DomEvent, L.latLng, L.latLngBounds, L.CRS …plain values and helpers, so plugins built on L.Class.extend load

What is not

Checking a port

Anything the namespace does not translate is reported once in the console as [lfshim] unsupported: …; it never throws. L.unsupportedReport() lists them, so you can check a port just by running it. For anything else, map.getMapLibre() returns the map underneath, and on the globe that map also has everything in the engine: terrain, 3D Tiles, the shared clock and the night side.

The test suite runs the Leaflet calls above against the real globe (npm run verify:ui -- t-leaflet in packages/globe). Unlike MapLibre and CesiumJS, there is no gallery of Leaflet's own examples yet, so the tables on this page are the list of what is covered.

Coming from other libraries? See From Google Maps, From MapLibre GL JS and From CesiumJS.

@ortho-earth/leaflet · GPL-3.0 · Kenji Yoshida · 2026