273 lines
9.0 KiB
JavaScript
273 lines
9.0 KiB
JavaScript
/**
|
|
* MAPS TOPOLOGI - MAPLIBRE GEOJSON VERSION (High Performance)
|
|
* Menggunakan GeoJSON Source & Symbol Layer agar ringan untuk ribuan data.
|
|
*/
|
|
|
|
let map;
|
|
var currentStyle = 'hybrid';
|
|
|
|
// --- DEFINISI STYLE GOOGLE MAPS ---
|
|
const googleHybridStyle = {
|
|
'version': 8,
|
|
'sources': {
|
|
'google-hybrid': {
|
|
'type': 'raster',
|
|
'tiles': ['https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}'],
|
|
'tileSize': 256,
|
|
'attribution': '© Google Maps'
|
|
}
|
|
},
|
|
'layers': [{
|
|
'id': 'google-hybrid-layer',
|
|
'type': 'raster',
|
|
'source': 'google-hybrid',
|
|
'minzoom': 0,
|
|
'maxzoom': 22
|
|
}]
|
|
};
|
|
|
|
const googleStreetStyle = {
|
|
'version': 8,
|
|
'sources': {
|
|
'google-street': {
|
|
'type': 'raster',
|
|
'tiles': ['https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}'],
|
|
'tileSize': 256,
|
|
'attribution': '© Google Maps'
|
|
}
|
|
},
|
|
'layers': [{
|
|
'id': 'google-street-layer',
|
|
'type': 'raster',
|
|
'source': 'google-street',
|
|
'minzoom': 0,
|
|
'maxzoom': 22
|
|
}]
|
|
};
|
|
|
|
function initMap(center) {
|
|
// Init MapLibre
|
|
map = new maplibregl.Map({
|
|
container: 'map',
|
|
style: googleHybridStyle, // Default Hybrid
|
|
center: [center.lng, center.lat], // [Lng, Lat]
|
|
zoom: 10,
|
|
attributionControl: true,
|
|
dragRotate: false,
|
|
pitchWithRotate: false,
|
|
touchPitch: false
|
|
});
|
|
|
|
map.touchZoomRotate.disableRotation();
|
|
map.addControl(new maplibregl.NavigationControl({ showCompass: false, showZoom: true }), 'top-right');
|
|
map.addControl(new maplibregl.FullscreenControl(), 'top-left');
|
|
|
|
// Tambahkan tombol ganti layer
|
|
addLayerSwitcher();
|
|
|
|
// Tunggu map load selesai
|
|
map.on('load', function() {
|
|
// 1. Load Icon ke GPU (Wajib untuk GeoJSON)
|
|
loadIcons(map, () => {
|
|
// 2. Setup Source & Layer setelah icon siap
|
|
setupMapLayers();
|
|
|
|
// 3. Ambil data pertama kali
|
|
fetchMarkers();
|
|
|
|
// 4. Refresh marker tiap 30 detik
|
|
setInterval(fetchMarkers, 30000);
|
|
});
|
|
});
|
|
}
|
|
|
|
// --- FUNGSI BARU: Load Icon Gambar ke Memory Peta ---
|
|
function loadIcons(map, callback) {
|
|
// Daftar icon sesuai file yang ada di folder dist/img/
|
|
const icons = [
|
|
{ name: 'icon-odp', url: 'dist/img/ODP.png' },
|
|
{ name: 'icon-on', url: 'dist/img/ON.png' },
|
|
{ name: 'icon-off', url: 'dist/img/OF.png' },
|
|
{ name: 'icon-on1', url: 'dist/img/ON1.png' },
|
|
{ name: 'icon-off1', url: 'dist/img/OF1.png' }
|
|
];
|
|
|
|
let loadedCount = 0;
|
|
|
|
// Load satu per satu
|
|
icons.forEach(ic => {
|
|
map.loadImage(ic.url, (error, image) => {
|
|
if (error) {
|
|
console.error("Gagal load icon:", ic.url);
|
|
} else {
|
|
if (!map.hasImage(ic.name)) map.addImage(ic.name, image);
|
|
}
|
|
|
|
loadedCount++;
|
|
// Jika semua icon sudah diproses (sukses/gagal), jalankan callback
|
|
if (loadedCount === icons.length) {
|
|
if (callback) callback();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// --- FUNGSI BARU: Menyiapkan Wadah (Source & Layer) ---
|
|
function setupMapLayers() {
|
|
// Tambahkan Source Kosong (Wadah Data)
|
|
map.addSource('markers-source', {
|
|
type: 'geojson',
|
|
data: { type: 'FeatureCollection', features: [] }
|
|
});
|
|
|
|
// Tambahkan Layer Tampilan (Symbol Layer)
|
|
map.addLayer({
|
|
id: 'markers-layer',
|
|
type: 'symbol',
|
|
source: 'markers-source',
|
|
layout: {
|
|
'icon-image': ['get', 'iconName'], // Ambil nama icon dari properties GeoJSON
|
|
'icon-size': 1, // Sesuaikan ukuran (0.5 - 1.0)
|
|
'icon-allow-overlap': true,
|
|
'icon-anchor': 'bottom' // Ujung bawah icon menunjuk ke titik
|
|
}
|
|
});
|
|
|
|
// Event Klik untuk Popup
|
|
map.on('click', 'markers-layer', (e) => {
|
|
const coordinates = e.features[0].geometry.coordinates.slice();
|
|
const content = e.features[0].properties.content;
|
|
|
|
// Pastikan popup muncul di atas elemen lain
|
|
while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
|
|
coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
|
|
}
|
|
|
|
new maplibregl.Popup({ offset: 25 })
|
|
.setLngLat(coordinates)
|
|
.setHTML(content)
|
|
.addTo(map);
|
|
});
|
|
|
|
// Ubah kursor jadi tangan saat hover
|
|
map.on('mouseenter', 'markers-layer', () => {
|
|
map.getCanvas().style.cursor = 'pointer';
|
|
});
|
|
map.on('mouseleave', 'markers-layer', () => {
|
|
map.getCanvas().style.cursor = '';
|
|
});
|
|
}
|
|
|
|
function addLayerSwitcher() {
|
|
class LayerSwitchControl {
|
|
onAdd(map) {
|
|
this._map = map;
|
|
this._container = document.createElement('div');
|
|
this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group';
|
|
|
|
const btn = document.createElement('button');
|
|
btn.innerHTML = '🗺️'; // Icon peta
|
|
btn.title = "Ganti Tampilan (Satelit/Jalan)";
|
|
btn.type = "button";
|
|
btn.onclick = () => {
|
|
if (currentStyle === 'hybrid') {
|
|
map.setStyle(googleStreetStyle);
|
|
currentStyle = 'street';
|
|
} else {
|
|
map.setStyle(googleHybridStyle);
|
|
currentStyle = 'hybrid';
|
|
}
|
|
|
|
// PENTING: Saat ganti style, resource gambar & source hilang.
|
|
// Kita harus load ulang icon dan layer.
|
|
setTimeout(() => {
|
|
loadIcons(map, () => {
|
|
// Cek apakah source masih ada, jika hilang buat lagi
|
|
if (!map.getSource('markers-source')) {
|
|
setupMapLayers();
|
|
fetchMarkers(); // Ambil data lagi
|
|
}
|
|
});
|
|
}, 500);
|
|
};
|
|
this._container.appendChild(btn);
|
|
return this._container;
|
|
}
|
|
onRemove() {
|
|
this._container.parentNode.removeChild(this._container);
|
|
this._map = undefined;
|
|
}
|
|
}
|
|
map.addControl(new LayerSwitchControl(), 'top-right');
|
|
}
|
|
|
|
function fetchMarkers() {
|
|
// topologiId diambil dari variabel global di halaman PHP
|
|
fetch(`load/maps_marker.php?i=${topologiId}`)
|
|
.then(response => {
|
|
if (!response.ok) throw new Error('Network response was not ok');
|
|
return response.json();
|
|
})
|
|
.then(res => {
|
|
if (!res.status) {
|
|
console.warn("⚠️ Tidak ada data marker");
|
|
return;
|
|
}
|
|
|
|
// --- KONVERSI DATA API KE GEOJSON ---
|
|
const features = res.data.map(item => {
|
|
// Tentukan nama icon berdasarkan filename dari database
|
|
let iconName = 'icon-odp'; // Default
|
|
|
|
// Cek string URL icon untuk menentukan jenisnya
|
|
if (item.icon.includes('ODP.png')) iconName = 'icon-odp';
|
|
else if (item.icon.includes('ON.png')) iconName = 'icon-on';
|
|
else if (item.icon.includes('OF.png')) iconName = 'icon-off';
|
|
else if (item.icon.includes('ON1.png')) iconName = 'icon-on1';
|
|
else if (item.icon.includes('OF1.png')) iconName = 'icon-off1';
|
|
|
|
return {
|
|
type: 'Feature',
|
|
geometry: {
|
|
type: 'Point',
|
|
coordinates: [parseFloat(item.lng), parseFloat(item.lat)]
|
|
},
|
|
properties: {
|
|
content: item.content, // HTML Popup
|
|
iconName: iconName // Nama icon untuk diload
|
|
}
|
|
};
|
|
});
|
|
|
|
// --- UPDATE DATA DI PETA (RINGAN) ---
|
|
const source = map.getSource('markers-source');
|
|
if (source) {
|
|
source.setData({
|
|
type: 'FeatureCollection',
|
|
features: features
|
|
});
|
|
console.log(`✅ ${features.length} Data updated via GeoJSON.`);
|
|
}
|
|
|
|
})
|
|
.catch(error => console.error('❌ Error fetching markers:', error));
|
|
}
|
|
|
|
// Entry Point: Ambil center dari API, lalu init map
|
|
window.onload = function() {
|
|
fetch(`load/maps_marker.php?i=${topologiId}`)
|
|
.then(res => res.json())
|
|
.then(res => {
|
|
if (res.status && res.centerlonglat) {
|
|
console.log("📍 Init map pakai centerlonglat:", res.centerlonglat);
|
|
initMap(res.centerlonglat);
|
|
} else {
|
|
console.warn("⚠️ Centerlonglat tidak ada, pakai fallback Jakarta");
|
|
initMap({ lat: -6.2, lng: 106.816666 });
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error("❌ Gagal ambil data awal:", err);
|
|
initMap({ lat: -6.2, lng: 106.816666 }); // fallback
|
|
});
|
|
}; |