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.
Three steps
- Create a free account and, in the dashboard, create a key of the type “Maps only”, with your website as allowed referer.
- Copy the code below and put in your key.
- 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:
- Open geojson.io, find your area and draw it with the polygon tool. Several separate areas are fine.
- Choose Save › GeoJSON and put the file as
area.geojsonnext to your page. - Use this code instead of the one above.
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. Every account includes 100'000 tiles a month, about 12'000 views of the map. Maps Small has 2 million for CHF 9 a month plus VAT, about 250'000 views. 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.