Beberapa tahun lalu saya dapat project WebGIS sederhana: menampilkan lokasi toko di peta dan menghitung jarak dari posisi user. Awalnya saya pikir cukup pakai rumus haversine manual, tinggal Math.atan2 dikit-dikit. Tapi begitu client minta fitur "cari toko dalam radius 5 km", "cek apakah titik ini masuk area banjir", sampai "hitung luas lahan dari polygon yang digambar user di peta", saya sadar: menulis semua rumus geometri sendiri itu buang-buang waktu dan rawan bug.
Waktu itu saya baru nemu Turf.js, library JavaScript untuk analisis spasial yang jalan langsung di browser. Semua fungsi yang saya sebut di atas ternyata sudah ada, tinggal pakai. Artikel ini saya tulis buat kamu yang baru mau mulai ngulik WebGIS atau kebetulan dapat project yang butuh manipulasi GeoJSON. Saya kasih contoh nyata yang bisa langsung kamu copy-paste, lengkap dengan pitfall yang pernah bikin saya garuk-garuk kepala.
Apa Itu Turf.js?
Turf.js adalah library JavaScript open-source untuk geospatial analysis. Dia modular: kamu bisa import cuma fungsi yang kamu butuh, nggak harus bawa seluruh library. Turf jalan di browser maupun Node.js, jadi cocok buat frontend, backend, bahkan script ETL data spasial.
Data yang diproses Turf semuanya berbasis GeoJSON, format JSON standar untuk data geografis. Ini penting karena formatnya plain text: gampang disimpan di MySQL atau PostgreSQL, gampang dikirim lewat REST API, dan gampang dirender di Leaflet, Mapbox, atau OpenLayers. Jadi kalau kamu sudah terbiasa dengan JSON, sebenarnya kamu sudah setengah jalan menguasai Turf.
Beberapa fungsi Turf yang paling sering dipakai di dunia nyata:
- turf.distance - hitung jarak antar dua titik
- turf.buffer - buat area radius di sekitar geometry
- turf.booleanPointInPolygon - cek apakah sebuah titik berada di dalam polygon
- turf.area - hitung luas polygon dalam meter persegi
- turf.union - gabungkan dua polygon jadi satu
- turf.simplify - kurangi jumlah koordinat biar render lebih cepat
Bayangkan Turf seperti "lodash-nya data spasial". Alih-alih nulis fungsi geometri sendiri, kamu pakai fungsi yang sudah diuji ribuan developer di seluruh dunia. Library ini dipakai di banyak production app, dari dashboard logistik sampai aplikasi properti, jadi bukan library abal-abal.
Turf.js vs PostGIS vs Hitung Manual
Sebelum masuk kode, saya jelaskan dulu kapan harus pakai Turf, kapan pakai PostGIS, dan kapan cukup rumus manual. Ini keputusan arsitektur yang sering salah di awal project:
- Hitung manual (haversine, dll): cukup buat 1-2 perhitungan sederhana, misal jarak dua titik sekali jalan. Tapi begitu butuh buffer, intersect, atau union, kodenya meledak jadi ratusan baris dan susah di-test.
- Turf.js di browser: paling pas buat interaksi real-time yang butuh feedback instan - user geser marker, langsung muncul jarak; user gambar polygon, langsung muncul luas. Semua jalan di sisi client, nggak ada round-trip ke server.
- PostGIS di server: paling pas buat query spasial di dataset besar - "cari 1000 toko terdekat dari 5 juta baris data" jelas lebih cepat pakai index GiST PostGIS daripada ngirim 5 juta titik ke browser. Kombinasi ideal: PostGIS untuk query besar, Turf untuk interaksi ringan di client.
Buat project skala kecil sampai menengah, Turf doang biasanya sudah cukup. Kalau performa mulai terasa lambat, baru pindahkan bagian berat ke PostGIS.
Setup: CDN atau NPM?
Buat project yang masih tahap eksperimen atau mau coba cepat, pakai CDN aja. Satu tag script, semua fungsi Turf langsung tersedia lewat objek global turf:
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
<script>
const jarak = turf.distance(
turf.point([110.3694, -7.8014]),
turf.point([110.8317, -7.5755]),
{ units: 'kilometers' }
);
console.log('Jarak Jogja ke Solo:', jarak.toFixed(2), 'km');
</script>
Buat project production, lebih baik pakai npm biar versinya terkunci dan bundle-nya bisa di-tree-shake:
npm install @turf/turf
# atau kalau mau bundle paling kecil, install per modul:
npm install @turf/distance @turf/buffer @turf/boolean-point-in-polygon @turf/area
// Import per modul - bundle lebih kecil
import distance from '@turf/distance';
import { point } from '@turf/helpers';
const jarak = distance(
point([110.3694, -7.8014]),
point([110.8317, -7.5755]),
{ units: 'kilometers' }
);
Catatan: install per modul memang agak ribet karena nama package-nya pakai dash (misal @turf/boolean-point-in-polygon), tapi hasil bundle-nya bisa beda jauh. Buat aplikasi yang nggak terlalu peduli ukuran bundle, @turf/turf lengkap lebih simpel.
Primer Singkat GeoJSON
Sebelum ngoding, kamu wajib paham struktur GeoJSON. Ada tiga geometry dasar yang paling sering dipakai:
// Point: satu koordinat [lng, lat]
const titik = turf.point([110.3694, -7.8014]);
// LineString: array koordinat yang membentuk garis
const jalur = turf.lineString([
[110.3694, -7.8014],
[110.4000, -7.7900],
[110.4317, -7.7800]
]);
// Polygon: array of rings, ring pertama outer, sisanya hole
const area = turf.polygon([[
[110.36, -7.82],
[110.40, -7.82],
[110.40, -7.78],
[110.36, -7.78],
[110.36, -7.82] // wajib kembali ke titik awal
]]);
// Kumpulan feature: FeatureCollection
const kumpulan = turf.featureCollection([titik, area]);
Perhatikan urutan koordinat: longitude (bujur) dulu, baru latitude (lintang). Ini sumber error nomor satu di dunia GIS. Kita terbiasa bilang "lintang bujur", tapi GeoJSON, Leaflet, Mapbox, dan Turf semuanya pakai format [lng, lat]. Kalau terbalik, titik kamu bisa meleset ke samudra dan error-nya susah dilacak karena secara format tetap valid.
Contoh 1: Hitung Jarak Antar Titik
Contoh paling dasar dan paling sering dicari. Misal kamu punya koordinat dua kota dan mau tahu jarak garis lurusnya:
const jogja = turf.point([110.3694, -7.8014]);
const solo = turf.point([110.8317, -7.5755]);
const jarak = turf.distance(jogja, solo, { units: 'kilometers' });
console.log(`Jarak Jogja - Solo: ${jarak.toFixed(2)} km`);
// Output: Jarak Jogja - Solo: 56.81 km
Fungsi turf.point cuma bikin object GeoJSON Point dari array [lng, lat]. Unit yang didukung turf.distance antara lain kilometers, miles, meters, feet, degrees, dan radians. Perhitungannya memakai formula haversine yang memperhitungkan lengkung bumi, jadi hasilnya jauh lebih akurat daripada sekadar rumus jarak euclidean di bidang datar.
Contoh 2: Buffer - Cari Semua Toko dalam Radius 5 km
Ini fitur yang dulu bikin saya pusing setengah hari. Secara konsep gampang: ambil posisi user, bikin lingkaran radius 5 km, lalu cek toko mana yang masuk. Di Turf, implementasinya cuma beberapa baris:
const posisiUser = turf.point([110.3694, -7.8014]); // Titik nol km Malioboro
const areaCari = turf.buffer(posisiUser, 5, { units: 'kilometers' });
const tokoDalamRadius = daftarToko.features.filter(toko => {
return turf.booleanPointInPolygon(toko, areaCari);
});
console.log(`Ada ${tokoDalamRadius.length} toko dalam radius 5 km`);
turf.buffer menghitung buffer secara geodesic, artinya dia memperhitungkan lengkung bumi, bukan sekadar gambar lingkaran di bidang datar. Hasilnya adalah polygon baru yang bisa langsung kamu render di peta atau kamu pakai buat query lanjutan. Ini fungsi yang sangat powerful buat fitur "cari di sekitar saya" di aplikasi marketplace, properti, atau layanan darurat.
Contoh 3: Cek Titik dalam Polygon (Point in Polygon)
Fitur klasik: user kirim koordinat GPS, kamu cek apakah dia berada di dalam zona tertentu. Misal zona rawan banjir:
const zonaBanjir = turf.polygon([[
[110.36, -7.82],
[110.40, -7.82],
[110.40, -7.78],
[110.36, -7.78],
[110.36, -7.82]
]]);
const posisiUser = turf.point([110.38, -7.80]);
if (turf.booleanPointInPolygon(posisiUser, zonaBanjir)) {
console.log('Peringatan: kamu berada di zona rawan banjir!');
} else {
console.log('Aman, lokasi kamu di luar zona banjir.');
}
Fungsi ini dipakai di banyak skenario: validasi alamat pengiriman, cek coverage area, zoning, sampai fitur absensi kantor yang membatasi radius. Catatan penting: koordinat polygon harus closed ring, titik pertama harus sama dengan titik terakhir. Kalau nggak, hasil pengecekan bisa salah diam-diam.
Contoh 4: Hitung Luas Polygon
Buat aplikasi pertanahan, properti, atau agrikultur, hitung luas lahan itu kebutuhan utama. User tinggal gambar polygon di peta, lalu luasnya langsung muncul:
const lahan = turf.polygon([[
[110.3694, -7.8014],
[110.3716, -7.8014],
[110.3716, -7.8028],
[110.3694, -7.8028],
[110.3694, -7.8014]
]]);
const luasM2 = turf.area(lahan);
const hektar = luasM2 / 10000;
console.log(`Luas lahan: ${luasM2.toFixed(0)} m2 (${hektar.toFixed(2)} hektar)`);
// Output: Luas lahan: 37675 m2 (3.77 hektar) - angka aktual bisa sedikit berbeda
turf.area selalu mengembalikan meter persegi, apapun CRS asal datanya, karena Turf menghitungnya dengan formula geodesic (spherical). Buat skala petak lahan sampai kecamatan, akurasinya sudah sangat memadai. Kalau butuh akurasi kadaster level sertifikat tanah, baru pertimbangkan transformasi ke CRS proyeksi seperti UTM dulu.
Contoh 5: Gabungkan Polygon dengan Union
Misal kamu punya beberapa polygon wilayah kelurahan dan mau gabung jadi satu wilayah kecamatan untuk analisis:
const kelurahanA = turf.polygon([[
[110.36, -7.82],
[110.39, -7.82],
[110.39, -7.79],
[110.36, -7.79],
[110.36, -7.82]
]]);
const kelurahanB = turf.polygon([[
[110.38, -7.81],
[110.41, -7.81],
[110.41, -7.78],
[110.38, -7.78],
[110.38, -7.81]
]]);
const gabungan = turf.union(turf.featureCollection([kelurahanA, kelurahanB]));
Catatan versi: di Turf v6, turf.union menerima satu FeatureCollection. Di v7, signature-nya berubah menjadi turf.union(polygonA, polygonB). Kalau kamu pakai bundle CDN @turf/turf@6 seperti contoh di atas, ikuti pola FeatureCollection. Inilah kenapa penting mengunci versi library di project production.
Studi Kasus: Fitur "Toko Terdekat" End-to-End
Sekarang kita gabungkan semuanya. Studi kasus: aplikasi katalog UMKM yang menampilkan 3 toko terdekat dari posisi user, lengkap dengan jaraknya. Alih-alih pakai fungsi nearest bawaan (yang di versi terbaru namanya berubah jadi nearestPoint), saya tunjukkan pendekatan manual yang lebih fleksibel dan nggak bergantung versi:
const posisiUser = turf.point([110.3694, -7.8014]);
const daftarToko = turf.featureCollection([
turf.point([110.3770, -7.7827], { nama: 'Warung Kopi Sae', kategori: 'kuliner' }),
turf.point([110.4123, -7.8055], { nama: 'Toko Bangunan Jaya', kategori: 'material' }),
turf.point([110.3501, -7.7912], { nama: 'Laundry Express', kategori: 'layanan' }),
turf.point([110.3988, -7.8134], { nama: 'Bengkel Motor Aji', kategori: 'otomotif' })
]);
const denganJarak = daftarToko.features.map(toko => {
const d = turf.distance(posisiUser, toko, { units: 'kilometers' });
return { toko, jarakKm: d };
});
const tigaTerdekat = denganJarak
.sort((a, b) => a.jarakKm - b.jarakKm)
.slice(0, 3);
tigaTerdekat.forEach(item => {
console.log(`${item.toko.properties.nama}: ${item.jarakKm.toFixed(2)} km`);
});
// Output:
// Warung Kopi Sae: 2.24 km
// Laundry Express: 2.41 km
// Bengkel Motor Aji: 3.50 km
Pendekatan ini lebih baik daripada fungsi nearest bawaan karena kamu bisa sort, filter berdasarkan kategori (misal user cuma mau lihat kuliner), dan tetap memegang nilai jaraknya buat ditampilkan ke user. Tambahkan satu baris turf.buffer dan kamu sudah punya fitur "toko dalam radius X km" sekaligus.
Tips Performa untuk Data Besar
Turf itu cepat untuk ratusan feature, tapi kalau datamu sudah ribuan sampai jutaan titik (misal data tracking GPS), kamu harus mikir performa. Beberapa tips yang saya pakai di production:
- Simplify dulu sebelum render.
turf.simplifymengurangi jumlah vertex polygon tanpa mengubah bentuk secara signifikan. Polygon batas desa yang asalnya 50.000 koordinat bisa turun ke 5.000 koordinat dengan tolerance 0.001 - render 10x lebih cepat. - Import per modul, bukan seluruh turf. Bundle
@turf/turflengkap sekitar 100+ KB minified. Kalau cuma butuh distance dan booleanPointInPolygon, import dari package masing-masing biar bundle tetap ramping. - Pindahkan komputasi berat ke Web Worker. Operasi yang butuh waktu lebih dari 100ms sebaiknya jangan jalan di main thread biar UI nggak freeze. Turf murni JavaScript, jadi jalan mulus di worker.
- Hitung di server kalau datanya statis. Buat daftar "toko terdekat" yang datanya jarang berubah, precompute di backend (PostGIS bisa) lalu simpan hasilnya. Browser cukup fetch hasil jadi, nggak perlu hitung ulang.
// Contoh simplify: kurangi detail polygon sebelum dikirim ke Leaflet
const batasDesa = turf.polygon([/* koordinat asli, bisa puluhan ribu vertex */]);
const ringkas = turf.simplify(batasDesa, 0.001, { highQuality: true });
console.log('Vertex asli:', batasDesa.geometry.coordinates[0].length);
console.log('Vertex setelah simplify:', ringkas.geometry.coordinates[0].length);
Pitfall yang Sering Bikin Error
Biar kamu nggak mengalami sakit yang sama seperti saya, ini daftar jebakan umum yang wajib kamu hafal:
- Urutan koordinat [lng, lat]. Sudah saya sebut di atas, tapi ini layak diulang. Nulis [lat, lng] diam-diam bikin titikmu meleset ke samudra. Selalu cek: 110 itu longitude (bujur), -7 itu latitude (lintang).
- Unit tidak konsisten.
turf.distancedefault-nya kilometers, tapi kalau kamu ngasih radius 5 keturf.buffertanpa specify units, hasilnya 5 derajat, bukan 5 km! Selalu tulis{ units: 'kilometers' }secara eksplisit. - Polygon tidak closed ring. Titik pertama harus sama dengan titik terakhir. Banyak data dari Excel atau CSV yang nggak closed, dan
booleanPointInPolygondiam-diam ngasih hasil salah. - GeoJSON invalid. Kalau dapat error aneh, cek dulu validitas datanya. Tool favorit saya: geojson.io buat visualisasi cepat. Error "coordinates must be an array of at least two numbers" biasanya artinya ada null atau string nyasar di data.
- Versi API berubah. Nama fungsi di v6 dan v7 kadang beda (contoh:
nearestvsnearestPoint, atau signatureunionyang berubah). Selalu cek dokumentasi versi yang kamu pakai, dan kunci versi di package.json buat production.
Integrasi dengan Leaflet: Render Hasil Analisis
Analisis spasial paling terasa manfaatnya kalau hasilnya langsung terlihat di peta. Contoh paling umum: user geser marker, lalu area buffer dan daftar toko di dalamnya langsung tampil. Kombinasi Leaflet + Turf sangat populer karena dua-duanya ringan dan API-nya simpel:
// Inisialisasi peta
const map = L.map('map').setView([-7.8014, 110.3694], 13);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// Marker posisi user
const marker = L.marker([-7.8014, 110.3694]).addTo(map);
// Hitung buffer 5 km dengan Turf, render sebagai layer
const posisi = turf.point([110.3694, -7.8014]);
const areaCari = turf.buffer(posisi, 5, { units: 'kilometers' });
L.geoJSON(areaCari, {
style: { color: '#c2185b', weight: 2, fillOpacity: 0.15 }
}).addTo(map);
Pola ini bisa kamu pakai untuk fitur apa saja: area layanan kurir, radius pencarian properti, sampai zona evakuasi bencana. Karena Turf menghasilkan GeoJSON standar, hasilnya bisa langsung dimakan Leaflet, Mapbox GL, atau OpenLayers tanpa konversi tambahan. Itu keunggulan terbesar Turf: ekosistemnya nyambung ke mana-mana.
Kesimpulan
Turf.js mengubah cara saya ngembangin aplikasi GIS. Yang dulu butuh ratusan baris rumus matematika, sekarang cukup satu fungsi yang sudah teruji oleh ribuan developer. Buat kamu yang baru mulai, saran saya: buka turfjs.org, pilih satu fungsi, dan langsung praktik pakai data kota kamu sendiri. Jangan keburu baca semua dokumentasi - cukup mulai dari distance dan booleanPointInPolygon, dua fungsi yang paling sering dipakai di dunia nyata.
Project apa yang lagi kamu bangun? Pernah dapat fitur GIS yang bikin pusing setengah hari? Cerita di kolom komentar, siapa tahu kita bisa bantu pecahin bareng-bareng.