add feature basemap
This commit is contained in:
@@ -0,0 +1,83 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>WebGIS - Peta Referensi Kota Pontianak</title>
|
||||||
|
<meta name="description" content="Peta referensi batas kecamatan, jaringan jalan, dan sungai besar Kota Pontianak." />
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Space+Grotesk:wght@500;700&display=swap" rel="stylesheet" />
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" />
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
|
||||||
|
<link rel="stylesheet" href="css/app.css" />
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/Turf.js/6.5.0/turf.min.js"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<header id="header">
|
||||||
|
<div class="brand">
|
||||||
|
<a href="index.html" style="text-decoration:none; color:inherit"><div class="brand-title">WebGIS Poverty</div></a>
|
||||||
|
<div class="brand-sub">Sistem Informasi Geografis Berbasis Web</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header-search" id="header-search">
|
||||||
|
<div class="search-box">
|
||||||
|
<span class="search-icon">⌕</span>
|
||||||
|
<input id="search-input" type="search" autocomplete="off" placeholder="Telusuri Webgis Poverty" aria-label="Cari objek peta" />
|
||||||
|
</div>
|
||||||
|
<div class="search-results" id="search-results" aria-label="Hasil pencarian"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header-right">
|
||||||
|
<div class="coord-display" id="coord-display">Lat: - | Lng: -</div>
|
||||||
|
<div class="status-pill"><span id="status-zoom">Zoom: 13</span></div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<section id="map-wrap">
|
||||||
|
<div id="instruction-banner"></div>
|
||||||
|
<div id="map"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<aside id="panel">
|
||||||
|
<div id="panel-head">
|
||||||
|
<a href="index.html" class="btn btn-ghost btn-sm" style="margin-bottom: 12px; display: inline-flex; align-items: center; gap: 6px; text-decoration: none;">
|
||||||
|
<span style="font-size: 14px;">←</span> Kembali ke Beranda
|
||||||
|
</a>
|
||||||
|
<h2 id="panel-title">Peta Referensi Pontianak</h2>
|
||||||
|
</div>
|
||||||
|
<div id="panel-body">
|
||||||
|
<div class="loading"><span class="spinner"></span>Memuat data...</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="modal-overlay" aria-hidden="true">
|
||||||
|
<div id="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
|
||||||
|
<div class="modal-head"><h3 id="modal-title">Modal</h3></div>
|
||||||
|
<div id="modal-body"></div>
|
||||||
|
<div id="modal-footer"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="toast-container"></div>
|
||||||
|
|
||||||
|
<script src="js/app-core.js"></script>
|
||||||
|
<script src="js/feature-kemiskinan.js"></script>
|
||||||
|
<script src="js/feature-spbu.js"></script>
|
||||||
|
<script src="js/feature-jalan.js"></script>
|
||||||
|
<script src="js/feature-persil.js"></script>
|
||||||
|
<script src="js/feature-basemap.js"></script>
|
||||||
|
<script>
|
||||||
|
initPage('basemap');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -46,6 +46,12 @@
|
|||||||
<h2 class="feature-title">Bidang Persil Tanah</h2>
|
<h2 class="feature-title">Bidang Persil Tanah</h2>
|
||||||
<p class="feature-desc">Pemetaan bidang tanah bersertifikat lengkap dengan informasi status kepemilikan.</p>
|
<p class="feature-desc">Pemetaan bidang tanah bersertifikat lengkap dengan informasi status kepemilikan.</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href="basemap.html" class="feature-card">
|
||||||
|
<div class="feature-icon">🏙️</div>
|
||||||
|
<h2 class="feature-title">Peta Referensi Pontianak</h2>
|
||||||
|
<p class="feature-desc">Batas kecamatan, jaringan jalan, dan sungai besar Kota Pontianak dari data GeoJSON.</p>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
<script src="js/feature-spbu.js"></script>
|
<script src="js/feature-spbu.js"></script>
|
||||||
<script src="js/feature-jalan.js"></script>
|
<script src="js/feature-jalan.js"></script>
|
||||||
<script src="js/feature-persil.js"></script>
|
<script src="js/feature-persil.js"></script>
|
||||||
|
<script src="js/feature-basemap.js"></script>
|
||||||
<script>
|
<script>
|
||||||
initPage('jalan');
|
initPage('jalan');
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+19
-5
@@ -35,14 +35,20 @@ const layers = {
|
|||||||
persilSHM: L.layerGroup(),
|
persilSHM: L.layerGroup(),
|
||||||
persilHGB: L.layerGroup(),
|
persilHGB: L.layerGroup(),
|
||||||
persilHGU: 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 = {
|
const layerVisible = {
|
||||||
rumahIbadah: true, ibadahRadius: true, pendudukMiskin: true,
|
rumahIbadah: true, ibadahRadius: true, pendudukMiskin: true,
|
||||||
spbu24: true, spbuNon24: true,
|
spbu24: true, spbuNon24: true,
|
||||||
jalanNasional: true, jalanProvinsi: true, jalanKabupaten: 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 = [];
|
const searchEntries = [];
|
||||||
@@ -320,7 +326,8 @@ function initPage(tab) {
|
|||||||
kemiskinan: ['rumahIbadah', 'ibadahRadius', 'pendudukMiskin'],
|
kemiskinan: ['rumahIbadah', 'ibadahRadius', 'pendudukMiskin'],
|
||||||
spbu: ['spbu24', 'spbuNon24'],
|
spbu: ['spbu24', 'spbuNon24'],
|
||||||
jalan: ['jalanNasional', 'jalanProvinsi', 'jalanKabupaten'],
|
jalan: ['jalanNasional', 'jalanProvinsi', 'jalanKabupaten'],
|
||||||
persil: ['persilSHM', 'persilHGB', 'persilHGU', 'persilHP']
|
persil: ['persilSHM', 'persilHGB', 'persilHGU', 'persilHP'],
|
||||||
|
basemap: ['basemap_kecamatan', 'basemap_jalan', 'basemap_sungai']
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tabLayers[tab]) {
|
if (tabLayers[tab]) {
|
||||||
@@ -329,8 +336,14 @@ function initPage(tab) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const titles = { kemiskinan: 'Ibadah & Kemiskinan', spbu: 'SPBU', jalan: 'Data Jalan', persil: 'Persil Tanah' };
|
const titles = {
|
||||||
document.getElementById('panel-title').textContent = titles[tab];
|
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);
|
renderPanel(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,6 +364,7 @@ async function renderPanel(tab) {
|
|||||||
if (tab === 'spbu') await renderSpbuPanel(pb);
|
if (tab === 'spbu') await renderSpbuPanel(pb);
|
||||||
if (tab === 'jalan') await renderJalanPanel(pb);
|
if (tab === 'jalan') await renderJalanPanel(pb);
|
||||||
if (tab === 'persil') await renderPersilPanel(pb);
|
if (tab === 'persil') await renderPersilPanel(pb);
|
||||||
|
if (tab === 'basemap') await renderBasemapPanel(pb);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findGeomLayer(type, id) {
|
function findGeomLayer(type, id) {
|
||||||
|
|||||||
@@ -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 `<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]);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -81,6 +81,7 @@
|
|||||||
<script src="js/feature-spbu.js"></script>
|
<script src="js/feature-spbu.js"></script>
|
||||||
<script src="js/feature-jalan.js"></script>
|
<script src="js/feature-jalan.js"></script>
|
||||||
<script src="js/feature-persil.js"></script>
|
<script src="js/feature-persil.js"></script>
|
||||||
|
<script src="js/feature-basemap.js"></script>
|
||||||
<script>
|
<script>
|
||||||
initPage('kemiskinan');
|
initPage('kemiskinan');
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
<script src="js/feature-spbu.js"></script>
|
<script src="js/feature-spbu.js"></script>
|
||||||
<script src="js/feature-jalan.js"></script>
|
<script src="js/feature-jalan.js"></script>
|
||||||
<script src="js/feature-persil.js"></script>
|
<script src="js/feature-persil.js"></script>
|
||||||
|
<script src="js/feature-basemap.js"></script>
|
||||||
<script>
|
<script>
|
||||||
initPage('persil');
|
initPage('persil');
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
<script src="js/feature-spbu.js"></script>
|
<script src="js/feature-spbu.js"></script>
|
||||||
<script src="js/feature-jalan.js"></script>
|
<script src="js/feature-jalan.js"></script>
|
||||||
<script src="js/feature-persil.js"></script>
|
<script src="js/feature-persil.js"></script>
|
||||||
|
<script src="js/feature-basemap.js"></script>
|
||||||
<script>
|
<script>
|
||||||
initPage('spbu');
|
initPage('spbu');
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user