initial commit
This commit is contained in:
@@ -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: '© <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
Reference in New Issue
Block a user