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,
    };
};