Leaflet
Leaflet is a lightweight, mobile-friendly mapping library with excellent browser support.
Python Example
| from anymap_ts import LeafletMap
m = LeafletMap(center=[-122.4, 37.8], zoom=10)
m.add_basemap("OpenStreetMap")
m.add_marker(-122.4194, 37.7749, popup="San Francisco")
m.add_marker(-122.2712, 37.8044, popup="Oakland")
m
|
TypeScript Implementation
The LeafletRenderer class handles coordinate conversion (Leaflet uses [lat, lng] order):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 | import * as L from 'leaflet';
import { BaseMapRenderer } from '../core/BaseMapRenderer';
import type { MapWidgetModel } from '../types/anywidget';
export class LeafletRenderer extends BaseMapRenderer<L.Map> {
private layersMap: Map<string, L.Layer> = new Map();
private markersMap: Map<string, L.Marker> = new Map();
protected createMap(): L.Map {
// Leaflet uses [lat, lng] but we receive [lng, lat] from Python
const center = this.model.get('center') as [number, number];
const zoom = this.model.get('zoom');
return L.map(this.mapContainer!, {
center: [center[1], center[0]], // Convert [lng, lat] to [lat, lng]
zoom,
zoomControl: false,
});
}
async initialize(): Promise<void> {
this.createMapContainer();
this.map = this.createMap();
this.setupModelListeners();
this.setupMapEvents();
this.isMapReady = true;
this.processPendingCalls();
}
}
|
Key Methods
Adding Markers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | private handleAddMarker(args: unknown[], kwargs: Record<string, unknown>): void {
const [lng, lat] = args as [number, number];
const id = kwargs.id as string || `marker-${Date.now()}`;
const popup = kwargs.popup as string;
// Note: Leaflet uses [lat, lng] order
const marker = L.marker([lat, lng]);
if (popup) {
marker.bindPopup(popup);
}
marker.addTo(this.map);
this.markersMap.set(id, marker);
}
|
Adding GeoJSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | private handleAddGeoJSON(args: unknown[], kwargs: Record<string, unknown>): void {
const geojson = kwargs.data as FeatureCollection;
const name = kwargs.name as string;
const style = kwargs.style as Record<string, unknown>;
const geoJsonLayer = L.geoJSON(geojson, {
style: (feature) => style || this.getDefaultStyle(feature.geometry.type),
pointToLayer: (feature, latlng) => {
return L.circleMarker(latlng, style || this.getDefaultStyle('Point'));
},
});
geoJsonLayer.addTo(this.map);
this.layersMap.set(name, geoJsonLayer);
// Optionally fit bounds
if (kwargs.fitBounds !== false) {
this.map.fitBounds(geoJsonLayer.getBounds());
}
}
|
Navigation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | private handleFlyTo(args: unknown[], kwargs: Record<string, unknown>): void {
const [lng, lat] = args as [number, number];
const zoom = kwargs.zoom as number;
const duration = (kwargs.duration as number) || 2000;
// Leaflet's flyTo uses [lat, lng] and duration in seconds
this.map.flyTo([lat, lng], zoom || this.map.getZoom(), {
duration: duration / 1000,
});
}
private handleFitBounds(args: unknown[], kwargs: Record<string, unknown>): void {
const [bounds] = args as [[number, number, number, number]];
// Convert [west, south, east, north] to Leaflet bounds
const leafletBounds = L.latLngBounds(
[bounds[1], bounds[0]], // Southwest: [lat, lng]
[bounds[3], bounds[2]] // Northeast: [lat, lng]
);
this.map.fitBounds(leafletBounds);
}
|
Controls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 | private handleAddControl(args: unknown[], kwargs: Record<string, unknown>): void {
const [controlType] = args as [string];
const position = this.convertPosition(kwargs.position as string);
let control: L.Control;
switch (controlType) {
case 'zoom':
control = L.control.zoom({ position });
break;
case 'scale':
control = L.control.scale({ position, imperial: false });
break;
case 'layers':
control = L.control.layers({}, {}, { position });
break;
}
control.addTo(this.map);
this.controlsMap.set(controlType, control);
}
// Convert position from MapLibre format to Leaflet format
private convertPosition(position: string): L.ControlPosition {
const map: Record<string, L.ControlPosition> = {
'top-left': 'topleft',
'top-right': 'topright',
'bottom-left': 'bottomleft',
'bottom-right': 'bottomright',
};
return map[position] || 'topright';
}
|
Default Styles
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | private getDefaultStyle(geometryType: string): Record<string, unknown> {
const defaults = {
Point: {
radius: 8,
fillColor: '#3388ff',
color: '#ffffff',
weight: 2,
fillOpacity: 0.8,
},
LineString: {
color: '#3388ff',
weight: 3,
opacity: 0.8,
},
Polygon: {
fillColor: '#3388ff',
color: '#0000ff',
weight: 2,
fillOpacity: 0.5,
},
};
return defaults[geometryType] || defaults.Point;
}
|
Source Files
- Renderer:
src/leaflet/LeafletRenderer.ts
- Types:
src/types/leaflet.ts
See also: Python notebook example