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

The previous command updates your app with the necessary dependencies.

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:
          '&copy; <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>;
  }
);

Interesting info about LeafletJS Map:

Official

  • Tutorials: Examples step by step to reference to create new features using Documentation. Reference.
  • Docs: All necessary info to work with LeafletJS. Reference

Contributors

Thanks to all the contributors who have helped make this documentation better!

  • mugan86
  • igorbabko
  • anartzdev
  • gimonaa
  • gioboa