432 lines
18 KiB
HTML
432 lines
18 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<base target="_top">
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>Choropleth Peta Kepadatan Penduduk - Kota Pontianak</title>
|
|
|
|
<!-- Leaflet CSS & JS -->
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
|
|
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html, body {
|
|
height: 100%;
|
|
width: 100%;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
overflow: hidden; /* Mencegah scroll */
|
|
}
|
|
|
|
/* Peta full screen */
|
|
#map {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Info panel tetap di atas peta */
|
|
.info {
|
|
padding: 8px 12px;
|
|
font: 15px/1.4 'Segoe UI', 'Arial', sans-serif;
|
|
background: white;
|
|
background: rgba(255,255,255,0.95);
|
|
box-shadow: 0 2px 12px rgba(0,0,0,0.2);
|
|
border-radius: 8px;
|
|
border-left: 4px solid #2c7da0;
|
|
min-width: 200px;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.legend {
|
|
text-align: left;
|
|
line-height: 20px;
|
|
color: #2c3e2f;
|
|
background: rgba(255,255,255,0.92);
|
|
padding: 8px 12px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.legend i {
|
|
width: 24px;
|
|
height: 18px;
|
|
float: left;
|
|
margin-right: 8px;
|
|
opacity: 0.85;
|
|
border-radius: 3px;
|
|
border: 0.5px solid rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.legend strong {
|
|
font-size: 13px;
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
color: #1f3b2c;
|
|
text-align: center;
|
|
border-bottom: 1px dashed #aaa;
|
|
}
|
|
|
|
/* Title note floating di atas peta */
|
|
.title-note {
|
|
position: fixed;
|
|
bottom: 15px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
z-index: 1000;
|
|
background: rgba(0,0,0,0.75);
|
|
backdrop-filter: blur(8px);
|
|
padding: 8px 20px;
|
|
border-radius: 40px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: white;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
|
pointer-events: none;
|
|
white-space: nowrap;
|
|
font-family: 'Segoe UI', sans-serif;
|
|
}
|
|
|
|
@media (max-width: 700px) {
|
|
.info { font-size: 12px; min-width: 160px; }
|
|
.legend { font-size: 11px; }
|
|
.title-note { font-size: 11px; padding: 5px 12px; white-space: nowrap; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div id="map-container">
|
|
<div id="map"></div>
|
|
</div>
|
|
|
|
<!-- Data GeoJSON terpisah: pontianak.js -->
|
|
<script type="text/javascript" src="pontianak.js"></script>
|
|
|
|
<script type="text/javascript">
|
|
// ==============================
|
|
// 1. INISIALISASI PETA
|
|
// ==============================
|
|
// Koordinat pusat Kota Pontianak (sekitar -0.020, 109.342)
|
|
const pontianakCenter = [-0.020, 109.342];
|
|
const initialZoom = 12;
|
|
|
|
const map = L.map('map').setView(pontianakCenter, initialZoom);
|
|
|
|
// Base map tiles (OpenStreetMap)
|
|
const tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
maxZoom: 19,
|
|
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
|
}).addTo(map);
|
|
|
|
// ==============================
|
|
// 2. INFO CONTROL (hover / klik)
|
|
// ==============================
|
|
const info = L.control();
|
|
|
|
info.onAdd = function () {
|
|
this._div = L.DomUtil.create('div', 'info');
|
|
this.update();
|
|
return this._div;
|
|
};
|
|
|
|
// Fungsi update info panel (menampilkan data kecamatan & jumlah penduduk)
|
|
info.update = function (props) {
|
|
let content = '';
|
|
if (props) {
|
|
// Menggunakan properti "Ket" untuk nama kecamatan, "Penduduk" untuk jumlah penduduk
|
|
const kecamatan = props.Ket || props.name || 'Tidak diketahui';
|
|
const penduduk = props.Penduduk !== undefined && props.Penduduk !== null ? props.Penduduk.toLocaleString('id-ID') : 'Data tidak tersedia';
|
|
content = `<b>🏙️ ${kecamatan}</b><br/>👥 Jumlah Penduduk: <strong>${penduduk}</strong> jiwa`;
|
|
} else {
|
|
content = 'Arahkan kursor ke wilayah kecamatan';
|
|
}
|
|
this._div.innerHTML = `<h4>📊 Kependudukan Kota Pontianak</h4>${content}`;
|
|
};
|
|
|
|
info.addTo(map);
|
|
|
|
// ==============================
|
|
// 3. FUNGSI WARNA BERDASARKAN JUMLAH PENDUDUK
|
|
// ==============================
|
|
// Menentukan warna berdasarkan kelas populasi (disesuaikan dengan data Pontianak)
|
|
// Data contoh: Pontianak Timur ~112.440, kita buat rentang sampai >150rb (jika ada)
|
|
function getColor(population) {
|
|
if (population === undefined || population === null) return '#cccccc'; // abu-abu untuk data kosong
|
|
|
|
if (population > 150000) return '#4d004b'; // ungu tua
|
|
if (population > 100000) return '#810f7c'; // ungu sedang
|
|
if (population > 70000) return '#c51b8a'; // magenta
|
|
if (population > 50000) return '#de77ae'; // pink
|
|
if (population > 30000) return '#f1b6da'; // pink muda
|
|
if (population > 10000) return '#fde0ef'; // sangat muda
|
|
return '#fff7fb'; // putih kebiruan
|
|
}
|
|
|
|
// Style untuk setiap feature (polygon)
|
|
function style(feature) {
|
|
const pop = feature?.properties?.Penduduk;
|
|
return {
|
|
weight: 1.8,
|
|
opacity: 0.9,
|
|
color: '#2c3e50',
|
|
dashArray: null,
|
|
fillOpacity: 0.75,
|
|
fillColor: getColor(pop)
|
|
};
|
|
}
|
|
|
|
// ==============================
|
|
// 4. INTERAKSI (HOVER, KLIK ZOOM)
|
|
// ==============================
|
|
let currentHighlightedLayer = null;
|
|
|
|
function highlightFeature(e) {
|
|
const layer = e.target;
|
|
|
|
// Reset style layer sebelumnya jika ada (opsional, tapi biar rapi)
|
|
if (currentHighlightedLayer && currentHighlightedLayer !== layer) {
|
|
geojsonLayer.resetStyle(currentHighlightedLayer);
|
|
}
|
|
|
|
layer.setStyle({
|
|
weight: 3.5,
|
|
color: '#ffaa33',
|
|
dashArray: '',
|
|
fillOpacity: 0.85,
|
|
fillColor: layer.feature.properties.Penduduk ? getColor(layer.feature.properties.Penduduk) : '#aaa'
|
|
});
|
|
|
|
layer.bringToFront();
|
|
|
|
// Update info panel dengan properti dari feature
|
|
info.update(layer.feature.properties);
|
|
currentHighlightedLayer = layer;
|
|
}
|
|
|
|
function resetHighlight(e) {
|
|
const layer = e.target;
|
|
geojsonLayer.resetStyle(layer);
|
|
// Jangan langsung hapus info, tapi update ke default tanpa properti
|
|
// Agar tidak menghilang saat mouse out, kita update info ke teks default.
|
|
// (opsi: biarkan info terakhir agar tidak kosong, namun lebih baik tampilkan petunjuk)
|
|
info.update(null);
|
|
currentHighlightedLayer = null;
|
|
}
|
|
|
|
function zoomToFeature(e) {
|
|
const layer = e.target;
|
|
map.fitBounds(layer.getBounds());
|
|
// setelah zoom, tetap tampilkan info
|
|
info.update(layer.feature.properties);
|
|
}
|
|
|
|
function onEachFeature(feature, layer) {
|
|
// pastikan properti yang diperlukan ada (fallback)
|
|
if (!feature.properties) feature.properties = {};
|
|
if (!feature.properties.Ket && feature.properties.Plan_Area) {
|
|
// alternatif jika nama kecamatan tidak pakai "Ket", bisa pakai "Plan_Area"? Tidak. Tapi kita pastikan
|
|
feature.properties.Ket = feature.properties.Ket || 'Kecamatan';
|
|
}
|
|
|
|
layer.on({
|
|
mouseover: highlightFeature,
|
|
mouseout: resetHighlight,
|
|
click: zoomToFeature
|
|
});
|
|
|
|
// Tambahkan tooltip sederhana (opsional) saat hover tanpa harus selalu pakai info
|
|
layer.bindTooltip(`${feature.properties.Ket || 'Kecamatan'} : ${(feature.properties.Penduduk || 0).toLocaleString('id-ID')} jiwa`, {
|
|
sticky: true,
|
|
className: 'custom-tooltip',
|
|
offset: [0, -5]
|
|
});
|
|
}
|
|
|
|
// ==============================
|
|
// 5. LOAD DATA GEOJSON (PONTIANAK.JS)
|
|
// ==============================
|
|
// Variabel global dari file pontianak.js harus bernama "pontianakData" atau "statesData"?
|
|
// Pada contoh dari user, file pontianak.js berisi object JSON langsung (tidak dibungkus var?)
|
|
// Tapi agar aman: kita akan deteksi apakah ada variabel global bernama "pontianakData" atau "statesData"
|
|
// Berdasarkan instruksi: file pontianak.js memiliki struktur seperti contoh (FeatureCollection) dengan properti Penduduk, Ket.
|
|
// Agar kode fleksibel, kita akan menggunakan data dari window jika tersedia.
|
|
// Namun user hanya menyebut "pontianak.js ada di file terpisah dengan index.html" dan isinya diawali dengan { ... }
|
|
// Biasanya file js berisi: var pontianakGeoJSON = { ... }; atau langsung window.data = ...
|
|
// Agar kompatibel, kita baca dari global scope: cek apakah ada variable "pontianakData", "statesData", atau "geoJsonPontianak".
|
|
// Kita juga bisa fallback jika file js langsung mengekspos window.pontianakData.
|
|
// Karena tidak dipastikan nama variabelnya, kita akan cari object FeatureCollection yang terdaftar.
|
|
|
|
let geoJsonData = null;
|
|
|
|
// Cek beberapa kemungkinan nama variabel global dari pontianak.js
|
|
if (typeof pontianakData !== 'undefined') {
|
|
geoJsonData = pontianakData;
|
|
} else if (typeof statesData !== 'undefined') {
|
|
// Hati-hati: statesData mungkin dari contoh US, tapi jika user mengganti isi pontianak.js dengan statesData, kita gunakan.
|
|
// Namun pastikan propertinya sesuai: cek apakah fiturnya punya "Penduduk"
|
|
if (statesData && statesData.features && statesData.features[0] && statesData.features[0].properties &&
|
|
(statesData.features[0].properties.Penduduk !== undefined || statesData.features[0].properties.Ket)) {
|
|
geoJsonData = statesData;
|
|
} else {
|
|
console.warn("Variabel statesData tersedia tapi bukan data Pontianak (tidak memiliki Penduduk).");
|
|
}
|
|
} else if (typeof pontianakGeoJSON !== 'undefined') {
|
|
geoJsonData = pontianakGeoJSON;
|
|
} else if (typeof dataPontianak !== 'undefined') {
|
|
geoJsonData = dataPontianak;
|
|
} else {
|
|
// Cek apakah ada variabel global lain yang mungkin berisi FeatureCollection dengan properti Penduduk?
|
|
// Kita loop properti window untuk debugging, tapi hati-hati performa.
|
|
for (let key in window) {
|
|
if (window.hasOwnProperty(key) && window[key] && window[key].type === 'FeatureCollection' && window[key].features) {
|
|
// cek sampel feature apakah mengandung Penduduk
|
|
const sample = window[key].features[0];
|
|
if (sample && sample.properties && (sample.properties.Penduduk !== undefined || sample.properties.Ket)) {
|
|
geoJsonData = window[key];
|
|
console.log(`Data GeoJSON ditemukan melalui variabel global: ${key}`);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Jika masih null, beri pesan error dan tampilkan peta kosong dengan notifikasi
|
|
if (!geoJsonData) {
|
|
console.error("Gagal memuat data GeoJSON. Pastikan file pontianak.js memiliki variabel global seperti 'pontianakData', 'statesData', atau langsung mengekspos FeatureCollection.");
|
|
// Tampilkan overlay peringatan di peta
|
|
const errorMsg = L.control({ position: 'bottomleft' });
|
|
errorMsg.onAdd = function() {
|
|
const div = L.DomUtil.create('div', 'info');
|
|
div.style.background = '#d9534f';
|
|
div.style.color = 'white';
|
|
div.style.fontWeight = 'bold';
|
|
div.style.padding = '8px 12px';
|
|
div.style.borderRadius = '6px';
|
|
div.innerHTML = '⚠️ Data GeoJSON (pontianak.js) tidak ditemukan. Pastikan file tersedia dan berisi variabel global dengan format FeatureCollection.';
|
|
return div;
|
|
};
|
|
errorMsg.addTo(map);
|
|
|
|
// Tambahkan layer kosong agar peta tetap interaktif
|
|
var geojsonLayer = L.geoJson(null).addTo(map);
|
|
window.geojsonLayer = geojsonLayer;
|
|
} else {
|
|
// Validasi dan tambahkan ke peta
|
|
try {
|
|
// Pastikan koordinat polygon sesuai (EPSG:4326)
|
|
const geojsonLayer = L.geoJson(geoJsonData, {
|
|
style: style,
|
|
onEachFeature: onEachFeature
|
|
}).addTo(map);
|
|
|
|
// Simpan layer ke variabel global untuk reset style
|
|
window.geojsonLayer = geojsonLayer;
|
|
|
|
// Optional: sesuaikan zoom agar pas dengan bounding box seluruh kecamatan Pontianak
|
|
if (geojsonLayer.getBounds().isValid()) {
|
|
map.fitBounds(geojsonLayer.getBounds());
|
|
// sedikit zoom out agar tidak terlalu ketat (opsi)
|
|
map.setZoom(map.getZoom() - 0.3);
|
|
}
|
|
|
|
// Tambahkan atribusi sumber data (sesuai kebutuhan)
|
|
map.attributionControl.addAttribution('Data Kependudukan & GeoJSON: Pemerintah Kota Pontianak (simulasi)');
|
|
|
|
} catch (err) {
|
|
console.error("Error saat memproses GeoJSON:", err);
|
|
const errorControl = L.control({ position: 'bottomleft' });
|
|
errorControl.onAdd = function() {
|
|
const div = L.DomUtil.create('div', 'info');
|
|
div.style.background = '#f0ad4e';
|
|
div.innerHTML = '⚠️ Format GeoJSON tidak valid. Periksa struktur pontianak.js.';
|
|
return div;
|
|
};
|
|
errorControl.addTo(map);
|
|
}
|
|
}
|
|
|
|
// ==============================
|
|
// 6. LEGEND (Berdasarkan Jumlah Penduduk)
|
|
// ==============================
|
|
const legend = L.control({ position: 'bottomright' });
|
|
|
|
legend.onAdd = function () {
|
|
const div = L.DomUtil.create('div', 'info legend');
|
|
// Rentang populasi sesuai data Pontianak (disesuaikan)
|
|
const populationRanges = [
|
|
{ min: 0, max: 10000, label: '< 10.000', color: '#fff7fb' },
|
|
{ min: 10000, max: 30000, label: '10.000 - 30.000', color: '#fde0ef' },
|
|
{ min: 30000, max: 50000, label: '30.000 - 50.000', color: '#f1b6da' },
|
|
{ min: 50000, max: 70000, label: '50.000 - 70.000', color: '#de77ae' },
|
|
{ min: 70000, max: 100000, label: '70.000 - 100.000', color: '#c51b8a' },
|
|
{ min: 100000, max: 150000, label: '100.000 - 150.000', color: '#810f7c' },
|
|
{ min: 150000, max: Infinity, label: '> 150.000', color: '#4d004b' }
|
|
];
|
|
|
|
div.innerHTML = '<strong>👥 JUMLAH PENDUDUK</strong><br>';
|
|
|
|
for (let range of populationRanges) {
|
|
const color = range.color;
|
|
const label = range.label;
|
|
div.innerHTML +=
|
|
'<i style="background:' + color + '; width: 24px; height: 18px; display: inline-block; margin-right: 8px; border: 0.5px solid #777;"></i> ' +
|
|
label + '<br>';
|
|
}
|
|
div.innerHTML += '<br><small style="font-size:10px;">Hover/klik polygon untuk detail</small>';
|
|
return div;
|
|
};
|
|
|
|
legend.addTo(map);
|
|
|
|
// Tambahkan kontrol skala (optional)
|
|
L.control.scale({ metric: true, imperial: false, position: 'bottomleft' }).addTo(map);
|
|
|
|
// ==============================
|
|
// 7. ADDITIONAL HANDLE: Jika data tidak punya properti 'Ket', coba gunakan 'Plan_Area' atau 'Id'
|
|
// ==============================
|
|
// Fungsi tambahan: pastikan info bisa membaca properti apapun yang relevan
|
|
// Override sedikit agar saat hover info properti bekerja
|
|
// sudah dilakukan di onEachFeature dan info.update, tapi kita patch info.update agar lebih fleksibel
|
|
const originalUpdate = info.update;
|
|
info.update = function(props) {
|
|
if (props) {
|
|
let name = props.Ket || props.NAMA_KEC || props.name || props.kecamatan || 'Kecamatan Pontianak';
|
|
let population = props.Penduduk !== undefined ? props.Penduduk : (props.JUMLAH_PENDUDUK || props.populasi || 0);
|
|
if (typeof population === 'string') population = parseInt(population, 10);
|
|
if (isNaN(population)) population = 0;
|
|
const formattedPop = population.toLocaleString('id-ID');
|
|
this._div.innerHTML = `<h4>📊 Data Kependudukan</h4><b>🏘️ ${name}</b><br/>👥 Jumlah Penduduk: <strong>${formattedPop}</strong> jiwa`;
|
|
} else {
|
|
this._div.innerHTML = `<h4>📊 Kependudukan Kota Pontianak</h4>Arahkan kursor ke wilayah kecamatan`;
|
|
}
|
|
};
|
|
|
|
// Jika data belum dimuat karena tidak ada global, kita bisa coba fetch manual? Tidak diperlukan karena menggunakan script tag.
|
|
// Tambahkan log sukses jika geojsonLayer berhasil.
|
|
setTimeout(() => {
|
|
if (window.geojsonLayer && window.geojsonLayer.getLayers().length > 0) {
|
|
console.log(`✅ Choropleth Pontianak siap. Jumlah kecamatan yang ditampilkan: ${window.geojsonLayer.getLayers().length}`);
|
|
} else if (!geoJsonData) {
|
|
console.warn("⚠️ Periksa kembali file pontianak.js. Pastikan file memiliki variabel global seperti: var pontianakData = { type: 'FeatureCollection', features: [...] }");
|
|
}
|
|
}, 300);
|
|
|
|
</script>
|
|
|
|
</body>
|
|
</html> |