# Map Zone Maps API — Philippines — Full Integration Guide for AI Agents > Copy this whole file into your coding agent's context (system prompt, CLAUDE.md/AGENTS.md, or paste directly > into chat) to have it build a basemap + search + directions integration against this API in one pass. Every > snippet below is a complete, working example - no SDK is published, these are plain `fetch()` calls plus > MapLibre GL JS, nothing else to install beyond `maplibre-gl`. Base URL: `https://ph.map.zone`. Auth: append `apikey=YOUR_KEY` or `key=YOUR_KEY` to any request (either name works, on all endpoints). Get a key via the contact link on [the live demo](../). **If you already know Google Maps Platform, this is a drop-in-compatible contract**: Geocoding/Places/Directions responses use the exact same JSON shape as Google's Geocoding API, Places API (Autocomplete + Details), and Directions/Distance Matrix API - same field names, same `status` enum values, same snake_case. HTTP is always 200 for those three; check `status` to know if the call succeeded (`status !== "OK"` is not a JS/network error, it's a normal response you must branch on). Tiles use ordinary HTTP status codes instead (200/204/404) since there's no Google equivalent to be compatible with. Full machine-readable specs: [tiles](specs/tiles-api.openapi.yaml) · [geocoding](specs/geocoding-api.openapi.yaml) · [routing](specs/routing-api.openapi.yaml). Try any call live, with your own key, before writing code: [playground](playground.html). --- ## 1. Vector basemap (MapLibre GL JS) The style endpoint returns a complete [MapLibre GL Style Spec](https://maplibre.org/maplibre-style-spec/) - pass its URL straight into `style`. Tile/glyph/sprite URLs inside the returned style already point back at this same API with the key handling worked out; no `transformRequest`, no manual source/layer setup. ```js import maplibregl from "maplibre-gl"; // npm i maplibre-gl const map = new maplibregl.Map({ container: "map", // a
style: `https://ph.map.zone/maps/styles/basemap-phl/style.json?apikey=${API_KEY}`, center: [121.0, 14.6], // [lng, lat] - Metro Manila zoom: 11, }); map.addControl(new maplibregl.NavigationControl()); // A plain marker: new maplibregl.Marker().setLngLat([120.9842, 14.5995]).addTo(map); ``` `basemap-phl` is the only published style name. Tiles outside the Philippines, or above the demo's zoom limit, come back as empty (204/0-byte) - not an error, MapLibre renders them as blank. --- ## 2. Place Autocomplete (search-as-you-type) Debounce this client-side (e.g. 200-300ms after the user stops typing) - it's meant for keystroke-driven calls. ```js async function autocomplete(input, nearLatLng /* "14.60,121.02", optional bias */) { const params = new URLSearchParams({ input, limit: "5", apikey: API_KEY }); if (nearLatLng) params.set("location", nearLatLng); const res = await fetch(`https://ph.map.zone/maps/api/place/autocomplete/json?${params}`); const body = await res.json(); if (body.status !== "OK") return []; // ZERO_RESULTS, INVALID_REQUEST, ... - just means no suggestions return body.predictions; // [{ description, place_id, structured_formatting: { main_text, secondary_text } }, ...] } ``` `structured_formatting.main_text_matched_substrings` gives you `{offset, length}` ranges if you want to bold the matched part of the typed text in the dropdown. ## 3. Place Details (resolve a prediction to coordinates) Autocomplete predictions carry a `place_id`, not coordinates - call Details to get them (and a formatted address) once the user picks one. ```js async function placeDetails(placeId) { const params = new URLSearchParams({ place_id: placeId, apikey: API_KEY }); const res = await fetch(`https://ph.map.zone/maps/api/place/details/json?${params}`); const body = await res.json(); if (body.status !== "OK") return null; return body.result; // { name, formatted_address, geometry: { location: { lat, lng } }, types } } // Usage: drop a pin after the user picks a suggestion. const place = await placeDetails(prediction.place_id); if (place) new maplibregl.Marker().setLngLat([place.geometry.location.lng, place.geometry.location.lat]).addTo(map); ``` ## 4. Geocoding (address → coordinates) For a direct "look up this address" flow, skip Autocomplete/Details and geocode in one call. ```js async function geocode(address) { const params = new URLSearchParams({ address, apikey: API_KEY }); const res = await fetch(`https://ph.map.zone/maps/api/geocode/json?${params}`); const body = await res.json(); if (body.status !== "OK") return null; return body.results[0]; // { formatted_address, geometry: { location: { lat, lng }, location_type }, place_id, types } } ``` ## 5. Reverse geocoding (coordinates → address) Same endpoint as #4, pass `latlng` instead of `address` - never both in the same request. ```js async function reverseGeocode(lat, lng) { const params = new URLSearchParams({ latlng: `${lat.toFixed(5)},${lng.toFixed(5)}`, apikey: API_KEY }); const res = await fetch(`https://ph.map.zone/maps/api/geocode/json?${params}`); const body = await res.json(); if (body.status !== "OK") return null; return body.results[0]; } // Usage: resolve an address when the user clicks the map. map.on("click", async (e) => { const place = await reverseGeocode(e.lngLat.lat, e.lngLat.lng); if (place) console.log(place.formatted_address); }); ``` ## 6. Directions (turn-by-turn route between stops) Up to 3 intermediate stops without optimization, 25 with TSP (`optimize:true` - see below). `mode` is `driving`, `walking`, or `bicycling`; always send it explicitly. ```js async function directions(origin, destination, { waypoints = [], mode = "driving", optimize = false } = {}) { const params = new URLSearchParams({ origin: `${origin.lat},${origin.lng}`, destination: `${destination.lat},${destination.lng}`, mode, apikey: API_KEY, }); if (waypoints.length) { const prefix = optimize ? "optimize:true|" : ""; params.set("waypoints", prefix + waypoints.map((w) => `${w.lat},${w.lng}`).join("|")); } const res = await fetch(`https://ph.map.zone/maps/api/directions/json?${params}`); const body = await res.json(); if (body.status !== "OK") return null; return body.routes[0]; // { summary, overview_polyline: { points }, legs: [...], waypoint_order? } } ``` ### Draw the route on the map `overview_polyline.points` is a [Google Encoded Polyline](https://developers.google.com/maps/documentation/utilities/polylinealgorithm) (precision 1e5, lat/lng order) - MapLibre has no built-in decoder, so decode it yourself: ```js function decodePolyline(encoded) { const coords = []; let index = 0, lat = 0, lng = 0; while (index < encoded.length) { let shift = 0, result = 0, b; do { b = encoded.charCodeAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); lat += result & 1 ? ~(result >> 1) : result >> 1; shift = 0; result = 0; do { b = encoded.charCodeAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); lng += result & 1 ? ~(result >> 1) : result >> 1; coords.push([lng / 1e5, lat / 1e5]); // GeoJSON order: [lng, lat] } return coords; } function drawRoute(map, route) { const data = { type: "Feature", geometry: { type: "LineString", coordinates: decodePolyline(route.overview_polyline.points) } }; if (map.getSource("route")) { map.getSource("route").setData(data); } else { map.addSource("route", { type: "geojson", data }); map.addLayer({ id: "route", type: "line", source: "route", layout: { "line-join": "round", "line-cap": "round" }, paint: { "line-color": "#1c64f2", "line-width": 5 } }); } } ``` ### TSP stop-order optimization `optimize:true` as the first `waypoints` element re-orders up to 25 stops, keeping origin/destination fixed. The response's `routes[0].waypoint_order` is a permutation of `0..n-1` (n = number of waypoints, not counting origin/destination): element *i* = the **input index** of the stop visited *i*-th. Example: you send stops `[B, C, D]` (indices 0, 1, 2) and get back `waypoint_order: [2, 0, 1]` → the visiting order is D, B, C. `routes[0].legs` is already in that visiting order (not input order) - use `waypoint_order` to re-label stops in your UI, not to re-sort `legs`. ## Distance Matrix (bulk travel time/distance, no route geometry) Up to 5 origins × 5 destinations. No polyline to draw - just a grid of durations/distances. ```js async function distanceMatrix(origins, destinations, mode = "driving") { const params = new URLSearchParams({ origins: origins.map((p) => `${p.lat},${p.lng}`).join("|"), destinations: destinations.map((p) => `${p.lat},${p.lng}`).join("|"), mode, apikey: API_KEY, }); const res = await fetch(`https://ph.map.zone/maps/api/distancematrix/json?${params}`); const body = await res.json(); if (body.status !== "OK") return null; return body.rows; // rows[i].elements[j] = { status, distance: {text, value}, duration: {text, value} } for origins[i] -> destinations[j] } ``` Per-cell `status` can differ from the request-level `status` (e.g. one origin/destination pair has no route while the rest succeed) - check `status` on each `elements[i][j]`, not just the top-level response. ## Common mistakes to avoid - Don't treat a non-`OK` `status` as a thrown error for Geocoding/Routing - it's a normal 200 response, branch on the field. - Don't send both `apikey` and `key` on the same request - send exactly one (has caused the real gateway to return a malformed `style.json` in testing; harmless on Geocoding/Routing but still avoid it). - Don't poll Autocomplete on every keystroke without debouncing - it's rate-limited per key. - `origin`/`destination`/`waypoints` take raw coordinates (`lat,lng`), never a free-text address or `place_id` - geocode first if you only have an address.