Minjin1050/mn-geocoding-api
0
1<!DOCTYPE html>2<html lang="mn">3 <head>4 <meta charset="UTF-8" />5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />6 <title>Geocoding API (Pro Version)</title>7 <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />8 <style>9 :root {10 --primary: #2563eb;11 --error: #dc2626;12 --success: #16a34a;13 --bg: #f8fafc;14 }15 body {16 font-family: sans-serif;17 background: var(--bg);18 margin: 0;19 padding: 20px;20 }21 .container {22 max-width: 1000px;23 margin: auto;24 background: white;25 padding: 25px;26 border-radius: 12px;27 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);28 }29 30 /* Tabs */31 .tabs {32 display: flex;33 gap: 5px;34 margin-bottom: 20px;35 border-bottom: 2px solid #e2e8f0;36 }37 .tab-btn {38 padding: 10px 20px;39 cursor: pointer;40 border: none;41 background: none;42 font-weight: bold;43 color: #64748b;44 }45 .tab-btn.active {46 color: var(--primary);47 border-bottom: 2px solid var(--primary);48 }49 50 .section {51 margin-bottom: 20px;52 padding: 15px;53 border: 1px solid #e2e8f0;54 border-radius: 8px;55 }56 .input-group {57 display: flex;58 gap: 10px;59 margin-bottom: 15px;60 flex-wrap: wrap;61 }62 input[type="text"],63 input[type="number"] {64 flex: 1;65 padding: 10px;66 border: 1px solid #cbd5e1;67 border-radius: 5px;68 min-width: 150px;69 }70 button {71 padding: 10px 20px;72 cursor: pointer;73 border: none;74 border-radius: 5px;75 background: var(--primary);76 color: white;77 font-weight: bold;78 }79 80 .hidden {81 display: none;82 }83 #mapContainer {84 height: 450px;85 border-radius: 8px;86 border: 1px solid #cbd5e1;87 margin-top: 15px;88 }89 90 /* Custom Marker Styles */91 .marker-pin {92 width: 30px;93 height: 30px;94 border-radius: 50% 50% 50% 0;95 position: absolute;96 transform: rotate(-45deg);97 left: 50%;98 top: 50%;99 margin: -15px 0 0 -15px;100 display: flex;101 align-items: center;102 justify-content: center;103 color: white;104 font-weight: bold;105 border: 2px solid white;106 }107 .marker-pin span {108 transform: rotate(45deg);109 display: inline-block;110 }111 112 table {113 width: 100%;114 border-collapse: collapse;115 margin-top: 15px;116 font-size: 13px;117 }118 th,119 td {120 border: 1px solid #e2e8f0;121 padding: 8px;122 text-align: left;123 }124 th {125 background: #f1f5f9;126 }127 </style>128 </head>129 <body>130 <div class="container">131 <h1>📍 Geocoding Enterprise</h1>132 133 <div class="tabs">134 <button class="tab-btn active" onclick="openTab(event, 'searchTab')">135 🔍 Хайлт & Файл136 </button>137 <button class="tab-btn" onclick="openTab(event, 'validateTab')">138 🧪 50м Шалгалт139 </button>140 </div>141 142 <div id="searchTab" class="tab-content">143 <div class="section">144 <h3>🔍 Ганцаарчилсан хайлт</h3>145 <div class="input-group">146 <input147 type="text"148 id="addressInput"149 placeholder="Хаяг оруулна уу..."150 />151 <button id="geocodeButton">Хөрвүүлэх</button>152 </div>153 </div>154 155 <div class="section">156 <h3>📁 Олноор боловсруулах (CSV / JSON)</h3>157 <input type="file" id="fileInput" accept=".csv, .json" />158 <button id="uploadButton">Файл илгээх</button>159 <div160 id="batchStatus"161 class="hidden"162 style="margin-top: 10px; color: var(--primary)"163 ></div>164 </div>165 166 <div id="downloadSection" class="section hidden">167 <button onclick="exportData('csv')">CSV татах</button>168 <button onclick="exportData('json')" style="background: #475569">169 JSON татах170 </button>171 </div>172 </div>173 174 <div id="validateTab" class="tab-content hidden">175 <div class="section">176 <h3>🧪 Моделийн таамаглалыг шалгах (50m)</h3>177 <div class="input-group">178 <input type="text" id="v_address" placeholder="Текст хаяг..." />179 <input180 type="number"181 id="v_lat"182 step="any"183 placeholder="Өргөрөг (Lat)"184 />185 <input186 type="number"187 id="v_lon"188 step="any"189 placeholder="Уртраг (Lon)"190 />191 <button192 onclick="validateAddress()"193 style="background: var(--success)"194 >195 Шалгах196 </button>197 </div>198 <p style="font-size: 12px; color: #64748b">199 * Мап дээр дарж координатыг шууд авч болно.200 </p>201 </div>202 <div id="valResults" class="section hidden">203 <button onclick="downloadValCsv(false)">Бүгдийг татах</button>204 <button205 onclick="downloadValCsv(true)"206 style="background: var(--error)"207 >208 Зөвхөн бурууг татах209 </button>210 <div id="valTableContainer"></div>211 </div>212 </div>213 214 <div id="mapContainer"></div>215 </div>216 217 <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>218 <script>219 const API_BASE_URL = "https://minjin1050-mn-geocoding-api.hf.space"; let map, markerGroup;220 let batchResults = [];221 let valHistory = [];222 223 document.addEventListener("DOMContentLoaded", () => {224 map = L.map("mapContainer").setView([47.918, 106.918], 13);225 L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(226 map227 );228 markerGroup = L.layerGroup().addTo(map);229 230 // Газрын зураг дээр дарахад координат авах (Validate tab-д)231 map.on("click", (e) => {232 document.getElementById("v_lat").value = e.latlng.lat.toFixed(6);233 document.getElementById("v_lon").value = e.latlng.lng.toFixed(6);234 });235 });236 237 function openTab(evt, tabName) {238 document239 .querySelectorAll(".tab-content")240 .forEach((t) => t.classList.add("hidden"));241 document242 .querySelectorAll(".tab-btn")243 .forEach((b) => b.classList.remove("active"));244 document.getElementById(tabName).classList.remove("hidden");245 evt.currentTarget.classList.add("active");246 }247 248 // --- CUSTOM MARKER ---249 function createMarker(latlng, num, color, title, desc) {250 const icon = L.divIcon({251 className: "custom-pin",252 html: `<div style="background-color: ${color};" class="marker-pin"><span>${num}</span></div>`,253 iconSize: [30, 30],254 iconAnchor: [15, 30],255 });256 return L.marker(latlng, { icon: icon }).bindPopup(257 `<b>${title}</b><br>${desc}`258 );259 }260 261 // --- TAB 1: SINGLE & BATCH ---262 document.getElementById("geocodeButton").onclick = async () => {263 const addr = document.getElementById("addressInput").value;264 const res = await fetch(`${API_BASE_URL}/geocode/single`, {265 method: "POST",266 headers: { "Content-Type": "application/json" },267 body: JSON.stringify({ address: addr }),268 }).then((r) => r.json());269 270 markerGroup.clearLayers();271 if (res.latitude !== 0) {272 const m = createMarker(273 [res.latitude, res.longitude],274 1,275 "#2563eb",276 "Олдсон",277 res.address278 );279 markerGroup.addLayer(m);280 map.setView([res.latitude, res.longitude], 15);281 } else alert("Хаяг олдсонгүй");282 };283 284 document.getElementById("uploadButton").onclick = async () => {285 const file = document.getElementById("fileInput").files[0];286 if (!file) return alert("Файл сонгоно уу!");287 const formData = new FormData();288 formData.append("file", file);289 290 document.getElementById("batchStatus").classList.remove("hidden");291 document.getElementById("batchStatus").innerText =292 "Боловсруулж байна...";293 294 try {295 const resp = await fetch(`${API_BASE_URL}/geocode/batch-file`, {296 method: "POST",297 body: formData,298 });299 batchResults = await resp.json();300 document.getElementById(301 "batchStatus"302 ).innerText = `Нийт ${batchResults.length} хаяг бэлэн боллоо.`;303 document.getElementById("downloadSection").classList.remove("hidden");304 } catch (e) {305 alert("Алдаа гарлаа");306 }307 };308 309 // --- TAB 2: VALIDATE ---310 async function validateAddress() {311 const addr = document.getElementById("v_address").value;312 const lat = parseFloat(document.getElementById("v_lat").value);313 const lon = parseFloat(document.getElementById("v_lon").value);314 315 if (!addr || isNaN(lat)) return alert("Мэдээлэл дутуу");316 317 const res = await fetch(`${API_BASE_URL}/geocode/validate`, {318 method: "POST",319 headers: { "Content-Type": "application/json" },320 body: JSON.stringify({ address: addr, user_lat: lat, user_lon: lon }),321 }).then((r) => r.json());322 323 if (res.status === "error") return alert(res.message);324 // ... (fetch хийсний дараах хэсэг)325 markerGroup.clearLayers(); // Хуучин пинүүдийг арилгах326 327// Хэрэглэгчийн цэг (Цэнхэр)328 const userMarker = createMarker([lat, lon], "U", "#2563eb", "Таны өгсөн", addr);329 markerGroup.addLayer(userMarker);330 331// Моделийн цэг (Ногоон/Улаан)332 const pLat = res.predicted_coords.lat;333 const pLon = res.predicted_coords.lon;334 const predMarker = createMarker([pLat, pLon], "M", res.is_correct ? "#16a34a" : "#dc2626", "Модел", res.predicted_address);335 markerGroup.addLayer(predMarker);336 337// Газрын зургийг хоёр цэг харагдахаар тааруулах338const bounds = L.latLngBounds([lat, lon], [pLat, pLon]);339map.fitBounds(bounds, {padding: [50, 50]});340 const index = valHistory.length + 1;341 const isCorrect = res.is_correct;342 343 valHistory.push({ ...res, id: index });344 345 // Мап дээр зурах346 const uM = createMarker(347 [lat, lon],348 index,349 "#2563eb",350 "Таны өгсөн",351 addr352 );353 const pM = createMarker(354 [res.predicted_coords.lat, res.predicted_coords.lon],355 index,356 isCorrect ? "#16a34a" : "#dc2626",357 "Модел",358 res.predicted_address359 );360 361 markerGroup.addLayer(uM);362 markerGroup.addLayer(pM);363 renderValTable();364 document.getElementById("valResults").classList.remove("hidden");365 }366 367 function renderValTable() {368 let h = `<table><tr><th>ID</th><th>Таамаглал</th><th>Зай(м)</th><th>Төлөв</th></tr>`;369 valHistory.forEach((v) => {370 h += `<tr><td>${v.id}</td><td>${v.predicted_address}</td><td>${371 v.distance_meters372 }</td><td style="color:${v.is_correct ? "green" : "red"}">${373 v.is_correct ? "Зөв" : "Буруу"374 }</td></tr>`;375 });376 document.getElementById("valTableContainer").innerHTML = h + `</table>`;377 }378 379 function downloadValCsv(onlyErr) {380 const data = onlyErr381 ? valHistory.filter((v) => !v.is_correct)382 : valHistory;383 let csv = "\ufeffID,Input,Predicted,Distance,Status\n";384 data.forEach(385 (v) =>386 (csv += `${v.id},"${v.input_address}","${v.predicted_address}",${387 v.distance_meters388 },${v.is_correct ? "Ok" : "Fail"}\n`)389 );390 const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });391 const a = document.createElement("a");392 a.href = URL.createObjectURL(blob);393 a.download = "validation.csv";394 a.click();395 }396 397 function exportData(type) {398 let content, name, mime;399 if (type === "json") {400 content = JSON.stringify(batchResults, null, 2);401 name = "results.json";402 mime = "application/json";403 } else {404 content =405 "Input,Matched,Lat,Lon\n" +406 batchResults407 .map(408 (r) =>409 `"${r.input_address}","${r.address}",${r.latitude},${r.longitude}`410 )411 .join("\n");412 name = "results.csv";413 mime = "text/csv";414 }415 const a = document.createElement("a");416 a.href = URL.createObjectURL(new Blob([content], { type: mime }));417 a.download = name;418 a.click();419 }420 </script>421 </body>422</html>423 