Pertama kali saya coba bikin aplikasi yang nunjukin titik di peta, saya langsung bingung. Data lokasi ada di MySQL, tapi gimana caranya cari "semua toko dalam radius 5 km"? Jawaban konvensional pakai rumus Haversine di SQL, tapi itu lambat banget kalau datanya ratusan ribu row. Akhirnya saya ketemu PostGIS, dan masalah itu selesai dalam hitungan milidetik.
PostGIS itu extension PostgreSQL yang nambahin kemampuan geospasial. Artinya, kamu bisa simpan data geometri (titik, garis, polygon) langsung di database, lalu query dengan operasi spatial seperti intersects, within, distance, dan buffer. Buat developer web yang bangun aplikasi berbasis peta, ini game changer banget.
MySQL memang punya tipe data spatial, tapi fiturnya terbatas. PostGIS support lebih dari 800 fungsi spatial, termasuk:
Selain itu, PostGIS support GeoJSON native, jadi data bisa langsung dikirim ke library peta seperti Leaflet atau OpenLayers tanpa konversi manual.
Kalau kamu sudah punya PostgreSQL, tinggal tambahin extension-nya:
# Install PostGIS di Ubuntu 22.04/24.04
sudo apt update
sudo apt install postgis postgresql-16-postgis-3
# Enable extension di database
sudo -u postgres psql -d nama_database -c "CREATE EXTENSION postgis;"
sudo -u postgres psql -d nama_database -c "CREATE EXTENSION postgis_topology;"
# Verifikasi instalasi
sudo -u postgres psql -d nama_database -c "SELECT PostGIS_Version();"
Output yang muncul biasanya seperti 3.4 USE_GEOS=1 USE_PROJ=1 USE_STATS=1. Kalau muncul error, cek versi PostgreSQL kamu dengan psql --version dan sesuaikan package-nya.
Buat tabel penyimpanan, kita pakai tipe data geometry atau geography. Perbedaannya:
Contoh bikin tabel toko dengan kolom lokasi:
CREATE TABLE toko (
id SERIAL PRIMARY KEY,
nama VARCHAR(100) NOT NULL,
alamat TEXT,
kategori VARCHAR(50),
rating DECIMAL(2,1),
lokasi GEOMETRY(Point, 4326) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Kolom 4326 = SRID untuk WGS84 (GPS standard)
-- Point = tipe geometri titik (longitude, latitude)
SRID 4326 itu sistem koordinat yang dipake GPS. Longitude di sumbu X, latitude di sumbu Y. Perhatikan urutannya: PostGIS pakai (X, Y) = (lon, lat), bukan (lat, lon) seperti kebanyakan library JavaScript.
Untuk masukin data lokasi, pakai fungsi ST_SetSRID(ST_MakePoint(lon, lat), 4326):
INSERT INTO toko (nama, alamat, kategori, rating, lokasi) VALUES
('Kopi Kenangan', 'Jl. Sudirman No. 10', 'Kafe', 4.5,
ST_SetSRID(ST_MakePoint(106.8227, -6.1751), 4326)),
('Gramedia Matraman', 'Jl. Matraman No. 46', 'Toko Buku', 4.2,
ST_SetSRID(ST_MakePoint(106.8567, -6.1944), 4326)),
('Mall Grand Indonesia', 'Jl. M.H. Thamrin No. 1', 'Mall', 4.7,
ST_SetSRID(ST_MakePoint(106.8218, -6.1952), 4326)),
('RS Cipto Mangunkusomo', 'Jl. Diponegoro No. 71', 'Rumah Sakit', 4.0,
ST_SetSRID(ST_MakePoint(106.8456, -6.1898), 4326)),
('Bakmi GM Pecenongan', 'Jl. Pecenongan No. 72', 'Restoran', 4.3,
ST_SetSRID(ST_MakePoint(106.8301, -6.1710), 4326));
Kalau data kamu sudah ada dalam format CSV dengan kolom latitude dan longitude, bisa pakai COPY command:
# Import CSV ke tabel sementara dulu
COPY toko_raw(nama, alamat, kategori, rating, lat, lon)
FROM '/tmp/data_toko.csv' DELIMITER ',' CSV HEADER;
# Lalu insert ke tabel utama dengan konversi geometri
INSERT INTO toko (nama, alamat, kategori, rating, lokasi)
SELECT nama, alamat, kategori, rating,
ST_SetSRID(ST_MakePoint(lon, lat), 4326)
FROM toko_raw;
Ini query paling umum. Misal user mau cari toko dalam radius 3 km dari lokasi mereka:
-- Cari toko dalam radius 3 km dari titik (-6.1751, 106.8227)
SELECT nama, alamat, kategori, rating,
ST_Distance(
lokasi::geography,
ST_SetSRID(ST_MakePoint(106.8227, -6.1751), 4326)::geography
) AS jarak_meter
FROM toko
WHERE ST_DWithin(
lokasi::geography,
ST_SetSRID(ST_MakePoint(106.8227, -6.1751), 4326)::geography,
3000 -- radius dalam meter
)
ORDER BY jarak_meter;
Kenapa pakai ::geography? Karena ST_DWithin dengan tipe geography menghitung jarak di permukaan bumi (meter), bukan derajat. Tanpa cast ke geography, 3000 itu artinya 3000 derajat, bukan 3000 meter.
Misal kamu punya polygon wilayah Jakarta Selatan dan mau cek apakah suatu toko ada di dalamnya:
-- Definisi polygon Jakarta Selatan (simplified)
WITH jaksel AS (
SELECT ST_GeomFromText(
'POLYGON((106.78 -6.26, 106.85 -6.26, 106.85 -6.18, 106.78 -6.18, 106.78 -6.26))',
4326
) AS geom
)
SELECT t.nama, t.alamat
FROM toko t, jaksel j
WHERE ST_Within(t.lokasi, j.geom);
Kalau kamu punya data polygon (misal area kebun, lahan, atau zona), bisa hitung luasnya:
-- Hitung luas dalam meter persegi (pakai geography)
SELECT nama,
ST_Area(geom::geography) AS luas_m2,
ST_Area(geom::geography) / 10000 AS luas_hektar
FROM zona_wilayah;
Buffer itu area di sekitar objek. Misal kamu mau bikin zona larangan bangun 100 meter dari sungai:
-- Buat buffer 100 meter di sekitar sungai
SELECT nama,
ST_Buffer(lokasi::geography, 100)::geometry AS buffer_geom
FROM sungai;
-- Atau cari bangunan yang masuk zona buffer
SELECT b.nama_bangunan
FROM bangunan b, sungai s
WHERE ST_DWithin(b.lokasi::geography, s.lokasi::geography, 100);
Mau cari 5 toko terdekat dari posisi user? Pakai operator <-> yang sudah di-index:
-- 5 toko terdekat dari titik user
SELECT nama, alamat, kategori,
ST_Distance(
lokasi::geography,
ST_SetSRID(ST_MakePoint(106.8227, -6.1751), 4326)::geography
) AS jarak_meter
FROM toko
ORDER BY lokasi <-> ST_SetSRID(ST_MakePoint(106.8227, -6.1751), 4326)
LIMIT 5;
Operator <-> itu KNN operator yang pakai spatial index. Jauh lebih cepat dari ORDER BY dengan ST_Distance biasa, apalagi kalau datanya jutaan row.
Tanpa index, query spasial bakal full table scan. Bayangkan scan 1 juta row tiap kali user buka peta - pasti lemot. PostGIS pakai GIST index:
-- Buat spatial index
CREATE INDEX idx_toko_lokasi ON toko USING GIST (lokasi);
-- Cek apakah index sudah dipakai
EXPLAIN ANALYZE
SELECT nama FROM toko
WHERE ST_DWithin(
lokasi::geography,
ST_SetSRID(ST_MakePoint(106.8227, -6.1751), 4326)::geography,
3000
);
Di output EXPLAIN ANALYZE, cari baris yang ada "Index Scan" atau "Bitmap Index Scan". Kalau muncul "Seq Scan", berarti index belum dipakai - cek apakah SRID-nya cocok dan apakah query-nya benar.
PostGIS bisa output data langsung ke GeoJSON. Di backend PHP/CodeIgniter, tinggal query dan kirim ke frontend:
// Controller CodeIgniter 4
public function getToko()
{
$lat = $this->request->getGet('lat');
$lon = $this->request->getGet('lon');
$radius = $this->request->getGet('radius') ?? 3000;
$sql = "SELECT nama, alamat, kategori, rating,
ST_X(lokasi) AS lng, ST_Y(lokasi) AS lat,
ST_Distance(
lokasi::geography,
ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography
) AS jarak
FROM toko
WHERE ST_DWithin(
lokasi::geography,
ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography,
?
)
ORDER BY jarak";
$db = \Config\Database::connect();
$result = $db->query($sql, [$lon, $lat, $lon, $lat, $radius])->getResult();
return $this->response->setJSON($result);
}
Di frontend pakai Leaflet.js:
// Fetch data toko terdekat
async function cariToko(lat, lon, radius) {
const resp = await fetch(
`/api/toko?lat=${lat}&lon=${lon}&radius=${radius}`
);
const data = await resp.json();
data.forEach(toko => {
L.marker([toko.lat, toko.lng])
.addTo(map)
.bindPopup(`
<b>${toko.nama}</b><br>
${toko.alamat}<br>
Rating: ${toko.rating} | ${toko.jarak}m
`);
});
}
// Auto-detect lokasi user
navigator.geolocation.getCurrentPosition(pos => {
const { latitude, longitude } = pos.coords;
map.setView([latitude, longitude], 14);
cariToko(latitude, longitude, 3000);
});
Kalau kamu punya data shapefile (format standar GIS), bisa import langsung pakai shp2pgsql:
# Convert shapefile ke SQL dan import
shp2pgsql -s 4326:4326 -I /path/to/batas_wilayah.shp public.batas_wilayah | \
psql -U appuser -d nama_database
# Atau pakai ogr2ogr (dari GDAL)
ogr2ogr -f PostgreSQL PG:"dbname=nama_database user=appuser" \
/path/to/data.shp -nln tabel_nama -overwrite
Kalau belum punya GDAL, install dengan:
sudo apt install gdal-bin
Beberapa hal yang sering bikin developer baru kesulitan:
PostGIS itu tools wajib buat developer yang bangun aplikasi berbasis peta. Sekali kamu terbiasa dengan query spasial, kamu bakal males balik ke cara konvensional pakai rumus Haversine di application layer. Semua perhitungan terjadi di database level, lebih cepat dan lebih scalable.
Kalau mau coba, mulai dari dataset kecil dulu - misal 1000 titik lokasi toko di kotamu. Bikin query radius, tambahin Leaflet di frontend, dan rasakan sendiri bedanya. PostGIS gratis, PostgreSQL gratis, Leaflet juga gratis. Tidak ada alasan untuk tidak mencoba.