169 lines
5.7 KiB
JavaScript
169 lines
5.7 KiB
JavaScript
// ============================================================
|
|
// 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 `<p><b>${cfg.popupLabel[p] || p}:</b> ${val}</p>`;
|
|
})
|
|
.filter(Boolean)
|
|
.join('');
|
|
|
|
const nameKeys = ['NAMOBJ', 'NAMA', 'NAME', 'KECAMATAN', 'KELURAHAN'];
|
|
const title = findVal(nameKeys) || cfg.label;
|
|
|
|
if (rows) {
|
|
layer.bindPopup(`<h4>${cfg.icon} ${esc(title)}</h4>${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 = `
|
|
<div class="layer-section">
|
|
<div class="layer-section-title">Layer Referensi Kota Pontianak</div>
|
|
${Object.entries(basemapCfg).map(([key, cfg]) => {
|
|
const lk = 'basemap_' + key;
|
|
const checked = layerVisible[lk];
|
|
return `
|
|
<div class="layer-item ${checked ? 'checked' : ''}" id="basemap-item-${key}" onclick="toggleBasemapLayer('${key}', this)">
|
|
<div class="layer-check">${checked ? '✓' : ''}</div>
|
|
<div class="layer-dot" style="background:${cfg.color};${cfg.fillOpacity > 0 ? '' : 'border-radius:2px;height:4px;width:18px;border-radius:2px'}"></div>
|
|
<div class="layer-label">${cfg.icon} ${cfg.label}</div>
|
|
</div>`;
|
|
}).join('')}
|
|
</div>
|
|
<div class="layer-section">
|
|
<div class="layer-section-title">Informasi</div>
|
|
<div style="font-size:11px;line-height:1.8;color:var(--text2);padding:10px 12px;background:var(--bg2);border:1px solid var(--border);border-radius:var(--radius)">
|
|
Data batas administrasi kecamatan, jaringan jalan, dan sungai besar Kota Pontianak.<br>
|
|
Klik objek di peta untuk melihat informasi detail.<br>
|
|
Centang/hilang centang untuk tampil/sembunyikan layer.
|
|
</div>
|
|
</div>`;
|
|
|
|
// 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]);
|
|
}
|
|
};
|