tools / admin /js /maps /map-coordinate.js
Tataaicoltd's picture
fix: restore tile-center interpolation from url_index.txt
9848111 verified
Raw History Blame Contribute Delete
8.7 kB
// =========================
// 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,
};
};