Back to skills
SKILL.md
Maps Geolocation
ASecurityBuild location-based features with mapping APIs. Use when a user asks to integrate Google Maps, Mapbox, or Leaflet, implement geocoding and reverse geocoding, calculate routes and distances, build store locators, add interactive maps to web apps, implement geofencing, work with GeoJSON, build delivery tracking systems, optimize routes for fleets, display heatmaps, implement address autocomplete, or build any location-aware application. Covers Google Maps Platform, Mapbox, Leaflet, and OpenStr...
- 142 stars
- 0 votes
- 2 copies
- 6 views
- Added May 29, 2026
Works with
Security analysis
92/100- Installs packages at runtime which could introduce malicious dependencies
npx -y skills add TerminalSkills/skills --skill maps-geolocation --agent claude-codeAre you the author of Maps Geolocation?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/terminalskills-maps-geolocation)---
name: maps-geolocation
description: >-
Build location-based features with mapping APIs. Use when a user asks to
integrate Google Maps, Mapbox, or Leaflet, implement geocoding and reverse
geocoding, calculate routes and distances, build store locators, add
interactive maps to web apps, implement geofencing, work with GeoJSON, build
delivery tracking systems, optimize routes for fleets, display heatmaps,
implement address autocomplete, or build any location-aware application.
Covers Google Maps Platform, Mapbox, Leaflet, and OpenStreetMap/Nominatim.
license: Apache-2.0
compatibility: Any language with an HTTP client; JS/TS for browser map rendering; API keys for Google Maps or Mapbox (Leaflet with OSM needs none)
metadata:
author: terminal-skills
version: 1.2.0
category: development
tags:
- maps
- geolocation
- google-maps
- mapbox
- leaflet
---
# Maps & Geolocation
## Overview
Build location-based applications using mapping and geolocation APIs. This skill covers four major platforms — Google Maps Platform, Mapbox, Leaflet (open-source), and OpenStreetMap/Nominatim (free) — for geocoding, routing, interactive maps, geofencing, heatmaps, store locators, fleet tracking, and address autocomplete. Choose based on budget: Google Maps for full-featured commercial use, Mapbox for custom styling, Leaflet+OSM for zero-cost self-hosted solutions.
## Instructions
### Step 1: Platform Selection & Setup
**Google Maps Platform** (billing account required; since March 2025 the flat $200 credit is replaced by a free monthly allowance per SKU, about 10,000 calls for Essentials SKUs such as Geocoding, 5,000 for Pro, 1,000 for Enterprise; check the pricing page for your SKUs):
```bash
# Enable in the Cloud console: Maps JavaScript API, Geocoding API, Routes API, Places API (New)
export GOOGLE_MAPS_API_KEY="AIzaSyD-example-restrict-this-key"
```
Directions API, Distance Matrix API and the old Places API are marked Legacy; use the Routes API (Step 4) for new projects. Restrict the key (HTTP referrers for browser keys, IP addresses or API restrictions for server keys).
**Mapbox** (free monthly tier: 50,000 GL JS map loads, 100,000 geocoding requests, 100,000 Directions requests; verify on the pricing page):
```bash
export MAPBOX_ACCESS_TOKEN="pk.eyJ1..."
```
**Leaflet + OpenStreetMap** (completely free, no API key):
```html
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
```
Or `npm install leaflet`. Leaflet 1.9.4 is the stable release; 2.x is still alpha.
**Nominatim** (free geocoding, max 1 request/second, no key, but a User-Agent or Referer that identifies your app is mandatory; autocomplete-as-you-type and bulk geocoding are not allowed on the public server, and results must be cached. For heavy use self-host Nominatim or use a commercial geocoder). The OSM Foundation's policy (operations.osmfoundation.org/policies/nominatim) also says code generated by an LLM must follow it, so read it before shipping.
### Step 2: Geocoding & Reverse Geocoding
```typescript
// Google Geocoding (v3 JSON endpoint; still served, see the v4 note below)
async function geocode(address: string) {
const res = await fetch(
`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${process.env.GOOGLE_MAPS_API_KEY}`
);
const data = await res.json();
// HTTP status is 200 even on errors: check the JSON status field
if (data.status !== "OK") throw new Error(`Geocoding failed: ${data.status}`);
const r = data.results[0];
return { lat: r.geometry.location.lat, lng: r.geometry.location.lng, formatted: r.formatted_address };
}
// Nominatim (free, no key; usage policy applies: identifying User-Agent, 1 request/second, cache results)
async function nominatimGeocode(query: string) {
const res = await fetch(
`https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&limit=5`,
{ headers: { "User-Agent": "StoreLocator/1.0 (https://brewline-coffee.dev)" } }
);
return res.json();
}
// Batch geocoding with rate limiting
async function batchGeocode(addresses: string[], delayMs = 100) {
const results = [];
for (const addr of addresses) {
try { results.push({ address: addr, ...await geocode(addr) }); }
catch (err) { results.push({ address: addr, lat: null, lng: null, error: err.message }); }
await new Promise(r => setTimeout(r, delayMs));
}
return results;
}
```
**Geocoding API v4** is now generally available and is the successor to the v3 `/maps/api/geocode/json` endpoint. It is a REST API on `geocode.googleapis.com`: address geocoding is `GET https://geocode.googleapis.com/v4/geocode/address/{address}` with the key in the `X-Goog-Api-Key` header and an `X-Goog-FieldMask` header, place-id lookups use `/v4/geocode/places/{place}`, and there is a `destinations` search for building entrances. Parameters are renamed (`language` to `languageCode`, `region` to `regionCode`), the `status` field is gone (errors use HTTP status codes), and Google says v4 is server-to-server only, so call it from your backend, never from browser code. For new server-side projects use v4; existing v3 code keeps working.
### Step 3: Interactive Maps
**Leaflet + OpenStreetMap (free):**
```javascript
const map = L.map("map").setView([48.8566, 2.3522], 12);
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
}).addTo(map);
L.marker([48.8584, 2.2945]).addTo(map)
.bindPopup("<b>Eiffel Tower</b><br>Paris, France").openPopup();
// Load GeoJSON layer
fetch("/data/zones.geojson").then(r => r.json()).then(data => {
L.geoJSON(data, {
style: { color: "#ef4444", weight: 2, fillOpacity: 0.1 },
onEachFeature: (feature, layer) => layer.bindPopup(feature.properties.name),
}).addTo(map);
});
```
The public OSM tile server is for light interactive use only (no bulk download or offline caching); for production traffic use a tile provider or self-host.
**Mapbox GL JS** (vector tiles, custom styles; current major is v3):
```javascript
mapboxgl.accessToken = "pk.eyJ1...";
const map = new mapboxgl.Map({
container: "map", style: "mapbox://styles/mapbox/streets-v12",
center: [2.3522, 48.8566], zoom: 12,
});
new mapboxgl.Marker({ color: "#ef4444" })
.setLngLat([2.2945, 48.8584])
.setPopup(new mapboxgl.Popup().setHTML("<h3>Eiffel Tower</h3>"))
.addTo(map);
map.addControl(new mapboxgl.NavigationControl());
```
### Step 4: Routing & Distance Matrix
```typescript
// Google Routes API (replaces the Directions and Distance Matrix APIs).
// POST + JSON body; the X-Goog-FieldMask header is mandatory and you pay for the fields you ask for.
const ROUTES = "https://routes.googleapis.com";
async function getRoute(origin: string, destination: string, travelMode = "DRIVE") {
const res = await fetch(`${ROUTES}/directions/v2:computeRoutes`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Goog-Api-Key": process.env.GOOGLE_MAPS_API_KEY!,
"X-Goog-FieldMask": "routes.distanceMeters,routes.duration,routes.polyline.encodedPolyline",
},
body: JSON.stringify({ origin: { address: origin }, destination: { address: destination }, travelMode }),
});
const route = (await res.json()).routes?.[0];
if (!route) throw new Error("No route found");
// duration is a string such as "1560s"
return { meters: route.distanceMeters, seconds: parseInt(route.duration), polyline: route.polyline.encodedPolyline };
}
// Many-to-many matrix: returns elements with originIndex, destinationIndex, distanceMeters, duration
async function distanceMatrix(origins: string[], destinations: string[]) {
const res = await fetch(`${ROUTES}/distanceMatrix/v2:computeRouteMatrix`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Goog-Api-Key": process.env.GOOGLE_MAPS_API_KEY!,
"X-Goog-FieldMask": "originIndex,destinationIndex,distanceMeters,duration,status,condition",
},
body: JSON.stringify({
origins: origins.map(address => ({ waypoint: { address } })),
destinations: destinations.map(address => ({ waypoint: { address } })),
travelMode: "DRIVE",
}),
});
return res.json();
}
// OSRM public demo server (free, no key, demo use only: self-host OSRM or use a hosted router in production).
// Coordinates are [lng, lat].
async function osrmRoute(coords: [number, number][]) {
const wp = coords.map(c => c.join(",")).join(";");
const res = await fetch(`https://router.project-osrm.org/route/v1/driving/${wp}?overview=full&geometries=geojson`);
const data = await res.json();
return { distance: data.routes[0].distance, duration: data.routes[0].duration, geometry: data.routes[0].geometry };
}
```
### Step 5: Geofencing & Utilities
```typescript
// Haversine distance (meters)
function haversineDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
const R = 6371000;
const dLat = ((lat2 - lat1) * Math.PI) / 180;
const dLng = ((lng2 - lng1) * Math.PI) / 180;
const a = Math.sin(dLat / 2) ** 2 +
Math.cos((lat1 * Math.PI) / 180) * Math.cos((lat2 * Math.PI) / 180) * Math.sin(dLng / 2) ** 2;
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
// Point-in-polygon check
function pointInPolygon(point: [number, number], polygon: [number, number][]): boolean {
const [x, y] = point;
let inside = false;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const [xi, yi] = polygon[i], [xj, yj] = polygon[j];
if ((yi > y) !== (yj > y) && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi) inside = !inside;
}
return inside;
}
// Circular geofence check
function isInRadius(point: [number, number], center: [number, number], radiusMeters: number): boolean {
return haversineDistance(point[0], point[1], center[0], center[1]) <= radiusMeters;
}
```
### Step 6: Store Locator & Route Optimization
**Nearby search with PostGIS:**
```sql
SELECT id, name, address, lat, lng,
ST_DistanceSphere(ST_MakePoint(lng, lat), ST_MakePoint($1, $2)) AS distance_meters
FROM stores
WHERE ST_DWithin(ST_MakePoint(lng, lat)::geography, ST_MakePoint($1, $2)::geography, $3)
ORDER BY distance_meters LIMIT $4;
```
**Route optimization** (Routes API reorders intermediate stops; for large fleets with time windows and capacities use the Route Optimization API or an open-source solver such as VROOM):
```typescript
async function optimizeRoute(origin: string, destination: string, stops: string[]) {
const res = await fetch("https://routes.googleapis.com/directions/v2:computeRoutes", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Goog-Api-Key": process.env.GOOGLE_MAPS_API_KEY!,
"X-Goog-FieldMask": "routes.optimizedIntermediateWaypointIndex,routes.distanceMeters,routes.duration",
},
body: JSON.stringify({
origin: { address: origin },
destination: { address: destination },
intermediates: stops.map(address => ({ address })),
travelMode: "DRIVE",
optimizeWaypointOrder: true,
}),
});
const route = (await res.json()).routes[0];
return {
optimizedOrder: route.optimizedIntermediateWaypointIndex, // indexes into stops
totalMeters: route.distanceMeters,
totalSeconds: parseInt(route.duration),
};
}
```
## Examples
### Example 1: Store locator with address search for a coffee chain
**User prompt:** "Build a store locator for our coffee shops. The user types an address, we geocode it, find the 5 nearest stores within 10km from our PostgreSQL database, and display them on a Leaflet map with distance labels."
The agent will create a geocoding function using Google Geocoding API (or Nominatim for low-volume, cached lookups within its usage policy) to convert the user's address input to coordinates. It will write a PostGIS SQL query using `ST_DWithin` to find the 5 nearest stores within 10,000 meters, returning name, address, coordinates, and distance. On the frontend, it will initialize a Leaflet map centered on the searched location, add a marker for each store with a popup showing name, address, and distance in km, and fit the map bounds to show all results.
### Example 2: Delivery fleet route optimization with geofence alerts
**User prompt:** "Optimize the delivery route for a driver starting from our Berlin warehouse with 6 drop-off addresses, then set up a 200m geofence around each stop that logs arrival timestamps."
The agent will call the Google Routes API (`computeRoutes` with `optimizeWaypointOrder: true`) to reorder the 6 stops for minimum travel time, returning the optimal sequence, total distance, and estimated duration. It will then create a geofence monitor with a 200-meter circular zone around each stop using the haversine distance function, checking incoming GPS coordinates against each fence and logging enter/exit events with timestamps to track delivery progress.
## Guidelines
- **Choose the right platform for cost** — Google Maps bills per SKU after a small monthly free allowance; Mapbox has a free monthly tier (50,000 map loads); Leaflet + OSM + Nominatim has no fee but the public servers have strict usage policies (1 request/second, no bulk or offline use).
- **Always include attribution** — OpenStreetMap requires visible attribution on rendered maps; Mapbox and Google have their own attribution requirements that must not be removed.
- **Rate-limit geocoding requests** — add delays between batch geocoding calls (100ms+ for Google, 1 second for Nominatim, which also forbids bulk jobs on the public server) to avoid hitting rate limits and getting blocked.
- **Close GeoJSON polygon rings** — the first and last coordinate in a polygon must be identical; omitting this causes rendering failures and invalid geometry errors across all platforms.
- **Cache geocoding results** — store lat/lng in your database after the first lookup to avoid repeated API calls for the same addresses, which both saves cost and improves response times.
- **Validate coordinate order** — Google Maps uses `{lat, lng}` objects, Mapbox and GeoJSON use `[lng, lat]` arrays; mixing these up is the most common source of misplaced markers.
- **Autocomplete** — Google's `google.maps.places.Autocomplete` widget is Legacy; new code uses `PlaceAutocompleteElement` (`await google.maps.importLibrary("places")`) or Places API (New) autocomplete requests. Nominatim cannot be used for type-ahead.
- **Protect keys** — never ship an unrestricted Google key or a Mapbox secret token (`sk.`) to the browser; browser code uses restricted keys or public `pk.` tokens only.
Attribution
Comments
Loading comments…