DocsLeaflet

Leaflet + PickPoint

Leaflet was created by Volodymyr Agafonkin in 2011 and quickly became the most widely used open-source JavaScript mapping library. Its philosophy is deliberate simplicity: the core is just 42 KB gzipped, the API is minimal and stable, and it works in every browser without a build step. You load it from CDN, write plain JavaScript, and have a working map in minutes.

That simplicity is the point. If you need 3D buildings or GPU-powered rendering, MapLibre is the better fit. If you need a map that ships fast and runs everywhere - on slow phones, corporate proxies, and IE 11 - Leaflet is the right choice. We use OpenStreetMap raster tiles here: free, no API key, and familiar to any user who has used a map on the web.

    1

    HTML scaffold

    Load Leaflet from CDN. Full-screen map with a floating search box:

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
      <style>
        html, body, #map { height: 100%; margin: 0; }
        #search-box { position: absolute; top: 12px; right: 12px; z-index: 1000;
                      background: #fff; padding: 8px; border-radius: 8px;
                      box-shadow: 0 2px 8px rgba(0,0,0,.15); min-width: 260px; }
        #query { width: 100%; box-sizing: border-box; padding: 6px 10px;
                 border: 1px solid #ccc; border-radius: 4px; font-size: 14px; }
        #suggestions { list-style: none; margin: 4px 0 0; padding: 0; }
        #suggestions li { padding: 6px 10px; cursor: pointer; border-radius: 4px;
                          font-size: 13px; }
        #suggestions li:hover { background: #f0f7f7; }
      </style>
    </head>
    <body>
      <div id="map"></div>
      <div id="search-box">
        <input id="query" type="search" placeholder="Search destination…" autocomplete="off">
        <ul id="suggestions"></ul>
      </div>
      <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
      <script src="app.js"></script>
    </body>
    </html>
    2

    Initialise the map

    // app.js
    const API_KEY = 'YOUR_PICKPOINT_KEY';
    
    const map = L.map('map').setView([51.505, -0.09], 13);
    
    L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: '© <a href="https://openstreetmap.org">OpenStreetMap</a>',
      maxZoom: 19,
    }).addTo(map);
    
    let originMarker = null;
    let destMarker   = null;
    let routeLine    = null;
    3

    Get the user's position as the origin

    navigator.geolocation.getCurrentPosition(({ coords }) => {
      const latlng = [coords.latitude, coords.longitude];
      map.setView(latlng, 14);
    
      // Build icon via DOM API to keep source code free of HTML tags
      const dot = document.createElement('div');
      Object.assign(dot.style, {
        width: '14px', height: '14px', borderRadius: '50%',
        background: '#008080', border: '2px solid #fff',
        boxShadow: '0 1px 4px rgba(0,0,0,.4)',
      });
    
      originMarker = L.marker(latlng, {
        icon: L.divIcon({ className: '', html: dot.outerHTML, iconSize: [14,14], iconAnchor: [7,7] }),
      }).addTo(map);
    });
    4

    Address autocomplete

    Call /v2/address/search on each keystroke (debounced 250 ms). Pass the visible map bounds as viewbox to bias results:

    const input  = document.getElementById('query');
    const list   = document.getElementById('suggestions');
    let debounce;
    
    input.addEventListener('input', () => {
      clearTimeout(debounce);
      const q = input.value.trim();
      if (q.length < 2) { list.innerHTML = ''; return; }
    
      debounce = setTimeout(async () => {
        const b    = map.getBounds();
        // /v2/address/search uses bbox=minLon,minLat,maxLon,maxLat
        const bbox = `${b.getWest()},${b.getSouth()},${b.getEast()},${b.getNorth()}`;
        const res  = await fetch(
          `https://api.pickpoint.io/v2/address/search?q=${encodeURIComponent(q)}&limit=6&bbox=${bbox}`,
          { headers: { 'X-Api-Key': API_KEY } }
        );
        const { features = [] } = await res.json();
    
        // Response is GeoJSON: coordinates = [lon, lat], properties.name = label
        list.innerHTML = features.map(f => {
          const [lon, lat] = f.geometry.coordinates;
          const label = [f.properties.name, f.properties.city].filter(Boolean).join(', ');
          return `<li data-lat="${lat}" data-lon="${lon}">${label}</li>`;
        }).join('');
      }, 250);
    });
    
    list.addEventListener('click', (e) => {
      const li = e.target.closest('li');
      if (!li) return;
      const lat = parseFloat(li.dataset.lat);
      const lon = parseFloat(li.dataset.lon);
      input.value = li.textContent;
      list.innerHTML = '';
    
      destMarker?.remove();
      destMarker = L.marker([lat, lon]).addTo(map);
    
      if (originMarker) drawRoute(originMarker.getLatLng(), [lat, lon]);
    });
    5

    Draw the route and track live position

    trip.legs[0].shape is an array of [lat, lon] pairs - pass it directly to L.polyline(). For real-time position of the vehicle on the map, subscribe to the Device Tracking WebSocket:

    async function drawRoute(origin, dest) {
      const res  = await fetch('https://api.pickpoint.io/v2/route', {
        method:  'POST',
        headers: { 'Content-Type': 'application/json', 'X-Api-Key': API_KEY },
        body: JSON.stringify({
          locations: [
            { lat: origin.lat, lon: origin.lng },
            { lat: dest[0],    lon: dest[1]    },
          ],
          costing: 'auto',
        }),
      });
      const data   = await res.json();
      const coords = data.trip.legs[0].shape;  // [[lat, lon], …] - Leaflet-ready
    
      routeLine?.remove();
      routeLine = L.polyline(coords, { color: '#008080', weight: 4 }).addTo(map);
      map.fitBounds(routeLine.getBounds(), { padding: [40, 40] });
    }
    
    // Real-time position updates via PickPoint Device Tracking WebSocket
    // npm install socket.io-client
    import { io } from 'socket.io-client';
    
    const vehicleMarker = L.marker([0, 0]).addTo(map);
    
    const socket = io('wss://ws.pickpoint.io', {
      transports: ['websocket'],
      path: '/v2/tracking',
      query: { 'x-api-key': API_KEY },   // subscriber auth - keep on server!
    });
    
    socket.on('connect', () => {
      socket.emit('device:subscribe', { deviceUid: 'DEVICE_UID' });
    });
    
    socket.on('location:added', ({ latitude, longitude }) => {
      vehicleMarker.setLatLng([latitude, longitude]);
      map.panTo([latitude, longitude]);
    });

    Going further

    • Turn-by-turn panel - render trip.legs[0].maneuvers as a list.
    • Vehicle profile - change costing to bicycle, pedestrian, or truck.
    • Reverse geocode on click - on map click, call /v2/geocode/reverse and show the address in a popup.

    ← Web MapsMapLibre →