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.
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.
Google Maps Platform ($200/month free credit):
# Enable: Maps JS API, Geocoding API, Directions API, Places API, Distance Matrix API
export GOOGLE_MAPS_API_KEY="AIzaSy..."
Mapbox (50,000 free map loads/month):
export MAPBOX_ACCESS_TOKEN="pk.eyJ1..."
Leaflet + OpenStreetMap (completely free, no API key):
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9/dist/leaflet.js"></script>
Nominatim (free geocoding, 1 req/sec rate limit, no key needed).
// Google Geocoding
async function geocode(address: string) {
const res = await fetch(
`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${API_KEY}`
);
const data = await res.json();
const r = data.results[0];
return { lat: r.geometry.location.lat, lng: r.geometry.location.lng, formatted: r.formatted_address };
}
// Nominatim (free, no key — requires User-Agent header per TOS)
async function nominatimGeocode(query: string) {
const res = await fetch(
`https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&limit=5`,
{ headers: { "User-Agent": "MyApp/1.0 (contact@myapp.com)" } }
);
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;
}
Leaflet + OpenStreetMap (free):
const map = L.map("map").setView([48.8566, 2.3522], 12);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: '© OpenStreetMap 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);
});
Mapbox GL JS (vector tiles, custom styles):
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());
// Google Directions
async function getRoute(origin: string, destination: string, mode = "driving") {
const res = await fetch(
`https://maps.googleapis.com/maps/api/directions/json?origin=${encodeURIComponent(origin)}&destination=${encodeURIComponent(destination)}&mode=${mode}&key=${API_KEY}`
);
const data = await res.json();
const leg = data.routes[0].legs[0];
return { distance: leg.distance.text, duration: leg.duration.text, polyline: data.routes[0].overview_polyline.points };
}
// Distance Matrix (many-to-many)
async function distanceMatrix(origins: string[], destinations: string[]) {
const res = await fetch(
`https://maps.googleapis.com/maps/api/distancematrix/json?origins=${origins.map(encodeURIComponent).join("|")}&destinations=${destinations.map(encodeURIComponent).join("|")}&key=${API_KEY}`
);
return res.json();
}
// OSRM (free, no key)
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 };
}
// 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;
}
Nearby search with PostGIS:
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 (Google Directions with waypoint reordering):
async function optimizeRoute(origin: string, destination: string, waypoints: string[]) {
const res = await fetch(
`https://maps.googleapis.com/maps/api/directions/json?origin=${encodeURIComponent(origin)}&destination=${encodeURIComponent(destination)}&waypoints=optimize:true|${waypoints.map(encodeURIComponent).join("|")}&key=${API_KEY}`
);
const route = (await res.json()).routes[0];
return {
optimizedOrder: route.waypoint_order,
totalDistance: route.legs.reduce((s: number, l: any) => s + l.distance.value, 0),
totalDuration: route.legs.reduce((s: number, l: any) => s + l.duration.value, 0),
};
}
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 free) 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.
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 Directions API with waypoints=optimize: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.
{lat, lng} objects, Mapbox and GeoJSON use [lng, lat] arrays; mixing these up is the most common source of misplaced markers.npx skills add TerminalSkills/maps-geolocation下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer