Alur kueri viewport dari pergerakan peta hingga render
Alur kueri viewport dari pergerakan peta hingga render. Ilustrasi konsep · Klik untuk memperbesar.
Contoh peta dengan cluster pada zoom rendah dan titik pada zoom tinggi
Contoh peta dengan cluster pada zoom rendah dan titik pada zoom tinggi. Ilustrasi konsep · Klik untuk memperbesar.

Peta bisa terasa lambat meskipun query database selesai dengan cepat. Browser masih perlu mengunduh geometri, memproses JSON, membuat objek, dan menggambar ribuan label sambil merespons pergeseran peta. Jika seluruh dataset dikirim dalam satu request, browser harus menangani semua data itu sekaligus.

API berbasis viewport mengembalikan data untuk wilayah yang terlihat, dengan detail sesuai tingkat zoom. Saat peta menampilkan satu provinsi, misalnya, response bisa berisi cluster. Saat diperbesar sampai tingkat jalan, response bisa berisi titik individual. Geometri lengkap baru diambil ketika pengguna memilih fitur. Tentukan bentuk response ini dalam kontrak server agar jumlah data yang diminta setiap kali peta bergerak tetap terbatas.

Tentukan format bounds sebelum menulis SQL

Gunakan konvensi koordinat yang tegas: west, south, east, north dalam bujur dan lintang, ditambah zoom bilangan bulat serta filter yang dikenal server. GeoJSON menempatkan bujur terlebih dahulu. Koordinat yang tertukar masih bisa berupa angka valid tetapi menunjuk benua yang salah. Kesalahan seperti ini sering lebih sulit ditemukan daripada kesalahan sintaks.

Pada bounding box biasa, west lebih kecil daripada east. Bounding box yang melintasi antimeridian berbeda: west bisa bernilai 170 dan east -175. Wilayah itu adalah jalur sempit di sekitar garis tanggal, bukan hampir seluruh dunia. Pecah menjadi dua envelope, satu berakhir pada 180 dan satu dimulai pada -180.

Beberapa library peta memberikan bounds dengan nilai bujur di luar rentang biasa setelah pengguna menggeser peta berkali-kali. Normalisasikan nilainya melalui adapter sebelum mengirim request. Tentukan juga cara menyatakan viewport yang mencakup seluruh dunia. Jika kedua ujung dinormalisasi secara terpisah, wilayah itu bisa terbaca sebagai bounding box kosong. Batas lintang bergantung pada proyeksi. Antarmuka Web Mercator sebaiknya menolak lintang di luar rentang yang didukung karena proyeksi ini tidak dapat menampilkan kutub.

Pastikan query PostGIS bisa memakai indeks

Untuk lokasi titik yang disimpan sebagai geometry dengan SRID 4326, indeks GiST mendukung penyaringan bounding box. Pertahankan kolom terindeks dalam sistem referensi aslinya ketika menyaring. Melakukan transformasi setiap titik di klausa WHERE dapat membuat indeks biasa tidak terpakai, kecuali tersedia expression index yang sesuai.

CREATE INDEX place_geom_gist ON place USING GIST (geom);

SELECT id, name, ST_X(geom) AS longitude, ST_Y(geom) AS latitude
FROM place
WHERE tenant_id = %(tenant_id)s
  AND geom && ST_MakeEnvelope(
    %(west)s, %(south)s, %(east)s, %(north)s, 4326
  )
ORDER BY id
LIMIT %(limit_plus_one)s;

Query berparameter ini menangani satu envelope yang tidak melintasi antimeridian. Untuk data titik, pemeriksaan bounding box sudah cukup untuk menentukan apakah titik berada di dalam viewport. Untuk garis dan poligon, pemeriksaan ini hanya menyaring kandidat. Gunakan predikat spasial seperti ST_Intersects untuk memeriksa geometri secara tepat jika false positive menjadi masalah. Jangan mengirim seluruh geometri poligon hanya untuk menempatkan label.

Untuk request yang melintasi antimeridian, periksa kedua envelope menggunakan predikat OR atau dua cabang query yang dibatasi. Hilangkan duplikasi berdasarkan ID fitur sebelum menerapkan batas hasil akhir. Fitur yang terletak di perbatasan dapat muncul dua kali saat hasil dipecah menjadi beberapa wilayah atau tile. Periksa query plan dengan EXPLAIN (ANALYZE, BUFFERS) pada data yang mewakili penggunaan aplikasi. Query belum tentu cepat meskipun sudah ada indeks. Untuk bounding box yang mencakup seluruh dunia, sequential scan bisa menjadi pilihan yang masuk akal.

Zoom menentukan bentuk response

Batas jumlah hasil mencegah payload membesar tanpa sengaja hingga berukuran megabyte. Namun, LIMIT saja dapat membuat sebagian fitur tidak muncul tanpa alasan yang jelas bagi pengguna. Minta satu baris tambahan untuk mendeteksi hasil yang terpotong, lalu kirim flag truncated dan saran tingkat zoom. Jelaskan bahwa hasilnya belum lengkap jika hanya sebagian fitur yang dikirim.

Pada zoom rendah, kirim cluster atau sel kepadatan dan dokumentasikan apa yang dihitung. Pada zoom tinggi, kirim fitur individual dalam jumlah terbatas. Sel grid yang stabil lebih mudah di-cache daripada cluster yang anggotanya berubah setiap kali viewport bergeser. Hitung sel dalam sistem koordinat terproyeksi yang sesuai. Pembulatan bujur dan lintang menghasilkan sel dengan ukuran fisik yang tidak seragam.

Menghitung agregat dari jutaan baris untuk satu viewport tetap bisa memakan banyak sumber daya. Untuk tampilan wilayah luas, pertimbangkan agregat yang sudah dihitung sebelumnya atau vector tile. Setiap tile bisa di-cache dan dimuat secara terpisah, tetapi clipping, duplikasi di batas tile, dan invalidasi cache tetap perlu ditangani. Jelaskan apakah angka yang ditampilkan menghitung seluruh anggota cluster atau hanya anggota yang terlihat. Gunakan ID fitur yang stabil ketika geometri yang sama muncul di tile bersebelahan.

Tentukan ambang zoom dan detail maksimum di server. Validasi nilai zoom dari browser dan batasi request agar tidak bisa mengambil semua fitur sekaligus. Batasi luas wilayah untuk request detail, ukuran response dalam byte, dan jumlah titik. Pasang juga statement timeout pada database. Untuk geometri besar, penyederhanaan atau endpoint detail berbasis tile lebih aman daripada satu response GeoJSON yang terlalu besar.

Batalkan pekerjaan dan tolak hasil lama

Gunakan debounce agar request menunggu hingga pergerakan peta berhenti sejenak. Jeda singkat ini mengurangi jumlah request selama pengguna menggeser peta, dibandingkan mengirim request pada setiap frame animasi. Tetap tampilkan fitur lama beserta indikator loading sambil menunggu data pengganti. Mengosongkan layer setiap kali request dimulai membuat peta berkedip.

Pembatalan saja belum cukup. Response bisa selesai tepat sebelum abort dipanggil, atau parsing bisa masih berjalan saat pembatalan terjadi. Gunakan abort controller bersama generation counter:

let generation = 0;
let controller;
let timer;

function scheduleViewport(query, render, showError) {
  const requestId = ++generation;
  clearTimeout(timer);
  controller?.abort();
  timer = setTimeout(async () => {
    controller = new AbortController();
    try {
      const response = await fetch(`/api/places?${query}`, {
        signal: controller.signal,
      });
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      const result = await response.json();
      if (requestId === generation) render(result);
    } catch (error) {
      if (requestId === generation && error.name !== 'AbortError') {
        showError(error);
      }
    }
  }, 180);
}

function disposeViewportLoader() {
  generation += 1;
  clearTimeout(timer);
  controller?.abort();
}

Sesuaikan nilai jeda pada contoh dengan kebutuhan peta. Saat request dijadwalkan, kenaikan nomor generasi langsung membuat request sebelumnya tidak berlaku, termasuk selama jeda debounce. Panggil fungsi cleanup ketika komponen peta di-unmount. Sertakan filter dalam identitas request bersama bounds dan zoom supaya response untuk filter lama tidak mengganti layer terbaru.

Abort di browser tidak menjamin reverse proxy, worker aplikasi, atau database berhenti bekerja. Timeout server dan query terbatas tetap diperlukan. Jika beberapa komponen memakai sumber data sama, gabungkan request identik yang sedang berjalan agar tidak mengulang pekerjaan.

Validasi input, ukur server dan browser

Pastikan setiap koordinat bisa dibaca sebagai angka finite. Tolak bounding box yang tidak valid, urutan batas lintang yang terbalik, tingkat zoom yang tidak didukung, filter yang tidak dikenal, dan query string yang terlalu panjang. Terapkan otorisasi tenant sebelum penyaringan spasial. Jumlah anggota cluster pun bisa membocorkan informasi privat, sama seperti data titik individual. Cache key harus memuat cakupan otorisasi, filter, versi representasi, identitas bounds atau tile, serta kelompok zoom.

Batasi juga beban rendering di browser. Ribuan marker DOM bisa lebih berat daripada layer WebGL dengan lebih banyak titik. Batasi label, gunakan canvas atau WebGL untuk data padat, dan ambil detail popup saat diperlukan. Web worker bisa memindahkan parsing atau pengindeksan dari main thread, tetapi menyalin payload besar antar-thread tetap membutuhkan waktu dan memori.

Ukur durasi query database, ukuran response, waktu parsing JSON, durasi pembaruan layer, dan latensi interaksi secara terpisah. Uji hasil kosong, kota dengan data padat, perubahan filter yang cepat, jaringan lambat, dan pergeseran peta melewati garis tanggal. Pastikan hasil request lama tidak mengganti tampilan viewport saat ini. Periksa juga bahwa beban untuk tampilan wilayah luas tetap terbatas meskipun dataset bertambah besar.

Referensi