Guide

Show your delivery area on a map

For pizzerias, shops that deliver, and trades with a service area: zones around your shop with the delivery fee of each, or an area you draw yourself, and an answer for any place a customer clicks on.

Click on the map where you want your pizza.
Loading the map…
The code below, with a pizzeria in Bern that does not exist.

Three steps

  1. Create a free account and, in the dashboard, create a key of the type “Maps only”, with your website as allowed referer.
  2. Copy the code below and put in your key.
  3. Enter the position of your shop (how to find it) and your zones, and paste the code into your page.

Zones by distance

<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@6.13.0/dist/maplibre-gl.css">
<style>
  #map { height: 460px; border-radius: 8px; }
  #answer { margin: 8px 0; font: 16px/1.4 system-ui, sans-serif; }
</style>

<p id="answer">Click on the map where you want your pizza.</p>
<div id="map"></div>

<script type="module">
import * as maplibregl from "https://unpkg.com/maplibre-gl@6.13.0/dist/maplibre-gl.mjs";

const shop = { name: "Muster Pizza", lon: 7.4474, lat: 46.9477 };
// the zones from the inside out: up to how many km, and what delivery costs
const zones = [
  { km: 3, label: "Free delivery", color: "#2e8b57" },
  { km: 6, label: "Delivery for CHF 5", color: "#e69f00" }
];

const map = new maplibregl.Map({
  container: "map",
  style: "https://api.overpass.ch/v1/YOUR_KEY/maps/styles/light.json?lang=en",
  center: [shop.lon, shop.lat],
  zoom: 11.3,
  cooperativeGestures: true
});
map.addControl(new maplibregl.NavigationControl({ showCompass: false }));
new maplibregl.Marker({ color: "#c0392b" }).setLngLat([shop.lon, shop.lat]).addTo(map);

map.on("load", () => {
  // under the labels, so that street and place names stay readable
  const labels = map.getStyle().layers.find((l) => l.type === "symbol")?.id;
  for (const z of zones) {
    map.addSource(`zone-${z.km}`, { type: "geojson", data: circle(shop.lon, shop.lat, z.km) });
    map.addLayer({ id: `zone-${z.km}`, type: "fill", source: `zone-${z.km}`,
      paint: { "fill-color": z.color, "fill-opacity": 0.12 } }, labels);
    map.addLayer({ id: `zone-${z.km}-line`, type: "line", source: `zone-${z.km}`,
      paint: { "line-color": z.color, "line-width": 2 } }, labels);
  }
  // the whole outer zone in view, on any screen
  const outer = circle(shop.lon, shop.lat, zones.at(-1).km).geometry.coordinates[0];
  map.fitBounds(outer.reduce((b, p) => b.extend(p), new maplibregl.LngLatBounds()), { padding: 20, duration: 0 });
});

const you = new maplibregl.Marker({ color: "#555" });
map.on("click", (e) => {
  const d = km(shop.lat, shop.lon, e.lngLat.lat, e.lngLat.lng);
  const zone = zones.find((z) => d <= z.km);
  you.setLngLat(e.lngLat).addTo(map);
  document.getElementById("answer").textContent =
    `${zone ? zone.label : "Outside our delivery area"}: ${d.toFixed(1)} km from ${shop.name}.`;
});

// a circle of km around a point, as a GeoJSON polygon
function circle(lon, lat, km) {
  const ring = [];
  for (let i = 0; i <= 64; i++) {
    const a = (i / 64) * 2 * Math.PI;
    ring.push([
      lon + (km / (111.32 * Math.cos((lat * Math.PI) / 180))) * Math.sin(a),
      lat + (km / 110.57) * Math.cos(a)
    ]);
  }
  return { type: "Feature", properties: {}, geometry: { type: "Polygon", coordinates: [ring] } };
}

// distance in km between two points (haversine)
function km(lat1, lon1, lat2, lon2) {
  const r = Math.PI / 180;
  const a = Math.sin((lat2 - lat1) * r / 2) ** 2 +
    Math.cos(lat1 * r) * Math.cos(lat2 * r) * Math.sin((lon2 - lon1) * r / 2) ** 2;
  return 12742 * Math.asin(Math.sqrt(a));
}
</script>

zones lists the zones from the inside out, each with its distance in km, the text for the customer and a colour. Add a third line for a third zone. The distance is a straight line from your shop; the way by road is longer.

An area you draw

If you deliver by district, or a river or a motorway marks the limit, draw the area yourself:

  1. Open geojson.io, find your area and draw it with the polygon tool. Several separate areas are fine.
  2. Choose Save › GeoJSON and put the file as area.geojson next to your page.
  3. Use this code instead of the one above.
Click on the map to see whether we come to you.
Loading the map…

The code for an area

<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@6.13.0/dist/maplibre-gl.css">
<style>
  #map { height: 460px; border-radius: 8px; }
  #answer { margin: 8px 0; font: 16px/1.4 system-ui, sans-serif; }
</style>

<p id="answer">Click on the map to see whether we come to you.</p>
<div id="map"></div>

<script type="module">
import * as maplibregl from "https://unpkg.com/maplibre-gl@6.13.0/dist/maplibre-gl.mjs";

// drawn on geojson.io with the polygon tool and saved as area.geojson
const area = await (await fetch("area.geojson")).json();

const map = new maplibregl.Map({
  container: "map",
  style: "https://api.overpass.ch/v1/YOUR_KEY/maps/styles/light.json?lang=en",
  center: [7.4474, 46.9477],
  zoom: 11,
  cooperativeGestures: true
});
map.addControl(new maplibregl.NavigationControl({ showCompass: false }));

map.on("load", () => {
  const labels = map.getStyle().layers.find((l) => l.type === "symbol")?.id;
  map.addSource("area", { type: "geojson", data: area });
  map.addLayer({ id: "area", type: "fill", source: "area",
    paint: { "fill-color": "#1f5fbf", "fill-opacity": 0.12 } }, labels);
  map.addLayer({ id: "area-line", type: "line", source: "area",
    paint: { "line-color": "#1f5fbf", "line-width": 2 } }, labels);
});

map.on("click", (e) => {
  const p = [e.lngLat.lng, e.lngLat.lat];
  const yes = area.features.some((f) => f.geometry.type === "Polygon" && inside(f.geometry.coordinates[0], p));
  document.getElementById("answer").textContent = yes ? "Yes, we come to you." : "Sorry, this is outside our area.";
});

// is point p inside the polygon ring? (ray casting)
function inside(ring, [x, y]) {
  let yes = false;
  for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
    const [xi, yi] = ring[i], [xj, yj] = ring[j];
    if ((yi > y) !== (yj > y) && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi) yes = !yes;
  }
  return yes;
}
</script>

The short inside() function works for areas without holes, which is what the polygon tool draws. For areas with holes, use booleanPointInPolygon of the library Turf.

What it costs

The first view of either map loaded 6 to 8 map tiles, in a window of 1000 × 640 pixels with MapLibre GL JS 6.13.0.

PlanMap tiles a monthViews at 8 tilesFor a business website
Every account, free100'000about 12'000✗ No, for non-commercial websites only
Maps Small, CHF 9 a month2 millionabout 250'000✓ Yes
Maps Medium, CHF 29 a month10 millionabout 1'200'000✓ Yes

Prices plus VAT. All plans: maps plans.

Good to know

  • The map sets no cookies: neither MapLibre nor our map server uses any.
  • The © OpenStreetMap credit must stay on the map. The (i) button in the corner opens it.
  • To show where your branches are as well, combine this with the store locator.