initial commit

This commit is contained in:
cindy
2026-06-10 19:20:46 +07:00
commit ec94359f9a
21 changed files with 23023 additions and 0 deletions
+432
View File
@@ -0,0 +1,432 @@
<!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: '&copy; <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>
File diff suppressed because it is too large Load Diff