---
name: geospatial-maps-expert
description: "Expert guide for maps and geospatial data (Mapbox GL JS, Leaflet, Google Maps, PostGIS) / Panduan ahli peta dan data geospasial (Mapbox GL JS, Leaflet, Google Maps, PostGIS)."
author: "Roedy Rustam"
version: "4.4.2"
---
# Geospatial & Maps Expert (2026 Edition)
[English](#english) | [Bahasa Indonesia](#bahasa-indonesia)
---
## English
### Description
Expert guide for integrating interactive mapping engines, geospatial vector tiles, and spatial database queries into modern web and mobile applications. Covers MapLibre GL JS (open-source, license-free), Mapbox GL JS v3, Leaflet, Google Maps Platform, deck.gl large-scale visualization, Supercluster client-side marker aggregation, and PostgreSQL/PostGIS spatial query optimization.
### Trigger Conditions
- Embedding interactive vector maps into React, Next.js, or mobile applications.
- Implementing location-based search (*"Find outlets within 5 km"*).
- Managing thousands of map markers with client-side clustering.
- Writing spatial SQL queries using PostGIS (`ST_DWithin`, `ST_MakePoint`, `ST_Distance`).
- Visualizing geospatial GeoJSON datasets, routes, and heatmaps.
### PostGIS Spatial Query Pattern (PostgreSQL)
```sql
-- Enable PostGIS extension and configure geometry table
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE TABLE IF NOT EXISTS merchant_locations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
address TEXT,
geom GEOMETRY(Point, 4326) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Crucial: GIST Spatial Index for sub-millisecond radius search
CREATE INDEX IF NOT EXISTS idx_merchant_geom ON merchant_locations USING GIST (geom);
-- Find nearest merchants within 5,000 meters (5 km) of user's coordinates
-- Note: Cast to GEOGRAPHY to calculate true geodesic distance in meters
SELECT
id,
name,
address,
ST_Y(geom::geometry) AS latitude,
ST_X(geom::geometry) AS longitude,
ROUND(ST_Distance(geom::geography, ST_SetSRID(ST_MakePoint(:userLng, :userLat), 4326)::geography)::numeric, 1) AS distance_meters
FROM merchant_locations
WHERE ST_DWithin(geom::geography, ST_SetSRID(ST_MakePoint(:userLng, :userLat), 4326)::geography, 5000)
ORDER BY distance_meters ASC
LIMIT 20;
```
### High-Performance React Map Component (MapLibre GL JS)
```tsx
// src/components/maps/StoreLocatorMap.tsx
'use client';
import { useEffect, useRef } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
interface StorePoint {
id: string;
name: string;
lng: number;
lat: number;
}
export function StoreLocatorMap({ stores }: { stores: StorePoint[] }) {
const mapContainer = useRef(null);
const mapRef = useRef(null);
useEffect(() => {
if (!mapContainer.current) return;
const map = new maplibregl.Map({
container: mapContainer.current,
style: 'https://tiles.openfreemap.org/styles/liberty', // Free open-source vector tiles
center: [106.8456, -6.2088], // Jakarta default
zoom: 12,
});
map.addControl(new maplibregl.NavigationControl(), 'top-right');
map.on('load', () => {
// Add GeoJSON source with built-in Supercluster support
map.addSource('stores', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: stores.map((s) => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [s.lng, s.lat] },
properties: { id: s.id, name: s.name },
})),
},
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50,
});
// Cluster circle layer
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'stores',
filter: ['has', 'point_count'],
paint: {
'circle-color': '#059669',
'circle-radius': ['step', ['get', 'point_count'], 18, 10, 24, 50, 30],
},
});
// Unclustered point marker
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'stores',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#2563eb',
'circle-radius': 8,
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff',
},
});
});
mapRef.current = map;
return () => map.remove();
}, [stores]);
return ;
}
```
## Orchestration & Integration
- **`modern-web-guidance`**: Modern Web APIs, canvas performance, and responsive map containers.
- **`design-system-architect`**: Map controls, custom HTML pin markers, popover callouts, and location search inputs.
- **`tailwind-expert`**: Map container overlays, HUD widgets, and sliding panel drawer styling.
- **`ui-ux-pro-max`**: Map viewport ergonomics, smooth fly-to easing, and location pin micro-interactions.
- **`visual-ui-auditor`**: Multimodal vision auditing of rendered map layers, marker contrast, and popups.
- **`anti-slop`**: Anti-slop geospatial craft, zero broken tile rendering and zero unclustered DOM overloads.
- **`database-orm-expert`**: Bridges PostGIS spatial types with Prisma / Drizzle ORM schemas.
- **`senior-frontend`**: Integrates responsive map canvases without blocking main-thread UI interactivity.
- **`performance-web-vitals`**: Lazy-loads vector tile stylesheets and WebGL libraries to maintain optimal LCP and INP.
### Sovereign Execution, Token Efficiency & Invariants
| Dimension | Sovereign Mandate & Deterministic Contract |
| :--- | :--- |
| **Frontier Model Cascade** | Medium / High (`GPT-5.6 Sol Medium`, `Gemini 4 Argon`, `Claude Sonnet 5.5 High`, `Grok 4.7 High Fast`) |
| **Availability Fallback** | `Gemini 4 Argon/Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6` (Zero unhandled missing-model errors) |
| **KV-Cache & Token Protocol** | Pin static instructions & schemas at prompt prefix (`kv-cache-prefix-optimizer`); pass surgical `CONTEXT_MAP.md` slices (<250 tokens/call) during Swarm Fan-Out |
| **SaaT CLI & MCP Dispatch** | CLI: `vibes skill exec geospatial-maps-expert ` / MCP: `vibes_skill_dispatch`, `vibes_ast_query`, `vibes_compile_cache`, `vibes_optimize_budget`, `vibes_verify_gate` |
| **NFR & Anti-Slop Invariants** | P99 API Gateway Latency < 85ms, OpenAPI 3.1 / TypeSpec strict schema parity, RFC 9457 Problem Details error contracts, Idempotency-Key enforcement; 100% complete zero-placeholder output verified via `node scripts/check-anti-slop.js --strict` |
---
---
## Bahasa Indonesia
### Deskripsi
Panduan ahli untuk mengintegrasikan mesin peta interaktif, ubin vektor (*vector tiles*), dan kueri database geospasial ke dalam aplikasi web dan mobile modern. Mencakup MapLibre GL JS (bebas lisensi dan *open-source*), Mapbox GL JS v3, Leaflet, Google Maps Platform, visualisasi data berskala besar dengan deck.gl, kluster marker sisi klien dengan Supercluster, serta optimasi kueri spasial PostgreSQL/PostGIS.
### Kondisi Pemicu
- Menambahkan peta vektor interaktif ke dalam komponen React atau Next.js.
- Mengimplementasikan pencarian berbasis radius jarak (*"Cari gerai terdekat dalam radius 5 km"*).
- Menampilkan ribuan titik marker secara efisien menggunakan kluster otomatis.
- Menulis kueri geospasial PostGIS (`ST_DWithin`, `ST_Distance`, `ST_MakePoint`).
- Visualisasi GeoJSON, rute navigasi, dan peta kepadatan (*heatmap*).
### Pola Integrasi Kueri Spasial di Backend (TypeScript + Drizzle/Raw SQL)
```typescript
// Pencarian gerai terdekat menggunakan PostGIS
export async function getNearbyStores(userLat: number, userLng: number, radiusMeters = 5000) {
const query = `
SELECT id, name, address,
ST_Y(geom) as lat, ST_X(geom) as lng,
ROUND(ST_Distance(geom::geography, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography)::numeric, 1) as distance
FROM merchant_locations
WHERE ST_DWithin(geom::geography, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography, $3)
ORDER BY distance ASC
LIMIT 20;
`;
const result = await db.execute(query, [userLng, userLat, radiusMeters]);
return result.rows;
}
```
### Praktik Terbaik Kinerja Peta
1. **Gunakan Indeks GIST**: Kueri spasial tanpa indeks GIST akan memindai seluruh tabel secara sekuensial. Wajib pasang `CREATE INDEX ... USING GIST (geom)`.
2. **Ubah ke Tipe GEOGRAPHY**: Untuk menghitung jarak nyata di bumi dalam meter/kilometer, lakukan *casting* `geom::geography`.
3. **Kluster di Sisi Klien**: Jangan render ribuan node DOM untuk setiap marker. Gunakan fitur clustering WebGL bawaan MapLibre agar performa rendering tetap 60 FPS.
## Integrasi Orkestrasi
- **`modern-web-guidance`**: API Web Modern, performa kanvas, dan kontainer peta responsif.
- **`design-system-architect`**: Kontrol navigasi peta, marker pin kustom, popover callout, dan input pencarian lokasi.
- **`tailwind-expert`**: Overlay kontainer peta, widget HUD, dan styling panel geser (drawer).
- **`ui-ux-pro-max`**: Ergonomi viewport peta, transisi easing fly-to yang mulus, dan mikro-interaksi pin lokasi.
- **`visual-ui-auditor`**: Audit visual multimodal untuk layer peta, kontras marker, dan popup yang dirender.
- **`anti-slop`**: Rekayasa geospasial anti-slop, nol rendering ubin rusak, dan nol overload DOM marker tanpa kluster.
- **`database-orm-expert`**: Mengelola migrasi tabel dengan tipe data geospasial PostGIS.
- **`senior-frontend`**: Menangani state interaksi klik marker dan jendela pop-up.
- **`performance-web-vitals`**: Menerapkan dynamic import (`next/dynamic` dengan `ssr: false`) agar bundle peta tidak memperlambat pemuatan awal.
### Matriks Eksekusi Berdaulat, Efisiensi Token & Invarian
| Dimensi | Mandat Berdaulat & Kontrak Deterministik |
| :--- | :--- |
| **Kaskade Model Frontier** | Medium / High (`GPT-5.6 Sol Medium`, `Gemini 4 Argon`, `Claude Sonnet 5.5 High`, `Grok 4.7 High Fast`) |
| **Invarian Fallback Ketersediaan** | `Gemini 4 Argon/Pro` -> `Gemini 3.8 Flash`; `Claude Opus 5.5` -> `Claude Opus 4.6` (Bebas gagal saat model belum tersedia/kuota penuh) |
| **Protokol Hemat Token & KV-Cache** | Kunci instruksi & skema statis di prefix awal (`kv-cache-prefix-optimizer`); kirim potongan `CONTEXT_MAP.md` (<250 token/panggilan) saat Fan-Out |
| **Eksekusi SaaT CLI & MCP** | CLI: `vibes skill exec geospatial-maps-expert ` / MCP: `vibes_skill_dispatch`, `vibes_ast_query`, `vibes_compile_cache`, `vibes_optimize_budget`, `vibes_verify_gate` |
| **Invarian NFR & Anti-Slop** | Latensi P99 API < 85ms, paritas skema ketat OpenAPI 3.1 / TypeSpec, kontrak error RFC 9457 Problem Details, penegakan Idempotency-Key; kode 100% tuntas tanpa placeholder diverifikasi melalui `node scripts/check-anti-slop.js --strict` |