const OFFLINE_MAP_DIR_KEY = 'OFFLINE_MAP_DIR__'; const OFFLINE_MAP_SERVER_DIR_KEY = 'OFFLINE_MAP_SERVER_DIR__'; export const OFFLINE_MAP_DIR_CHANGED_EVENT = 'offline-map-dir-changed'; const LOCAL_MAP_PROTOCOL = 'localmap://'; function isValidOfflineMapPath(value: string) { const text = String(value || '').trim(); if (!text) return false; if (text === '[object Object]') return false; if (text === 'null' || text === 'undefined') return false; return ( /^[a-zA-Z]:[\\/]/.test(text) || text.startsWith('/') || text.startsWith('file://') || text.startsWith(LOCAL_MAP_PROTOCOL) ); } function normalizePath(value: string) { const text = String(value || '').trim(); if (!isValidOfflineMapPath(text)) return ''; return text.replace(/[\\/]+$/, ''); } export function getOfflineMapDir() { try { const serverDir = normalizePath(window.localStorage.getItem(OFFLINE_MAP_SERVER_DIR_KEY) || ''); if (serverDir) return serverDir; return normalizePath(window.localStorage.getItem(OFFLINE_MAP_DIR_KEY) || ''); } catch { return ''; } } export function getManualOfflineMapDir() { try { return normalizePath(window.localStorage.getItem(OFFLINE_MAP_DIR_KEY) || ''); } catch { return ''; } } export function getServerOfflineMapDir() { try { return normalizePath(window.localStorage.getItem(OFFLINE_MAP_SERVER_DIR_KEY) || ''); } catch { return ''; } } export function setOfflineMapDir(path: string) { const normalized = normalizePath(path); try { if (!normalized) { window.localStorage.removeItem(OFFLINE_MAP_DIR_KEY); window.dispatchEvent(new CustomEvent(OFFLINE_MAP_DIR_CHANGED_EVENT, { detail: '' })); return; } window.localStorage.setItem(OFFLINE_MAP_DIR_KEY, normalized); window.dispatchEvent(new CustomEvent(OFFLINE_MAP_DIR_CHANGED_EVENT, { detail: normalized })); } catch {} } export function clearOfflineMapDir() { setOfflineMapDir(''); } export function setServerOfflineMapDir(path: string) { const normalized = normalizePath(path); try { if (!normalized) { window.localStorage.removeItem(OFFLINE_MAP_SERVER_DIR_KEY); window.dispatchEvent(new CustomEvent(OFFLINE_MAP_DIR_CHANGED_EVENT, { detail: getOfflineMapDir() })); return; } window.localStorage.setItem(OFFLINE_MAP_SERVER_DIR_KEY, normalized); window.dispatchEvent(new CustomEvent(OFFLINE_MAP_DIR_CHANGED_EVENT, { detail: normalized })); } catch {} } export function buildFileUrl(filePath: string) { const normalized = normalizePath(filePath).replace(/\\/g, '/'); if (!normalized) return ''; if (new RegExp(`^${LOCAL_MAP_PROTOCOL}`, 'i').test(normalized)) return normalized; if (/^file:\/\//i.test(normalized)) return toLocalMapUrl(normalized); if (/^[a-zA-Z]:/.test(normalized)) { return encodeURI(`${LOCAL_MAP_PROTOCOL}/${normalized}`); } return encodeURI(`${LOCAL_MAP_PROTOCOL}${normalized.startsWith('/') ? '' : '/'}${normalized}`); } export function getOfflineMapPmtilesUrl(fileName = 'china.pmtiles') { const dir = getOfflineMapDir(); if (!dir) return ''; return buildFileUrl(`${dir}/${fileName}`); } export function getOfflineMapFileUrl(fileName: string) { const dir = getOfflineMapDir(); if (!dir || !fileName) return ''; return buildFileUrl(`${dir}/${fileName}`); } export function getOfflineMapFilePath(fileName: string) { const dir = getOfflineMapDir(); if (!dir || !fileName) return ''; return normalizePath(`${dir}/${fileName}`); } export function toLocalMapUrl(value: string) { const text = String(value || '').trim(); if (!text) return ''; if (new RegExp(`^${LOCAL_MAP_PROTOCOL}`, 'i').test(text)) return text; if (/^file:\/\//i.test(text)) { try { const url = new URL(text); const pathname = decodeURIComponent(url.pathname || ''); const normalized = /^[\\/][a-zA-Z]:/.test(pathname) ? pathname.slice(1) : pathname; return buildFileUrl(normalized); } catch { return text; } } if (/^[a-zA-Z]:/.test(text) || text.startsWith('/')) { return buildFileUrl(text); } return text; } export class ElectronPmtilesSource { filePath: string; constructor(filePath: string) { this.filePath = normalizePath(filePath); } getKey() { return this.filePath; } async getBytes(offset: number, length: number) { const ipcRenderer = (window as any)?.electron?.ipcRenderer; if (!ipcRenderer?.invoke || !this.filePath || !length) { return { data: new ArrayBuffer(0) }; } const payload = (await ipcRenderer.invoke('controller/os/readBinaryFileRange', { filePath: this.filePath, offset, length, })) || {}; const base64 = String(payload?.data || ''); const size = Number(payload?.size || 0); if (!base64 || !size) { return { data: new ArrayBuffer(0) }; } const binary = window.atob(base64); const bytes = new Uint8Array(binary.length); for (let index = 0; index < binary.length; index += 1) { bytes[index] = binary.charCodeAt(index); } return { data: bytes.buffer }; } }