Gallery

Coffee within a walk

The ten cafés nearest a corner in Portland's Buckman neighborhood, and the walk to each, darker for the longer walks. Click anywhere to see the cafés around there instead: in most cities, the map fills with routes in a second or two.

    Places:

    Build it yourself

    A click sends one nearby search for food:coffee_shop within half a mile, then asks routing for a walking route to each café, one at a time. It's the map above without the list of cafés. It was tested with MapLibre GL JS 6.15 in Chromium.

    What you need

    Make the page

    Make a folder, and in it index.html:

    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Coffee within a walk</title>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@6.15.0/dist/maplibre-gl.css">
      </head>
      <body style="margin: 0">
        <div id="map" style="height: 100vh"></div>
        <script type="module">
          import * as maplibregl from "https://cdn.jsdelivr.net/npm/maplibre-gl@6.15.0/+esm";
    
          const key = "mapricot_pk_…";
          const empty = { type: "FeatureCollection", features: [] };
    
          async function mapricot(path, body) {
            const response = await fetch(`https://api.mapricot.com${path}`, {
              method: body ? "POST" : "GET",
              headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
              body: body && JSON.stringify(body),
            });
            if (!response.ok) throw new Error(`Mapricot returned ${response.status}`);
            return response.json();
          }
    
          // Routes come as polylines with six decimal places; MapLibre wants [lon, lat] pairs.
          function decode(polyline) {
            const points = [];
            const position = [0, 0];
            let index = 0;
            while (index < polyline.length) {
              for (const axis of [0, 1]) {
                let shift = 0;
                let value = 0;
                let byte;
                do {
                  byte = polyline.charCodeAt(index++) - 63;
                  value |= (byte & 0x1f) << shift;
                  shift += 5;
                } while (byte >= 0x20);
                position[axis] += value & 1 ? ~(value >> 1) : value >> 1;
              }
              points.push([position[1] / 1e6, position[0] / 1e6]);
            }
            return points;
          }
    
          const map = new maplibregl.Map({
            container: "map",
            style: "https://tiles.mapricot.com/styles/light.json",
            center: [-122.6375, 45.5235],
            zoom: 15,
          });
          await map.once("load");
    
          map.addSource("walks", { type: "geojson", data: empty });
          map.addLayer({
            id: "walks",
            type: "line",
            source: "walks",
            layout: { "line-cap": "round", "line-join": "round" },
            paint: {
              "line-width": 4,
              "line-color": ["interpolate", ["linear"], ["get", "minutes"], 0, "#ffc078", 5, "#fd7e14", 15, "#9a3412"],
            },
          });
          map.addSource("cafes", { type: "geojson", data: empty });
          map.addLayer({
            id: "cafes",
            type: "circle",
            source: "cafes",
            paint: { "circle-radius": 5, "circle-color": "#fd7e14", "circle-stroke-color": "#ffffff", "circle-stroke-width": 2 },
          });
    
          async function walkFrom(lat, lon) {
            const start = { lat: Number(lat.toFixed(6)), lon: Number(lon.toFixed(6)) };
            const { results } = await mapricot(
              `/v1/nearby?lat=${start.lat}&lon=${start.lon}&category=food:coffee_shop&radius=800&limit=10`,
            );
            const walks = [];
            for (const place of results) {
              const { routes } = await mapricot("/v1/route", { waypoints: [start, { lat: place.lat, lon: place.lon }], mode: "walk" });
              walks.push({
                type: "Feature",
                properties: { minutes: routes[0].durationSeconds / 60 },
                geometry: { type: "LineString", coordinates: decode(routes[0].polyline) },
              });
            }
            map.getSource("walks").setData({ type: "FeatureCollection", features: walks });
            map.getSource("cafes").setData({
              type: "FeatureCollection",
              features: results.map((place) => ({
                type: "Feature",
                properties: { name: place.name },
                geometry: { type: "Point", coordinates: [place.lon, place.lat] },
              })),
            });
          }
    
          map.on("click", ({ lngLat }) => walkFrom(lngLat.lat, lngLat.wrap().lng));
          map.on("click", "cafes", (event) => {
            const [cafe] = event.features;
            new maplibregl.Popup({ offset: 10 }).setLngLat(cafe.geometry.coordinates).setText(cafe.properties.name).addTo(map);
          });
          await walkFrom(45.5235, -122.6375);
        </script>
      </body>
    </html>

    Put your browser key in place of mapricot_pk_….

    Open it

    Serve the folder with npx serve or python3 -m http.server, and open the address it prints. Opened straight from the file, the map stays blank and the API refuses the key, as Open the page explains. You should see ten walks from a corner in Buckman, in Portland. Click anywhere for the cafés around it.

    The cafés come back nearest first as the crow flies, but the walk can put one across a river or a highway further away. For the same nearby search with markers and a list, see Add a search box to a MapLibre map and Add markers to MapLibre GL JS.

    Data