LeafletJS Map
Leaflet is the leading open-source JavaScript library for mobile-friendly interactive maps. Weighing just about 42 KB of JS, it has all the mapping features most developers ever need.
Leaflet is designed with simplicity, performance and usability in mind. It works efficiently across all major desktop and mobile platforms, can be extended with lots of plugins, has a beautiful, easy to use and well-documented API and a simple, readable source code that is a joy to contribute to. LeafletJS Map Website
Usage
You can add LeafletJS Map easily by using the following Qwik starter script:
pnpm run qwik add leaflet-map
npm run qwik add leaflet-map
yarn run qwik add leaflet-map
bun run qwik add leaflet-map
The previous command updates your app with the necessary dependencies.
leaflet@1.9.4
@types/leaflet@1.9.4
It also adds new files to your project folder:
src/helpers/boundary-box.tsx
: Check map area boundaries function.src/models/location.ts
: Model to define locations info elements to use in props.src/models/map.ts
: Model to define map info to use in props.src/components/leaflet-map/index.tsx
: Leaflet map simple map features component.src/routes/basic-map/index.tsx
: Example to consume Leaflet Map component with demo data
Example
The main component configures the map, including the initial position and the group of markers to load. This setup allows you to create a dynamic and interactive map that can be easily configured and extended with different locations and markers.
Here is an example:
import {
component$,
noSerialize,
useSignal,
useStyles$,
useVisibleTask$,
type Signal,
} from '@builder.io/qwik';
import * as L from 'leaflet';
import leafletStyles from 'leaflet/dist/leaflet.css?inline';
// Sample data json and geojson
export const fvg: any = {
type: 'FeatureCollection',
name: 'FVG_line_0_001',
crs: { type: 'name', properties: { name: 'urn:ogc:def:crs:OGC:1.3:CRS84' } },
features: [
{
type: 'Feature',
properties: { ID_OGG: '08020060000', NAME: 'GEOJSON NAME' },
geometry: {
type: 'MultiLineString',
coordinates: [
[
[12.4188, 46.3528],
[12.4178, 46.3547],
[12.4284, 46.3517],
[12.4425, 46.3599],
[12.4488, 46.3605],
[12.4554, 46.3652],
[12.4552, 46.3672],
[12.4513, 46.3706],
],
],
},
},
],
};
const markers: Record<string, MarkersProps[]> = {
FDA: [
{
name: "Terzo d'Aquileia",
label: 'TRZ',
lat: '45.770946',
lon: '13.31338',
},
{
name: 'Musi',
label: 'MUS',
lat: '46.312663',
lon: '13.274682',
},
],
FVG: [
{
name: 'Borgo Grotta Gigante',
label: 'BGG',
lat: '45.709385',
lon: '13.764681',
},
{
name: 'Muggia',
label: 'MGG',
lat: '45.610495',
lon: '13.752682',
},
],
};
export default component$(() => {
useStyles$(
leafletStyles +
`
.marker-label {
color: red;
font-weight: 700;
}
`
);
const groupSig = useSignal('FDA');
const currentLocation = useSignal<LocationsProps>({
name: 'Udine',
point: [46.06600881056668, 13.237724558490601],
zoom: 10,
marker: true,
});
return (
<>
Change markers:{' '}
<select name="group" class="leaflet-ctrl" bind:value={groupSig}>
<option value="FDA">FDA</option>
<option value="FVG">FVG</option>
</select>
<LeafletMap
location={currentLocation}
markers={markers[groupSig.value]}
group={groupSig}
></LeafletMap>
</>
);
});
// The properties (props) used in the `LeafletMap` component and other related components are defined as follows:
export interface MapProps {
location: Signal<LocationsProps>;
markers?: MarkersProps[];
group?: Signal<string>;
}
export interface LocationsProps {
name: string;
point: [number, number];
zoom: number;
marker: boolean;
}
export interface MarkersProps {
name: string;
label: string;
lat: string;
lon: string;
}
/*
The `LeafletMap` component leverages the Leaflet library to render an interactive map.
This component can be configured with various properties (props) to set the central location, add markers, and draw boundaries.
In the `LeafletMap` component, both the location and the group signal are tracked.
This ensures that when the signal changes, the server function is called, and the map is updated with the new data.
*/
export const LeafletMap = component$<MapProps>(
({ location, markers, group }) => {
const mapContainerSig = useSignal<L.Map>();
useVisibleTask$(async ({ track }) => {
track(location);
group && track(group);
if (mapContainerSig.value) {
mapContainerSig.value.remove();
}
// center location
const { value: locationData } = location;
const centerPosition = locationData.point;
// layers
const markersLayer = new L.LayerGroup();
const bordersLayer = new L.LayerGroup();
// map
const map = L.map('map', {
layers: [markersLayer, bordersLayer],
}).setView(centerPosition, locationData.zoom || 14);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution:
'© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(map);
// center position marker
const qwikMarker = L.divIcon({
html: `
<svg xmlns="http://www.w3.org/2000/svg" width="30.12" height="32" viewBox="0 0 256 272">
<path fill="#18B6F6"
d="m224.803 271.548l-48.76-48.483l-.744.107v-.532L71.606 120.252l25.55-24.667l-15.01-86.12l-71.222 88.247c-12.136 12.226-14.372 32.109-5.642 46.781l44.5 73.788c6.813 11.376 19.163 18.18 32.47 18.074l22.038-.213z" />
<path fill="#AC7EF4"
d="m251.414 96.01l-9.795-18.075l-5.11-9.25l-2.023-3.615l-.212.213l-26.829-46.463C200.738 7.125 188.176-.105 174.55 0l-23.527.639l-70.158.213c-13.307.106-25.444 7.123-32.151 18.5l-42.69 84.632L82.353 9.25l100.073 109.937l-17.779 17.968l10.646 86.015l.107-.213v.213h-.213l.213.212l8.304 8.081l40.348 39.445c1.704 1.595 4.472-.318 3.3-2.339l-24.911-49.014l43.436-80.273l1.383-1.595c.533-.638 1.065-1.276 1.491-1.914c8.517-11.589 9.688-27.112 2.662-39.764" />
<path fill="#FFF" d="M182.746 118.763L82.353 9.358l14.266 85.695l-25.55 24.773L175.08 223.065l-9.368-85.696z" />
</svg>
`,
className: '',
iconSize: [24, 40],
});
locationData.marker &&
L.marker(centerPosition, { icon: qwikMarker })
.bindPopup(`Udine`)
.addTo(map);
// add boundaries to map
L.geoJSON(fvg, { style: { color: '#005DA4' } }).addTo(bordersLayer);
// add markers to map
const markersList = await markers;
markersList &&
markersList.map((m) => {
const myIcon = L.divIcon({
className: 'marker-point',
html: `<div class="marker-label" title="${m.name}" >${m.label}</div>`,
});
L.marker([+m.lat, +m.lon], { icon: myIcon }).addTo(markersLayer);
});
mapContainerSig.value = noSerialize(map);
});
return <div id="map" style={{ height: '25rem' }}></div>;
}
);