diff --git a/basemap.html b/basemap.html new file mode 100644 index 0000000..d2db26e --- /dev/null +++ b/basemap.html @@ -0,0 +1,83 @@ + + + + + +WebGIS - Peta Referensi Kota Pontianak + + + + + + + + + + + + + + + +
+ + +
+
+
+
+
+ + +
+
+ + + +
+ + + + + + + + + + diff --git a/index.html b/index.html index f518ebb..da61d43 100644 --- a/index.html +++ b/index.html @@ -46,6 +46,12 @@

Bidang Persil Tanah

Pemetaan bidang tanah bersertifikat lengkap dengan informasi status kepemilikan.

+ + +
🏙️
+

Peta Referensi Pontianak

+

Batas kecamatan, jaringan jalan, dan sungai besar Kota Pontianak dari data GeoJSON.

+
diff --git a/jalan.html b/jalan.html index 0ded2fc..3c9837b 100644 --- a/jalan.html +++ b/jalan.html @@ -81,6 +81,7 @@ + diff --git a/js/app-core.js b/js/app-core.js index 06d6e44..5823f67 100644 --- a/js/app-core.js +++ b/js/app-core.js @@ -35,14 +35,20 @@ const layers = { persilSHM: L.layerGroup(), persilHGB: L.layerGroup(), persilHGU: L.layerGroup(), - persilHP: L.layerGroup() + persilHP: L.layerGroup(), + // Layer referensi GeoJSON statis + basemap_kecamatan: L.layerGroup(), + basemap_jalan: L.layerGroup(), + basemap_sungai: L.layerGroup() }; const layerVisible = { rumahIbadah: true, ibadahRadius: true, pendudukMiskin: true, spbu24: true, spbuNon24: true, jalanNasional: true, jalanProvinsi: true, jalanKabupaten: true, - persilSHM: true, persilHGB: true, persilHGU: true, persilHP: true + persilSHM: true, persilHGB: true, persilHGU: true, persilHP: true, + // Basemap GeoJSON (default tampil semua) + basemap_kecamatan: true, basemap_jalan: true, basemap_sungai: true }; const searchEntries = []; @@ -320,7 +326,8 @@ function initPage(tab) { kemiskinan: ['rumahIbadah', 'ibadahRadius', 'pendudukMiskin'], spbu: ['spbu24', 'spbuNon24'], jalan: ['jalanNasional', 'jalanProvinsi', 'jalanKabupaten'], - persil: ['persilSHM', 'persilHGB', 'persilHGU', 'persilHP'] + persil: ['persilSHM', 'persilHGB', 'persilHGU', 'persilHP'], + basemap: ['basemap_kecamatan', 'basemap_jalan', 'basemap_sungai'] }; if (tabLayers[tab]) { @@ -329,8 +336,14 @@ function initPage(tab) { }); } - const titles = { kemiskinan: 'Ibadah & Kemiskinan', spbu: 'SPBU', jalan: 'Data Jalan', persil: 'Persil Tanah' }; - document.getElementById('panel-title').textContent = titles[tab]; + const titles = { + kemiskinan: 'Ibadah & Kemiskinan', + spbu: 'SPBU', + jalan: 'Data Jalan', + persil: 'Persil Tanah', + basemap: 'Peta Referensi Pontianak' + }; + document.getElementById('panel-title').textContent = titles[tab] || tab; renderPanel(tab); } @@ -351,6 +364,7 @@ async function renderPanel(tab) { if (tab === 'spbu') await renderSpbuPanel(pb); if (tab === 'jalan') await renderJalanPanel(pb); if (tab === 'persil') await renderPersilPanel(pb); + if (tab === 'basemap') await renderBasemapPanel(pb); } function findGeomLayer(type, id) { diff --git a/js/feature-basemap.js b/js/feature-basemap.js new file mode 100644 index 0000000..041137e --- /dev/null +++ b/js/feature-basemap.js @@ -0,0 +1,168 @@ +// ============================================================ +// feature-basemap.js +// Layer referensi statis dari GeoJSON lokal: +// - Batas Kecamatan Kota Pontianak (Admin_Kecamatan) +// - Jaringan Jalan (Jaringan_Jalan) +// - Sungai Besar (Sungai_Besar) +// ============================================================ + +const basemapCfg = { + kecamatan: { + label: 'Batas Kecamatan', + file: 'assets/map/Admin_Kecamatan.geojson', + color: '#7c3aed', + fillColor: '#7c3aed', + fillOpacity: 0.08, + weight: 2.5, + icon: '🏙️', + popupProp: ['KECAMATAN', 'KELURAHAN', 'KODE_KEC', 'LUAS_HA'], + popupLabel: { KECAMATAN: 'Kecamatan', KELURAHAN: 'Kelurahan', KODE_KEC: 'Kode', LUAS_HA: 'Luas (ha)' } + }, + jalan: { + label: 'Jaringan Jalan', + file: 'assets/map/Jaringan_Jalan.geojson', + color: '#d97706', + weight: 1.8, + fillOpacity: 0, + icon: '🛤️', + popupProp: ['NAMOBJ', 'REMARK', 'FCODE'], + popupLabel: { NAMOBJ: 'Nama', REMARK: 'Keterangan', FCODE: 'Kode' } + }, + sungai: { + label: 'Sungai Besar', + file: 'assets/map/Sungai_Besar.geojson', + color: '#0ea5e9', + fillColor: '#0ea5e9', + fillOpacity: 0.25, + weight: 1.5, + icon: '🌊', + popupProp: ['NAMOBJ', 'REMARK', 'FCODE'], + popupLabel: { NAMOBJ: 'Nama', REMARK: 'Keterangan', FCODE: 'Kode' } + } +}; + +// Cache untuk data GeoJSON yang sudah di-fetch agar tidak fetch ulang +const _basemapCache = {}; + +/** + * Fetch GeoJSON dan buat Leaflet GeoJSON layer dengan style + popup. + */ +async function loadBasemapLayer(key) { + const cfg = basemapCfg[key]; + const layerKey = 'basemap_' + key; + + // Jika layer sudah ada isinya, tidak perlu reload + let hasLayers = false; + layers[layerKey].eachLayer(() => { hasLayers = true; }); + if (hasLayers) return; + + try { + // Gunakan cache jika sudah pernah di-fetch + let geojsonData = _basemapCache[key]; + if (!geojsonData) { + const resp = await fetch(cfg.file); + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); + geojsonData = await resp.json(); + _basemapCache[key] = geojsonData; + } + + const styleOpts = { + color: cfg.color, + weight: cfg.weight, + fillOpacity: cfg.fillOpacity ?? 0, + fillColor: cfg.fillColor || cfg.color, + opacity: 0.9 + }; + + const geoLayer = L.geoJSON(geojsonData, { + style: styleOpts, + pointToLayer: (feature, latlng) => + L.circleMarker(latlng, { radius: 5, ...styleOpts }), + onEachFeature: (feature, layer) => { + const props = feature.properties || {}; + // Cari nama property (case-insensitive fallback) + const findVal = (keys) => { + for (const k of keys) { + const found = Object.keys(props).find(p => p.toUpperCase() === k.toUpperCase()); + if (found && props[found]) return props[found]; + } + return null; + }; + + const rows = cfg.popupProp + .map(p => { + const found = Object.keys(props).find(k => k.toUpperCase() === p.toUpperCase()); + const val = found ? props[found] : null; + if (!val) return ''; + return `

${cfg.popupLabel[p] || p}: ${val}

`; + }) + .filter(Boolean) + .join(''); + + const nameKeys = ['NAMOBJ', 'NAMA', 'NAME', 'KECAMATAN', 'KELURAHAN']; + const title = findVal(nameKeys) || cfg.label; + + if (rows) { + layer.bindPopup(`

${cfg.icon} ${esc(title)}

${rows}`, { maxWidth: 280 }); + } + } + }); + + layers[layerKey].addLayer(geoLayer); + } catch (err) { + console.warn(`[basemap] Gagal memuat ${cfg.file}:`, err); + toast(`Gagal memuat layer ${cfg.label}`, 'error'); + } +} + +/** + * Render panel kontrol untuk layer basemap di sidebar. + * Dipanggil dari renderPanel() di app-core.js jika tab === 'basemap'. + */ +async function renderBasemapPanel(pb) { + pb.innerHTML = ` +
+
Layer Referensi Kota Pontianak
+ ${Object.entries(basemapCfg).map(([key, cfg]) => { + const lk = 'basemap_' + key; + const checked = layerVisible[lk]; + return ` +
+
${checked ? '✓' : ''}
+
+
${cfg.icon} ${cfg.label}
+
`; + }).join('')} +
+
+
Informasi
+
+ Data batas administrasi kecamatan, jaringan jalan, dan sungai besar Kota Pontianak.
+ Klik objek di peta untuk melihat informasi detail.
+ Centang/hilang centang untuk tampil/sembunyikan layer. +
+
`; + + // Load semua layer yang aktif + const loadPromises = Object.keys(basemapCfg) + .filter(key => layerVisible['basemap_' + key]) + .map(key => loadBasemapLayer(key)); + await Promise.all(loadPromises); +} + +/** + * Toggle visibilitas layer basemap dan load data jika belum ada. + */ +window.toggleBasemapLayer = async function(key, el) { + const lk = 'basemap_' + key; + layerVisible[lk] = !layerVisible[lk]; + el.classList.toggle('checked', layerVisible[lk]); + el.querySelector('.layer-check').textContent = layerVisible[lk] ? '✓' : ''; + + if (layerVisible[lk]) { + await loadBasemapLayer(key); + map.addLayer(layers[lk]); + } else { + map.removeLayer(layers[lk]); + } +}; diff --git a/kemiskinan.html b/kemiskinan.html index 3bb3863..67fc3ed 100644 --- a/kemiskinan.html +++ b/kemiskinan.html @@ -81,6 +81,7 @@ + diff --git a/persil.html b/persil.html index e01a8f9..3fa303a 100644 --- a/persil.html +++ b/persil.html @@ -81,6 +81,7 @@ + diff --git a/spbu.html b/spbu.html index f98a80e..0a94ddb 100644 --- a/spbu.html +++ b/spbu.html @@ -81,6 +81,7 @@ +