Update index.html
Browse files- index.html +376 -18
index.html
CHANGED
|
@@ -1,19 +1,377 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="vi">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
| 6 |
+
<title>Bản đồ Ranh giới Hành chính Việt Nam – Long Ngo, 2025</title>
|
| 7 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css"/>
|
| 8 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"/>
|
| 9 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.js"></script>
|
| 10 |
+
<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
|
| 11 |
+
<style>
|
| 12 |
+
:root{
|
| 13 |
+
--brand-bg: #eef6fa;
|
| 14 |
+
--brand-blue: #1790e0;
|
| 15 |
+
--brand-blue-dark: #0c3a69;
|
| 16 |
+
--brand-blue-light: #b3e5fc;
|
| 17 |
+
--brand-yellow: #ffeb3b;
|
| 18 |
+
--brand-white: #fff;
|
| 19 |
+
--brand-table-border: #90caf9;
|
| 20 |
+
}
|
| 21 |
+
body{font-family:"Segoe UI",Tahoma,Geneva,Verdana,sans-serif;background:var(--brand-bg);color:#174067;min-height:100vh;}
|
| 22 |
+
.header{
|
| 23 |
+
background: linear-gradient(90deg, #e3f2fd 0%, #b3e5fc 100%);
|
| 24 |
+
padding: 1.1rem 2rem 1rem 2rem;
|
| 25 |
+
border-bottom: 4px solid var(--brand-blue);
|
| 26 |
+
box-shadow: 0 2px 16px rgba(23,144,224,.13);
|
| 27 |
+
display: flex; align-items: center; gap: 16px;
|
| 28 |
+
}
|
| 29 |
+
.header h1{font-size:2.1rem;font-weight:bold;color:var(--brand-blue-dark);margin:0;letter-spacing:0.5px;flex:1;}
|
| 30 |
+
.map-controls{
|
| 31 |
+
position:absolute;top:18px;left:18px;z-index:1000;display:flex;flex-direction:column;gap:13px
|
| 32 |
+
}
|
| 33 |
+
.map-controls .box{
|
| 34 |
+
background:var(--brand-white);padding:7px 14px 7px 13px;border-left:5px solid var(--brand-blue);
|
| 35 |
+
border-radius:10px;box-shadow:0 2px 7px rgba(23,144,224,.09)
|
| 36 |
+
}
|
| 37 |
+
.switch{display:inline-flex;align-items:center;gap:9px;font-weight:600;color:var(--brand-blue-dark)}
|
| 38 |
+
.switch .label{font-size:1.04rem}
|
| 39 |
+
.switch input{display:none}
|
| 40 |
+
.switch .slider{
|
| 41 |
+
width:47px;height:23px;border-radius:12px;background:var(--brand-blue-light);position:relative;cursor:pointer;transition:.3s}
|
| 42 |
+
.switch .slider::before{
|
| 43 |
+
content:"";position:absolute;left:2px;top:2px;width:18px;height:18px;border-radius:50%;background:#fff;
|
| 44 |
+
box-shadow:0 1px 5px rgba(23,144,224,.16);transition:.3s}
|
| 45 |
+
.switch input:checked + .slider{background:var(--brand-blue)}
|
| 46 |
+
.switch input:checked + .slider::before{transform:translateX(24px)}
|
| 47 |
+
.basemap-select{margin-left:7px;font-size:1.03rem;border-radius:6px;padding:4px 8px;border:1.2px solid #b3e5fc;background:#fff;color:#1976d2;}
|
| 48 |
+
.download-btn{
|
| 49 |
+
display:inline-block;padding:9px 15px;border-radius:8px;background:var(--brand-blue-dark);
|
| 50 |
+
color:#fff;font-weight:600;text-decoration:none;box-shadow:0 2px 7px rgba(23,144,224,.12)
|
| 51 |
+
}
|
| 52 |
+
.download-btn:hover{background:var(--brand-blue)}
|
| 53 |
+
.leaflet-popup-content-wrapper{
|
| 54 |
+
background:#fff;border:1.6px solid var(--brand-blue-dark);border-radius:10px;
|
| 55 |
+
box-shadow:0 5px 18px rgba(23,144,224,.14)
|
| 56 |
+
}
|
| 57 |
+
.leaflet-popup-close-button{font-size:20px !important;padding:4px 7px;color:#1976d2}
|
| 58 |
+
.leaflet-popup-content{padding:10px 15px !important;font-size:15px;line-height:1.47}
|
| 59 |
+
.popup-title{margin:0 0 6px;font-weight:700;color:var(--brand-blue-dark)}
|
| 60 |
+
.popup-row{display:flex;gap:8px;margin-bottom:5px}
|
| 61 |
+
.popup-row .icon{width:22px;text-align:center}
|
| 62 |
+
.province-label {
|
| 63 |
+
font-size: 13px;
|
| 64 |
+
font-weight: 700;
|
| 65 |
+
color: var(--brand-blue-dark);
|
| 66 |
+
background:rgba(255,255,255,0.88);
|
| 67 |
+
padding:1px 6px 1px 6px;
|
| 68 |
+
border-radius:8px;
|
| 69 |
+
border:1.1px solid #1976d2;
|
| 70 |
+
box-shadow: 0 0 3px #b3e5fc, 0 0 5px #fff;
|
| 71 |
+
pointer-events: none;
|
| 72 |
+
white-space: nowrap;
|
| 73 |
+
letter-spacing: 0.5px;
|
| 74 |
+
}
|
| 75 |
+
.leaflet-tooltip.province-label {
|
| 76 |
+
background: transparent;
|
| 77 |
+
border: none;
|
| 78 |
+
box-shadow: none;
|
| 79 |
+
}
|
| 80 |
+
.leaflet-tooltip.province-label::after { display: none; }
|
| 81 |
+
.compare-bottom{
|
| 82 |
+
position:absolute;left:0;right:0;bottom:0;max-height:42%;overflow:auto;
|
| 83 |
+
background:#fff;border-top:4px solid var(--brand-blue-dark);
|
| 84 |
+
box-shadow:0 -7px 20px rgba(23,144,224,.13);padding:14px 15px 8px 15px;
|
| 85 |
+
border-radius:13px 13px 0 0;z-index:900
|
| 86 |
+
}
|
| 87 |
+
#comparePanel table,#comparePanel th,#comparePanel td{border:1.5px solid var(--brand-table-border)}
|
| 88 |
+
#comparePanel th,#comparePanel td{padding:5px 9px;text-align:center}
|
| 89 |
+
#comparePanel th{background:var(--brand-blue-light);font-size:1.01rem;}
|
| 90 |
+
.compare-remove{cursor:pointer;color:#c00;font-weight:bold}
|
| 91 |
+
.compare-remove:hover{text-decoration:underline}
|
| 92 |
+
.leaflet-control-zoom{display:none !important}
|
| 93 |
+
.footer {
|
| 94 |
+
position: fixed; left:0; right:0; bottom:0; z-index: 99999; background:rgba(240,248,255,0.94);
|
| 95 |
+
color:#0d47a1;font-size:14px;padding:3px 16px;text-align:right;box-shadow:0 -1.5px 10px #b3e5fc;
|
| 96 |
+
letter-spacing:0.5px
|
| 97 |
+
}
|
| 98 |
+
</style>
|
| 99 |
+
</head>
|
| 100 |
+
<body>
|
| 101 |
+
<div class="header">
|
| 102 |
+
<h1>Bản đồ Ranh giới Hành chính Việt Nam</h1>
|
| 103 |
+
<select id="basemapSelect" class="basemap-select" title="Chọn nền bản đồ">
|
| 104 |
+
<option value="satellite">Vệ tinh</option>
|
| 105 |
+
<option value="roadmap">Bản đồ đường</option>
|
| 106 |
+
</select>
|
| 107 |
+
</div>
|
| 108 |
+
<div class="container">
|
| 109 |
+
<div id="map" style="height: 85vh;"></div>
|
| 110 |
+
<div class="map-controls">
|
| 111 |
+
<div class="box">
|
| 112 |
+
<label class="switch">
|
| 113 |
+
<span class="label">Cũ</span>
|
| 114 |
+
<input type="checkbox" id="switchBoundary">
|
| 115 |
+
<span class="slider"></span>
|
| 116 |
+
<span class="label">Mới</span>
|
| 117 |
+
</label>
|
| 118 |
+
</div>
|
| 119 |
+
<button id="toggleCompare" class="download-btn" style="margin-top:7px;">
|
| 120 |
+
Hiện So sánh
|
| 121 |
+
</button>
|
| 122 |
+
</div>
|
| 123 |
+
<div id="comparePanel" class="compare-bottom" style="display:none">
|
| 124 |
+
<h3><i class="fas fa-balance-scale"></i> So sánh (0/5)</h3>
|
| 125 |
+
<div id="compareTable" style="font-size:13px"></div>
|
| 126 |
+
</div>
|
| 127 |
+
</div>
|
| 128 |
+
<div class="footer">Nguồn: Long Ngo, 2025.</div>
|
| 129 |
+
<script>
|
| 130 |
+
const CONFIG = {
|
| 131 |
+
old:"https://raw.githubusercontent.com/lqtue/LacaProvinceMap/main/old.geojson",
|
| 132 |
+
new:"https://raw.githubusercontent.com/lqtue/LacaProvinceMap/main/new.geojson"
|
| 133 |
+
};
|
| 134 |
+
let map, oldLayer, newLayer;
|
| 135 |
+
let baseLayerSatellite, baseLayerRoad;
|
| 136 |
+
const dataStore = {old:null,new:null};
|
| 137 |
+
let compareList=[];
|
| 138 |
+
|
| 139 |
+
const iconMap = {
|
| 140 |
+
"Tỉnh thành mới": "📍", "Tỉnh thành cũ": "📍", "TT hành chính": "📍",
|
| 141 |
+
"GRDP 2024 (tỷ VND)": "💰", "Thu ngân sách 2024 (tỷ VND)": "💰",
|
| 142 |
+
"Diện tích (km2)": "🗺️", "Dân số": "👥", "ĐVHC cấp xã": "🏛️"
|
| 143 |
+
};
|
| 144 |
+
const labelOverrides = {
|
| 145 |
+
"Khánh Hoà": [12.248126980225129, 109.183807743233],
|
| 146 |
+
"TP HCM": [10.801867540653552, 106.68102175169227],
|
| 147 |
+
"TP. Hồ Chí Minh": [10.801867540653552, 106.68102175169227]
|
| 148 |
+
};
|
| 149 |
+
const numFmt = v=>{
|
| 150 |
+
const s = String(v).replace(/,/g, '.').replace(/ /g, '');
|
| 151 |
+
const n = parseFloat(s);
|
| 152 |
+
return isNaN(n) ? v : n.toLocaleString('vi-VN');
|
| 153 |
+
};
|
| 154 |
+
const ATTRS = ["Diện tích (km2)","Dân số","GRDP 2024 (tỷ VND)","Thu ngân sách 2024 (tỷ VND)","ĐVHC cấp xã"];
|
| 155 |
+
const numAttrs = ["Diện tích (km2)","Dân số","GRDP 2024 (tỷ VND)","Thu ngân sách 2024 (tỷ VND)"];
|
| 156 |
+
const palette = [
|
| 157 |
+
'#1565c0','#1976d2','#0277bd','#00838f','#0288d1','#00b8d4',
|
| 158 |
+
'#00897b','#00695c','#039be5','#00acc1','#0097a7','#01579b',
|
| 159 |
+
'#1e88e5','#29b6f6','#26c6da','#26a69a','#388e3c','#2e7d32',
|
| 160 |
+
'#4caf50','#0288d1','#2b32b2','#134e5e','#56ab2f','#0f2027',
|
| 161 |
+
'#43cea2','#185a9d','#283e51','#4776e6','#36d1c4','#159957'
|
| 162 |
+
];
|
| 163 |
+
function getProvinceColour(name){
|
| 164 |
+
let hash = 0;
|
| 165 |
+
for(let i=0;i<name.length;i++) hash = name.charCodeAt(i)+(hash<<5)-hash;
|
| 166 |
+
const idx = Math.abs(hash) % palette.length;
|
| 167 |
+
return palette[idx];
|
| 168 |
+
}
|
| 169 |
+
function init(){
|
| 170 |
+
map = L.map('map',{zoomControl:false, attributionControl:false}).setView([16,108],6);
|
| 171 |
+
baseLayerSatellite = L.tileLayer('https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}',
|
| 172 |
+
{subdomains:['mt0','mt1','mt2','mt3'], attribution:'© Google Satellite'});
|
| 173 |
+
baseLayerRoad = L.tileLayer('https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}',
|
| 174 |
+
{subdomains:['mt0','mt1','mt2','mt3'], attribution:'© Google Maps'});
|
| 175 |
+
baseLayerSatellite.addTo(map);
|
| 176 |
+
|
| 177 |
+
L.control.attribution({prefix: ''}).addTo(map);
|
| 178 |
+
map.attributionControl.setPrefix('© Google | Leaflet');
|
| 179 |
+
oldLayer=L.layerGroup().addTo(map);
|
| 180 |
+
newLayer=L.layerGroup().addTo(map);
|
| 181 |
+
|
| 182 |
+
fetch(CONFIG.old).then(r=>r.json()).then(gOld=>{
|
| 183 |
+
dataStore.old=gOld; drawLayer(gOld,'old');
|
| 184 |
+
});
|
| 185 |
+
fetch(CONFIG.new).then(r=>r.json()).then(gNew=>{
|
| 186 |
+
dataStore.new=gNew; drawLayer(gNew,'new');
|
| 187 |
+
if (gNew && gNew.features && gNew.features.length > 0) {
|
| 188 |
+
map.fitBounds(L.geoJSON(gNew).getBounds());
|
| 189 |
+
}
|
| 190 |
+
});
|
| 191 |
+
|
| 192 |
+
const sw=document.getElementById('switchBoundary');
|
| 193 |
+
sw.checked=true; map.removeLayer(oldLayer);
|
| 194 |
+
sw.onchange=()=> {
|
| 195 |
+
if (sw.checked) {
|
| 196 |
+
map.removeLayer(oldLayer); map.addLayer(newLayer);
|
| 197 |
+
} else {
|
| 198 |
+
map.removeLayer(newLayer); map.addLayer(oldLayer);
|
| 199 |
+
}
|
| 200 |
+
updateLabels();
|
| 201 |
+
};
|
| 202 |
+
document.getElementById('basemapSelect').onchange = function() {
|
| 203 |
+
if (this.value==="satellite") {
|
| 204 |
+
map.removeLayer(baseLayerRoad); map.addLayer(baseLayerSatellite);
|
| 205 |
+
} else {
|
| 206 |
+
map.removeLayer(baseLayerSatellite); map.addLayer(baseLayerRoad);
|
| 207 |
+
}
|
| 208 |
+
};
|
| 209 |
+
}
|
| 210 |
+
function drawLayer(gjson, type) {
|
| 211 |
+
const layer = type === 'old' ? oldLayer : newLayer;
|
| 212 |
+
layer.clearLayers();
|
| 213 |
+
L.geoJSON(gjson, {
|
| 214 |
+
style: feature => {
|
| 215 |
+
const provName = feature.properties["Tỉnh thành mới"] || feature.properties["Tỉnh thành cũ"];
|
| 216 |
+
return {
|
| 217 |
+
color: '#ffeb3b',
|
| 218 |
+
weight: 2.7,
|
| 219 |
+
opacity: 1,
|
| 220 |
+
fillColor: getProvinceColour(provName),
|
| 221 |
+
fillOpacity: 0.73
|
| 222 |
+
};
|
| 223 |
+
},
|
| 224 |
+
onEachFeature: (f, ly) => {
|
| 225 |
+
const p = f.properties;
|
| 226 |
+
const title = (p["Tỉnh thành mới"] || p["Tỉnh thành cũ"]) || 'Thông tin';
|
| 227 |
+
let html = `<h4 class="popup-title">${title}</h4>`;
|
| 228 |
+
[
|
| 229 |
+
"Tỉnh thành mới", "Tỉnh thành cũ", "TT hành chính", ...ATTRS
|
| 230 |
+
].forEach(k => {
|
| 231 |
+
if (p[k]) {
|
| 232 |
+
const val = numAttrs.includes(k) ? numFmt(p[k]) : p[k];
|
| 233 |
+
html += `<div class="popup-row"><span class="icon">${iconMap[k] || ''}</span>
|
| 234 |
+
<strong>${k}:</strong> <span>${val}</span></div>`;
|
| 235 |
+
}
|
| 236 |
+
});
|
| 237 |
+
const key = (type === 'old' ? 'old:' : 'new:') + title;
|
| 238 |
+
html += `<button class="addCompare" data-key="${key}" data-type="${type}"
|
| 239 |
+
style="margin-top:8px;padding:6px 10px;border:none;border-radius:6px;
|
| 240 |
+
background:var(--brand-blue);color:#fff;cursor:pointer">
|
| 241 |
+
+ Thêm so sánh</button>`;
|
| 242 |
+
ly.bindPopup(html);
|
| 243 |
+
const labelText = type === 'old'
|
| 244 |
+
? (p["Tỉnh thành cũ"] || p["Tỉnh thành mới"])
|
| 245 |
+
: (p["Tỉnh thành mới"] || p["Tỉnh thành cũ"]);
|
| 246 |
+
let labelPosition = null;
|
| 247 |
+
const featureGeometry = f.geometry;
|
| 248 |
+
if (labelText && labelOverrides[labelText]) {
|
| 249 |
+
labelPosition = labelOverrides[labelText];
|
| 250 |
+
}
|
| 251 |
+
else if (labelText && featureGeometry) {
|
| 252 |
+
let labelPolygonFeature = null;
|
| 253 |
+
if (featureGeometry.type === 'Polygon') {
|
| 254 |
+
labelPolygonFeature = turf.polygon(featureGeometry.coordinates);
|
| 255 |
+
} else if (featureGeometry.type === 'MultiPolygon') {
|
| 256 |
+
let largestArea = 0;
|
| 257 |
+
featureGeometry.coordinates.forEach(polygonCoords => {
|
| 258 |
+
const poly = turf.polygon(polygonCoords);
|
| 259 |
+
const area = turf.area(poly);
|
| 260 |
+
if (area > largestArea) {
|
| 261 |
+
largestArea = area;
|
| 262 |
+
labelPolygonFeature = poly;
|
| 263 |
+
}
|
| 264 |
+
});
|
| 265 |
+
}
|
| 266 |
+
if (labelPolygonFeature) {
|
| 267 |
+
try {
|
| 268 |
+
const pointOnSurface = turf.pointOnFeature(labelPolygonFeature);
|
| 269 |
+
if (pointOnSurface && pointOnSurface.geometry && pointOnSurface.geometry.coordinates) {
|
| 270 |
+
labelPosition = [pointOnSurface.geometry.coordinates[1], pointOnSurface.geometry.coordinates[0]];
|
| 271 |
+
}
|
| 272 |
+
} catch (e) {
|
| 273 |
+
const bounds = ly.getBounds();
|
| 274 |
+
if (bounds.isValid()) {
|
| 275 |
+
labelPosition = bounds.getCenter();
|
| 276 |
+
}
|
| 277 |
+
}
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
const tooltipOptions = {
|
| 281 |
+
permanent: true,
|
| 282 |
+
direction: 'center',
|
| 283 |
+
className: 'province-label',
|
| 284 |
+
pane: 'tooltipPane'
|
| 285 |
+
};
|
| 286 |
+
if (labelPosition) {
|
| 287 |
+
const customTooltip = L.tooltip(tooltipOptions)
|
| 288 |
+
.setLatLng(labelPosition)
|
| 289 |
+
.setContent(labelText);
|
| 290 |
+
ly.bindTooltip(customTooltip);
|
| 291 |
+
} else if (labelText) {
|
| 292 |
+
ly.bindTooltip(labelText, tooltipOptions);
|
| 293 |
+
}
|
| 294 |
+
ly.on('mouseover', e => e.target.setStyle({ fillOpacity: 0.88, weight: 3.3 }));
|
| 295 |
+
ly.on('mouseout', e => e.target.setStyle({ fillOpacity: 0.73, weight: 2.7 }));
|
| 296 |
+
}
|
| 297 |
+
}).addTo(layer);
|
| 298 |
+
}
|
| 299 |
+
function updateCompare(){
|
| 300 |
+
const panel=document.getElementById('comparePanel');
|
| 301 |
+
const tgt=document.getElementById('compareTable');
|
| 302 |
+
if(!compareList.length){panel.style.display='none';return;}
|
| 303 |
+
panel.style.display='block';
|
| 304 |
+
panel.querySelector('h3').innerHTML=
|
| 305 |
+
`<i class="fas fa-balance-scale"></i> So sánh (${compareList.length}/5)`;
|
| 306 |
+
let html='<table style="width:100%;border-collapse:collapse"><tr><th>Thuộc tính</th>';
|
| 307 |
+
compareList.forEach(c=>{
|
| 308 |
+
const nm=c.props["Tỉnh thành mới"]||c.props["Tỉnh thành cũ"];
|
| 309 |
+
html+=`<th>${nm}<br><span style="font-size:11px;color:#666">(${c.layer==='old'?'Cũ':'Mới'})</span>
|
| 310 |
+
<span class="compare-remove" data-k="${c.key}">×</span></th>`;
|
| 311 |
+
});
|
| 312 |
+
html+='</tr>';
|
| 313 |
+
ATTRS.forEach(a=>{
|
| 314 |
+
html+=`<tr><td><strong>${a}</strong></td>`;
|
| 315 |
+
compareList.forEach(c=>{
|
| 316 |
+
let v=c.props[a]??'--';
|
| 317 |
+
if(numAttrs.includes(a)) v=numFmt(v);
|
| 318 |
+
html+=`<td>${v}</td>`;
|
| 319 |
+
});
|
| 320 |
+
html+='</tr>';
|
| 321 |
+
});
|
| 322 |
+
html+='</table>';
|
| 323 |
+
tgt.innerHTML=html;
|
| 324 |
+
tgt.querySelectorAll('.compare-remove').forEach(x=>{
|
| 325 |
+
x.onclick=()=>{compareList=compareList.filter(c=>c.key!==x.dataset.k);updateCompare();};
|
| 326 |
+
});
|
| 327 |
+
}
|
| 328 |
+
function attachCompare(){
|
| 329 |
+
map.on('popupopen',e=>{
|
| 330 |
+
const btn=e.popup._contentNode.querySelector('.addCompare');
|
| 331 |
+
if(!btn) return;
|
| 332 |
+
btn.onclick=()=>{
|
| 333 |
+
const props=e.popup._source.feature.properties;
|
| 334 |
+
const key=btn.dataset.key, layerType=btn.dataset.type;
|
| 335 |
+
if(compareList.find(c=>c.key===key)) return;
|
| 336 |
+
if(compareList.length>=5){alert('Tối đa 5 mục so sánh.');return;}
|
| 337 |
+
compareList.push({key,props,layer:layerType});
|
| 338 |
+
updateCompare();
|
| 339 |
+
};
|
| 340 |
+
});
|
| 341 |
+
}
|
| 342 |
+
function updateLabels() {
|
| 343 |
+
const zoom = map.getZoom();
|
| 344 |
+
map.eachLayer(layer => {
|
| 345 |
+
if (layer.getTooltip && layer.getTooltip()) {
|
| 346 |
+
const tooltip = layer.getTooltip();
|
| 347 |
+
if (tooltip.options.className && tooltip.options.className.includes('province-label')) {
|
| 348 |
+
const el = tooltip.getElement();
|
| 349 |
+
if (el) {
|
| 350 |
+
if (zoom < 7) {el.style.display = 'none';}
|
| 351 |
+
else {el.style.display = 'block'; el.style.fontSize = Math.min(Math.max(zoom * 1.5, 12), 22) + 'px';}
|
| 352 |
+
}
|
| 353 |
+
}
|
| 354 |
+
}
|
| 355 |
+
});
|
| 356 |
+
}
|
| 357 |
+
document.addEventListener('DOMContentLoaded',()=>{
|
| 358 |
+
init(); attachCompare();
|
| 359 |
+
map && map.whenReady(() => { updateLabels(); });
|
| 360 |
+
map && map.on('zoomend', updateLabels);
|
| 361 |
+
map && map.on('layeradd layerremove', updateLabels);
|
| 362 |
+
const toggleBtn = document.getElementById('toggleCompare');
|
| 363 |
+
const comparePanel = document.getElementById('comparePanel');
|
| 364 |
+
toggleBtn.addEventListener('click', () => {
|
| 365 |
+
const isHidden = comparePanel.style.display === 'none' || comparePanel.style.display === '';
|
| 366 |
+
if (isHidden) {
|
| 367 |
+
comparePanel.style.display = 'block';
|
| 368 |
+
toggleBtn.innerHTML = '<i class="fas fa-eye-slash"></i> Ẩn So sánh';
|
| 369 |
+
} else {
|
| 370 |
+
comparePanel.style.display = 'none';
|
| 371 |
+
toggleBtn.innerHTML = '<i class="fas fa-eye"></i> Hiện So sánh';
|
| 372 |
+
}
|
| 373 |
+
});
|
| 374 |
+
});
|
| 375 |
+
</script>
|
| 376 |
+
</body>
|
| 377 |
</html>
|