Spaces:
Running
Running
File size: 8,703 Bytes
a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 9848111 a62f8b4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | // =========================
// CONFIG map-coordinate.js
// =========================
// Tile-center interpolation β mα»i tile cΓ³ center lat/lon chΓnh xΓ‘c tα»«
// url_index.txt, dΓΉng lΓ m anchor Δα» tΓnh offset sub-tile β giαΊ£m tα»i Δa sai sα».
//
// Grid: 23 cols Γ 42 rows = 966 tiles, mα»i tile 2400Γ1442 px native.
// url_index.txt format: idx;lat;lon (center cα»§a tile, 1-indexed)
//
// TαΊ‘i sao Γt sai sα» hΖ‘n:
// CΕ©: anchor top-left β offset tΓch lΕ©y 42 hΓ ng β ~0.0015Β° (~165m)
// Mα»i: anchor center tile hiα»n tαΊ‘i (ground-truth) β offset β€ nα»a tile β <5m
const _TC_COLS = 23;
const _TC_ROWS = 42;
const _TC_TW = 2400; // tile width (native px)
const _TC_TH = 1442; // tile height (native px)
// Global-average lat/lon per pixel (dΓΉng Δα» Ζ°α»c lượng tile + fallback)
// lat step β 0.0075633Β°/row Γ· 1442 px β 5.245e-6 Β°/px
// lon step β 0.0128531Β°/col Γ· 2400 px β 5.355e-6 Β°/px
const _AVG_LAT_PP = 5.245e-6;
const _AVG_LON_PP = 5.355e-6;
// _tileCenters[idx] = {lat, lon}, 1-indexed (idx 1..966)
window._tileCenters = null;
// ββ Loader ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
window.loadTileCenters = async function () {
if (window._tileCenters) return;
try {
const res = await fetch('url_index.txt');
const text = await res.text();
const centers = new Array(_TC_COLS * _TC_ROWS + 2);
text.trim().split('\n').forEach(line => {
const p = line.trim().split(';');
if (p.length < 3) return;
const idx = parseInt(p[0], 10);
const lat = parseFloat(p[1]);
const lon = parseFloat(p[2]);
if (!isNaN(idx) && !isNaN(lat) && !isNaN(lon) &&
idx >= 1 && idx <= _TC_COLS * _TC_ROWS) {
centers[idx] = { lat, lon };
}
});
window._tileCenters = centers;
console.log('[map-coordinate] Loaded', _TC_COLS * _TC_ROWS,
'tile centers from url_index.txt');
} catch (e) {
console.warn('[map-coordinate] Cannot load url_index.txt β using fallback.', e);
}
};
// BαΊ―t ΔαΊ§u fetch ngay khi script load (fire-and-forget)
window.loadTileCenters();
// ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/** Center {lat,lon} cα»§a tile tαΊ‘i display (row, col) β 0-indexed */
function _tc(row, col) {
row = Math.max(0, Math.min(_TC_ROWS - 1, row));
col = Math.max(0, Math.min(_TC_COLS - 1, col));
return window._tileCenters[row * _TC_COLS + col + 1] || { lat: 0, lon: 0 };
}
/**
* Lat/lon per pixel cα»₯c bα» cho tile (row, col).
* DΓΉng center-to-center distance cα»§a tiles kα» bΓͺn.
* NαΊΏu tile irregular (cuα»i map), clamp vα» global average.
*/
function _localScale(row, col) {
let latPerPx, lonPerPx;
if (row > 0 && row < _TC_ROWS - 1) {
latPerPx = (_tc(row - 1, col).lat - _tc(row + 1, col).lat) / (2 * _TC_TH);
} else if (row === 0) {
latPerPx = (_tc(0, col).lat - _tc(1, col).lat) / _TC_TH;
} else {
latPerPx = (_tc(_TC_ROWS - 2, col).lat - _tc(_TC_ROWS - 1, col).lat) / _TC_TH;
}
if (col > 0 && col < _TC_COLS - 1) {
lonPerPx = (_tc(row, col + 1).lon - _tc(row, col - 1).lon) / (2 * _TC_TW);
} else if (col === 0) {
lonPerPx = (_tc(row, 1).lon - _tc(row, 0).lon) / _TC_TW;
} else {
lonPerPx = (_tc(row, _TC_COLS - 1).lon - _tc(row, _TC_COLS - 2).lon) / _TC_TW;
}
// Clamp nαΊΏu tile irregular lΓ m scale lα»ch > 3Γ
if (latPerPx < _AVG_LAT_PP * 0.3 || latPerPx > _AVG_LAT_PP * 3) latPerPx = _AVG_LAT_PP;
if (lonPerPx < _AVG_LON_PP * 0.3 || lonPerPx > _AVG_LON_PP * 3) lonPerPx = _AVG_LON_PP;
return { latPerPx, lonPerPx };
}
// ββ Fallback tuyαΊΏn tΓnh (trΖ°α»c khi url_index.txt load xong) βββββββββββββββββ
const _FB_TL_LAT = 10.947849, _FB_TL_LON = 106.532560;
const _FB_BR_LAT = 10.940261, _FB_BR_LON = 106.545442;
function _fallbackPixelToLatLon(left, top, scale) {
const lonPP = (_FB_BR_LON - _FB_TL_LON) / _TC_TW;
const latPP = (_FB_TL_LAT - _FB_BR_LAT) / _TC_TH;
const nx = (left + 1) / scale, ny = (top + 1) / scale;
return { lat: _FB_TL_LAT - ny * latPP, lon: _FB_TL_LON + nx * lonPP };
}
function _fallbackLatLonToPixel(lat, lon, scale) {
const lonPP = (_FB_BR_LON - _FB_TL_LON) / _TC_TW;
const latPP = (_FB_TL_LAT - _FB_BR_LAT) / _TC_TH;
return {
left: (((lon - _FB_TL_LON) / lonPP) * scale) - 1,
top: (((_FB_TL_LAT - lat) / latPP) * scale) - 1,
};
}
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// pixelToLatLon
//
// 1. Native px: undo scale + 1px offset
// 2. Floor-divide β xΓ‘c Δα»nh tile nΓ o Δang click (display row/col)
// 3. Anchor = center lat/lon cα»§a tile (ground-truth tα»« url_index.txt)
// 4. Sub-tile offset (native px tα»« center) Γ local scale β delta lat/lon
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
window.pixelToLatLon = function (left, top, scale) {
scale = scale || 1;
if (!window._tileCenters) return _fallbackPixelToLatLon(left, top, scale);
const nx = (left + 1) / scale;
const ny = (top + 1) / scale;
const col = Math.max(0, Math.min(_TC_COLS - 1, Math.floor(nx / _TC_TW)));
const row = Math.max(0, Math.min(_TC_ROWS - 1, Math.floor(ny / _TC_TH)));
const cx = (col + 0.5) * _TC_TW;
const cy = (row + 0.5) * _TC_TH;
const center = _tc(row, col);
const { latPerPx, lonPerPx } = _localScale(row, col);
const dx = nx - cx; // native px, + = ΔΓ΄ng
const dy = ny - cy; // native px, + = nam
return {
lat: center.lat - dy * latPerPx,
lon: center.lon + dx * lonPerPx,
};
};
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// latLonToPixel
//
// 1. Ζ―α»c lượng display tile tα»« tile #1 center + global-average scale
// 2. TΓ¬m kiαΊΏm 3Γ3 window β tile center gαΊ§n nhαΊ₯t vα»i (lat, lon)
// 3. Anchor + sub-tile offset β scaled pixel
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
window.latLonToPixel = function (lat, lon, scale) {
scale = scale || 1;
if (!window._tileCenters) return _fallbackLatLonToPixel(lat, lon, scale);
// 1. Ζ―α»c lượng tα»« tile #1 center
const c1 = window._tileCenters[1];
const approxRow = Math.round((c1.lat - lat) / (_AVG_LAT_PP * _TC_TH));
const approxCol = Math.round((lon - c1.lon) / (_AVG_LON_PP * _TC_TW));
// 2. TΓ¬m tile center gαΊ§n nhαΊ₯t trong 3Γ3 window
let bestRow = Math.max(0, Math.min(_TC_ROWS - 1, approxRow));
let bestCol = Math.max(0, Math.min(_TC_COLS - 1, approxCol));
let bestDist = Infinity;
const rMin = Math.max(0, approxRow - 1), rMax = Math.min(_TC_ROWS - 1, approxRow + 1);
const cMin = Math.max(0, approxCol - 1), cMax = Math.min(_TC_COLS - 1, approxCol + 1);
for (let r = rMin; r <= rMax; r++) {
for (let c = cMin; c <= cMax; c++) {
const t = _tc(r, c);
const d = (lat - t.lat) ** 2 + (lon - t.lon) ** 2;
if (d < bestDist) { bestDist = d; bestRow = r; bestCol = c; }
}
}
// 3. Anchor + sub-tile offset β scaled pixel
const center = _tc(bestRow, bestCol);
const { latPerPx, lonPerPx } = _localScale(bestRow, bestCol);
const dx = (lon - center.lon) / lonPerPx; // native px, + = ΔΓ΄ng
const dy = (center.lat - lat) / latPerPx; // native px, + = nam
const cx = (bestCol + 0.5) * _TC_TW;
const cy = (bestRow + 0.5) * _TC_TH;
return {
left: (cx + dx) * scale - 1,
top: (cy + dy) * scale - 1,
};
}; |