From 16fa77eb09618d70900e363a7c41159e8b7fd8be Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sun, 5 Jan 2025 03:10:41 +0800 Subject: [PATCH] migrate map code to typescript --- src/DesktopApp.vue | 4 +- src/MobileApp.vue | 4 +- src/components/common/MapView.vue | 200 +++++++++++++------------ src/consts/map.ts | 14 +- src/lib/map/amap.js | 173 ---------------------- src/lib/map/amap.ts | 192 ++++++++++++++++++++++++ src/lib/map/baidumap.js | 166 --------------------- src/lib/map/baidumap.ts | 189 ++++++++++++++++++++++++ src/lib/map/base.ts | 31 ++++ src/lib/map/googlemap.js | 113 --------------- src/lib/map/googlemap.ts | 125 ++++++++++++++++ src/lib/map/index.js | 147 ------------------- src/lib/map/index.ts | 36 +++++ src/lib/map/leaflet.js | 199 ------------------------- src/lib/map/leaflet.ts | 234 ++++++++++++++++++++++++++++++ src/views/desktop/AboutPage.vue | 2 +- src/views/mobile/AboutPage.vue | 2 +- 17 files changed, 919 insertions(+), 912 deletions(-) delete mode 100644 src/lib/map/amap.js create mode 100644 src/lib/map/amap.ts delete mode 100644 src/lib/map/baidumap.js create mode 100644 src/lib/map/baidumap.ts create mode 100644 src/lib/map/base.ts delete mode 100644 src/lib/map/googlemap.js create mode 100644 src/lib/map/googlemap.ts delete mode 100644 src/lib/map/index.js create mode 100644 src/lib/map/index.ts delete mode 100644 src/lib/map/leaflet.js create mode 100644 src/lib/map/leaflet.ts diff --git a/src/DesktopApp.vue b/src/DesktopApp.vue index b3be8df6..03fff19f 100644 --- a/src/DesktopApp.vue +++ b/src/DesktopApp.vue @@ -29,7 +29,7 @@ import { useExchangeRatesStore } from '@/stores/exchangeRates.js'; import { APPLICATION_LOGO_PATH } from '@/consts/asset.ts'; import { ThemeType } from '@/core/theme.ts'; import { isProduction } from '@/lib/version.ts'; -import { loadMapAssets } from '@/lib/map/index.js'; +import { initMapProvider } from '@/lib/map/index.ts'; import { getSystemTheme, setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts'; export default { @@ -113,7 +113,7 @@ export default { mounted() { document.addEventListener('DOMContentLoaded', () => { const languageInfo = this.$locale.getCurrentLanguageInfo(); - loadMapAssets(languageInfo ? languageInfo.alternativeLanguageTag : null); + initMapProvider(languageInfo ? languageInfo.alternativeLanguageTag : null); }); } } diff --git a/src/MobileApp.vue b/src/MobileApp.vue index fc35e649..746bd3fa 100644 --- a/src/MobileApp.vue +++ b/src/MobileApp.vue @@ -19,7 +19,7 @@ import { APPLICATION_LOGO_PATH } from '@/consts/asset.ts'; import { ThemeType } from '@/core/theme.ts'; import { isProduction } from '@/lib/version.ts'; import { getTheme, isEnableAnimate } from '@/lib/settings.ts'; -import { loadMapAssets } from '@/lib/map/index.js'; +import { initMapProvider } from '@/lib/map/index.ts'; import { setExpenseAndIncomeAmountColor } from '@/lib/ui/common.ts'; import { isModalShowing, setAppFontSize } from '@/lib/ui/mobile.js'; @@ -194,7 +194,7 @@ export default { document.addEventListener('DOMContentLoaded', () => { const languageInfo = this.$locale.getCurrentLanguageInfo(); - loadMapAssets(languageInfo ? languageInfo.alternativeLanguageTag : null); + initMapProvider(languageInfo ? languageInfo.alternativeLanguageTag : null); }); }, methods: { diff --git a/src/components/common/MapView.vue b/src/components/common/MapView.vue index bfb731cd..4f017724 100644 --- a/src/components/common/MapView.vue +++ b/src/components/common/MapView.vue @@ -8,112 +8,110 @@ v-if="!mapSupported || !mapDependencyLoaded"> - diff --git a/src/consts/map.ts b/src/consts/map.ts index b07150e6..d1454f42 100644 --- a/src/consts/map.ts +++ b/src/consts/map.ts @@ -1,23 +1,23 @@ -export interface PresetLeafletTileSource { +export interface LeafletTileSource { readonly tileUrlFormat: string; readonly tileUrlSubDomains: string; - readonly tileUrlExtraParams?: PresetLeafletTileSourceExtraParam[]; + readonly tileUrlExtraParams?: LeafletTileSourceExtraParam[]; readonly annotationUrlFormat?: string; readonly annotationUrlSubDomains?: string; - readonly annotationUrlExtraParams?: PresetLeafletTileSourceExtraParam[]; + readonly annotationUrlExtraParams?: LeafletTileSourceExtraParam[]; readonly minZoom: number; readonly maxZoom: number; readonly defaultZoomLevel: number; - readonly website: string; - readonly attribution: string; + readonly website?: string; + readonly attribution?: string; } -export interface PresetLeafletTileSourceExtraParam { +export interface LeafletTileSourceExtraParam { readonly paramName: string; readonly paramValueType: string; } -export const LEAFLET_TILE_SOURCES: Record = { +export const LEAFLET_TILE_SOURCES: Record = { 'openstreetmap': { tileUrlFormat: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', tileUrlSubDomains: 'abc', diff --git a/src/lib/map/amap.js b/src/lib/map/amap.js deleted file mode 100644 index 65ce140f..00000000 --- a/src/lib/map/amap.js +++ /dev/null @@ -1,173 +0,0 @@ -import { asyncLoadAssets } from '@/lib/misc.ts'; -import services from '@/lib/services.js'; -import { - getAmapSecurityVerificationMethod, - getAmapApiExternalProxyUrl, - getAmapApplicationSecret -} from '@/lib/server_settings.ts'; -import logger from '@/lib/logger.ts'; - -const amapHolder = { - AMap: null -}; - -export function getAmapWebsite() { - return 'https://www.amap.com'; -} - -export function loadAmapAssets() { - if (amapHolder.AMap) { - return; - } - - if (!window._AMapSecurityConfig) { - const amapSecurityConfig = {}; - - if (getAmapSecurityVerificationMethod() === 'internalproxy') { - amapSecurityConfig.serviceHost = services.generateAmapApiInternalProxyUrl(); - } else if (getAmapSecurityVerificationMethod() === 'externalproxy') { - amapSecurityConfig.serviceHost = getAmapApiExternalProxyUrl(); - } else if (getAmapSecurityVerificationMethod() === 'plaintext') { - amapSecurityConfig.securityJsCode = getAmapApplicationSecret(); - } - - window._AMapSecurityConfig = amapSecurityConfig; - } - - if (!window.onAMapCallback) { - window.onAMapCallback = () => { - amapHolder.AMap = window.AMap; - }; - } - - return asyncLoadAssets('js', services.generateAmapJavascriptUrl('onAMapCallback')); -} - -export function createAmapHolder() { - return { - mapProvider: 'amap', - dependencyLoaded: !!amapHolder.AMap, - inited: false, - defaultZoomLevel: 14, - minZoomLevel: 1, - amapInstance: null, - amapToolbar: null, - amapCenterPosition: null, - amapCenterMarker: null - }; -} - -export function createAmapInstance(mapHolder, mapContainer, options) { - if (!amapHolder.AMap) { - return null; - } - - const AMap = amapHolder.AMap; - const amapInstance = new AMap.Map(mapContainer, { - zoom: options.zoomLevel, - center: [ options.initCenter.longitude, options.initCenter.latitude ], - zooms: [ 1, 19 ], - jogEnable: false - }); - - const amapToolbar = new AMap.ToolBar({ - position: 'LT' - }); - amapInstance.addControl(amapToolbar); - - mapHolder.amapInstance = amapInstance; - mapHolder.amapToolbar = amapToolbar; - mapHolder.inited = true; -} - -export function setAmapCenterTo(mapHolder, center, zoomLevel) { - if (!amapHolder.AMap || !mapHolder.amapInstance) { - return; - } - - const AMap = amapHolder.AMap; - - if (amapHolder.amapCenterPosition - && amapHolder.amapCenterPosition.originalLongitude === center.longitude - && amapHolder.amapCenterPosition.originalLatitude === center.latitude - && amapHolder.amapCenterPosition.convertedLongitude - && amapHolder.amapCenterPosition.convertedLatitude - ) { - mapHolder.amapInstance.setZoomAndCenter(zoomLevel, new AMap.LngLat(amapHolder.amapCenterPosition.convertedLongitude, amapHolder.amapCenterPosition.convertedLatitude)); - return; - } - - amapHolder.amapCenterPosition = { - originalLongitude: center.longitude, - originalLatitude: center.latitude, - convertedLongitude: null, - convertedLatitude: null - }; - - const centerPoint = new AMap.LngLat(center.longitude, center.latitude); - - AMap.convertFrom(centerPoint, 'gps', (status, result) => { - let convertedCenterPoint = centerPoint; - - if (result.info !== 'ok' || !result.locations) { - logger.warn('amap geo position convert failed'); - } else { - convertedCenterPoint = result.locations[0]; - amapHolder.amapCenterPosition.convertedLongitude = convertedCenterPoint.getLng(); - amapHolder.amapCenterPosition.convertedLatitude = convertedCenterPoint.getLat(); - } - - mapHolder.amapInstance.setZoomAndCenter(zoomLevel, convertedCenterPoint); - }); -} - -export function setAmapCenterMaker(mapHolder, position) { - if (!amapHolder.AMap || !mapHolder.amapInstance) { - return; - } - - const AMap = amapHolder.AMap; - const setMaker = function (point) { - if (!mapHolder.amapCenterMarker) { - mapHolder.amapCenterMarker = new AMap.Marker({ - position: point - }); - mapHolder.amapInstance.add(mapHolder.amapCenterMarker); - } else { - mapHolder.amapCenterMarker.setPosition(point); - } - } - - if (amapHolder.amapCenterPosition - && amapHolder.amapCenterPosition.originalLongitude === position.longitude - && amapHolder.amapCenterPosition.originalLatitude === position.latitude - && amapHolder.amapCenterPosition.convertedLongitude - && amapHolder.amapCenterPosition.convertedLatitude - ) { - setMaker(new AMap.LngLat(amapHolder.amapCenterPosition.convertedLongitude, amapHolder.amapCenterPosition.convertedLatitude)); - return; - } - - const markerPoint = new AMap.LngLat(position.longitude, position.latitude); - - AMap.convertFrom(markerPoint, 'gps', (status, result) => { - let convertedMarkPoint = markerPoint; - - if (result.info !== 'ok' || !result.locations) { - logger.warn('amap geo position convert failed'); - } else { - convertedMarkPoint = result.locations[0]; - } - - setMaker(convertedMarkPoint); - }); -} - -export function removeAmapCenterMaker(mapHolder) { - if (!mapHolder.amapInstance || !mapHolder.amapCenterMarker) { - return; - } - - mapHolder.amapInstance.remove(mapHolder.amapCenterMarker); - mapHolder.amapCenterMarker = null; -} diff --git a/src/lib/map/amap.ts b/src/lib/map/amap.ts new file mode 100644 index 00000000..4c96dfcb --- /dev/null +++ b/src/lib/map/amap.ts @@ -0,0 +1,192 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import type { MapProvider, MapInstance, MapInstanceInitOptions, MapPosition } from './base.ts'; + +import { asyncLoadAssets } from '@/lib/misc.ts'; +import services from '@/lib/services.js'; +import { + getAmapSecurityVerificationMethod, + getAmapApiExternalProxyUrl, + getAmapApplicationSecret +} from '@/lib/server_settings.ts'; +import logger from '@/lib/logger.ts'; + +export class AmapMapProvider implements MapProvider { + public static AMap: unknown = null; + + public getWebsite(): string { + return 'https://www.amap.com'; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public asyncLoadAssets(language: string): Promise { + if (AmapMapProvider.AMap) { + return Promise.resolve(); + } + + if (!window._AMapSecurityConfig) { + const amapSecurityConfig = {}; + + if (getAmapSecurityVerificationMethod() === 'internalproxy') { + amapSecurityConfig.serviceHost = services.generateAmapApiInternalProxyUrl(); + } else if (getAmapSecurityVerificationMethod() === 'externalproxy') { + amapSecurityConfig.serviceHost = getAmapApiExternalProxyUrl(); + } else if (getAmapSecurityVerificationMethod() === 'plaintext') { + amapSecurityConfig.securityJsCode = getAmapApplicationSecret(); + } + + window._AMapSecurityConfig = amapSecurityConfig; + } + + if (!window.onAMapCallback) { + window.onAMapCallback = () => { + AmapMapProvider.AMap = window.AMap; + }; + } + + return asyncLoadAssets('js', services.generateAmapJavascriptUrl('onAMapCallback')); + } + + public createMapInstance(): MapInstance | null { + return new AmapMapInstance(); + } +} + +export class AmapMapInstance implements MapInstance { + public dependencyLoaded: boolean = false; + public inited: boolean = false; + + public readonly defaultZoomLevel: number = 14; + public readonly minZoomLevel: number = 1; + + private amapInstance: unknown = null; + private amapToolbar: unknown = null; + private amapCenterPosition: unknown = null; + private amapCenterMarker: unknown | null; + + constructor() { + this.dependencyLoaded = !!AmapMapProvider.AMap; + } + + public initMapInstance(mapContainer: HTMLElement, options: MapInstanceInitOptions): void { + if (!AmapMapProvider.AMap) { + return; + } + + const AMap = AmapMapProvider.AMap; + const amapInstance = new AMap.Map(mapContainer, { + zoom: options.zoomLevel, + center: [ options.initCenter.longitude, options.initCenter.latitude ], + zooms: [ 1, 19 ], + jogEnable: false + }); + + const amapToolbar = new AMap.ToolBar({ + position: 'LT' + }); + amapInstance.addControl(amapToolbar); + + this.amapInstance = amapInstance; + this.amapToolbar = amapToolbar; + this.inited = true; + } + + public setMapCenterTo(center: MapPosition, zoomLevel: number): void { + if (!AmapMapProvider.AMap || !this.amapInstance) { + return; + } + + const AMap = AmapMapProvider.AMap; + + if (this.amapCenterPosition + && this.amapCenterPosition.originalLongitude === center.longitude + && this.amapCenterPosition.originalLatitude === center.latitude + && this.amapCenterPosition.convertedLongitude + && this.amapCenterPosition.convertedLatitude + ) { + this.amapInstance.setZoomAndCenter(zoomLevel, new AMap.LngLat(this.amapCenterPosition.convertedLongitude, this.amapCenterPosition.convertedLatitude)); + return; + } + + this.amapCenterPosition = { + originalLongitude: center.longitude, + originalLatitude: center.latitude, + convertedLongitude: null, + convertedLatitude: null + }; + + const centerPoint = new AMap.LngLat(center.longitude, center.latitude); + + AMap.convertFrom(centerPoint, 'gps', (status, result) => { + let convertedCenterPoint = centerPoint; + + if (result.info !== 'ok' || !result.locations) { + logger.warn('amap geo position convert failed'); + } else { + convertedCenterPoint = result.locations[0]; + this.amapCenterPosition.convertedLongitude = convertedCenterPoint.getLng(); + this.amapCenterPosition.convertedLatitude = convertedCenterPoint.getLat(); + } + + this.amapInstance.setZoomAndCenter(zoomLevel, convertedCenterPoint); + }); + } + + public setMapCenterMarker(position: MapPosition): void { + if (!AmapMapProvider.AMap || !this.amapInstance) { + return; + } + + const AMap = AmapMapProvider.AMap; + + if (this.amapCenterPosition + && this.amapCenterPosition.originalLongitude === position.longitude + && this.amapCenterPosition.originalLatitude === position.latitude + && this.amapCenterPosition.convertedLongitude + && this.amapCenterPosition.convertedLatitude + ) { + this.setMaker(new AMap.LngLat(this.amapCenterPosition.convertedLongitude, this.amapCenterPosition.convertedLatitude)); + return; + } + + const markerPoint = new AMap.LngLat(position.longitude, position.latitude); + + AMap.convertFrom(markerPoint, 'gps', (status, result) => { + let convertedMarkPoint = markerPoint; + + if (result.info !== 'ok' || !result.locations) { + logger.warn('amap geo position convert failed'); + } else { + convertedMarkPoint = result.locations[0]; + } + + this.setMaker(convertedMarkPoint); + }); + } + + public removeMapCenterMarker(): void { + if (!this.amapInstance || !this.amapCenterMarker) { + return; + } + + this.amapInstance.remove(this.amapCenterMarker); + this.amapCenterMarker = null; + } + + private setMaker(point: unknown): void { + if (!AmapMapProvider.AMap || !this.amapInstance) { + return; + } + + const AMap = AmapMapProvider.AMap; + + if (!this.amapCenterMarker) { + this.amapCenterMarker = new AMap.Marker({ + position: point + }); + this.amapInstance.add(this.amapCenterMarker); + } else { + this.amapCenterMarker.setPosition(point); + } + } +} diff --git a/src/lib/map/baidumap.js b/src/lib/map/baidumap.js deleted file mode 100644 index 664ccfb3..00000000 --- a/src/lib/map/baidumap.js +++ /dev/null @@ -1,166 +0,0 @@ -import { asyncLoadAssets } from '@/lib/misc.ts'; -import services from '@/lib/services.js'; -import logger from '@/lib/logger.ts'; - -const baiduMapHolder = { - BMap: null, - BMAP_NAVIGATION_CONTROL_ZOOM: window.BMAP_NAVIGATION_CONTROL_ZOOM || 3, - BMAP_ANCHOR_TOP_LEFT: window.BMAP_ANCHOR_TOP_LEFT || 0, - COORDINATES_WGS84: window.COORDINATES_WGS84 || 1, - COORDINATES_BD09: window.COORDINATES_BD09 || 5 -}; - -export function getBaiduMapWebsite() { - return 'https://map.baidu.com'; -} - -export function loadBaiduMapAssets() { - if (baiduMapHolder.BMap) { - return; - } - - if (!window.onBMapCallback) { - window.onBMapCallback = () => { - baiduMapHolder.BMap = window.BMap; - }; - } - - return asyncLoadAssets('js', services.generateBaiduMapJavascriptUrl('onBMapCallback')); -} - -export function createBaiduMapHolder() { - return { - mapProvider: 'baidumap', - dependencyLoaded: !!baiduMapHolder.BMap, - inited: false, - defaultZoomLevel: 15, - minZoomLevel: 1, - baiduMapInstance: null, - baiduMapConverter: null, - baiduMapNavigationControl: null, - baiduMapCenterPosition: null, - baiduMapCenterMarker: null - }; -} - -export function createBaiduMapInstance(mapHolder, mapContainer, options) { - if (!baiduMapHolder.BMap) { - return null; - } - - const BMap = baiduMapHolder.BMap; - const baiduMapInstance = new BMap.Map(mapContainer, { - maxZoom: 19 - }); - baiduMapInstance.enableScrollWheelZoom(); - - const baiduMapNavigationControl = new BMap.NavigationControl({ - type: baiduMapHolder.BMAP_NAVIGATION_CONTROL_ZOOM, - anchor: baiduMapHolder.BMAP_ANCHOR_TOP_LEFT - }); - baiduMapInstance.addControl(baiduMapNavigationControl); - baiduMapInstance.centerAndZoom(new BMap.Point(options.initCenter.longitude, options.initCenter.latitude), options.zoomLevel); - - mapHolder.baiduMapInstance = baiduMapInstance; - mapHolder.baiduMapConverter = new BMap.Convertor(); - mapHolder.baiduMapNavigationControl = baiduMapNavigationControl; - mapHolder.inited = true; -} - -export function setBaiduMapCenterTo(mapHolder, center, zoomLevel) { - if (!baiduMapHolder.BMap || !mapHolder.baiduMapInstance) { - return; - } - - const BMap = baiduMapHolder.BMap; - - if (baiduMapHolder.baiduMapCenterPosition - && baiduMapHolder.baiduMapCenterPosition.originalLongitude === center.longitude - && baiduMapHolder.baiduMapCenterPosition.originalLatitude === center.latitude - && baiduMapHolder.baiduMapCenterPosition.convertedLongitude - && baiduMapHolder.baiduMapCenterPosition.convertedLatitude - ) { - mapHolder.baiduMapInstance.centerAndZoom(new BMap.Point(baiduMapHolder.baiduMapCenterPosition.convertedLongitude, baiduMapHolder.baiduMapCenterPosition.convertedLatitude), zoomLevel); - return; - } - - baiduMapHolder.baiduMapCenterPosition = { - originalLongitude: center.longitude, - originalLatitude: center.latitude, - convertedLongitude: null, - convertedLatitude: null - }; - - const centerPoint = new BMap.Point(center.longitude, center.latitude); - - if (mapHolder.baiduMapConverter) { - mapHolder.baiduMapConverter.translate([ centerPoint ], baiduMapHolder.COORDINATES_WGS84, baiduMapHolder.COORDINATES_BD09, data => { - let convertedCenterPoint = centerPoint; - - if (data.status !== 0 || !data.points) { - logger.warn('baidu map geo position convert failed'); - } else { - convertedCenterPoint = data.points[0]; - baiduMapHolder.baiduMapCenterPosition.convertedLongitude = convertedCenterPoint.lng; - baiduMapHolder.baiduMapCenterPosition.convertedLatitude = convertedCenterPoint.lat; - } - - mapHolder.baiduMapInstance.centerAndZoom(convertedCenterPoint, zoomLevel); - }); - } else { - mapHolder.baiduMapInstance.centerAndZoom(centerPoint, zoomLevel); - } -} - -export function setBaiduMapCenterMaker(mapHolder, position) { - if (!baiduMapHolder.BMap || !mapHolder.baiduMapInstance) { - return; - } - - const BMap = baiduMapHolder.BMap; - const setMaker = function (point) { - if (!mapHolder.baiduMapCenterMarker) { - mapHolder.baiduMapCenterMarker = new BMap.Marker(point); - mapHolder.baiduMapInstance.addOverlay(mapHolder.baiduMapCenterMarker); - } else { - mapHolder.baiduMapCenterMarker.setPosition(point); - } - } - - if (baiduMapHolder.baiduMapCenterPosition - && baiduMapHolder.baiduMapCenterPosition.originalLongitude === position.longitude - && baiduMapHolder.baiduMapCenterPosition.originalLatitude === position.latitude - && baiduMapHolder.baiduMapCenterPosition.convertedLongitude - && baiduMapHolder.baiduMapCenterPosition.convertedLatitude - ) { - setMaker(new BMap.Point(baiduMapHolder.baiduMapCenterPosition.convertedLongitude, baiduMapHolder.baiduMapCenterPosition.convertedLatitude)); - return; - } - - const markerPoint = new BMap.Point(position.longitude, position.latitude); - - if (mapHolder.baiduMapConverter) { - mapHolder.baiduMapConverter.translate([ markerPoint ], baiduMapHolder.COORDINATES_WGS84, baiduMapHolder.COORDINATES_BD09, data => { - let convertedMarkPoint = markerPoint; - - if (data.status !== 0 || !data.points) { - logger.warn('baidu map geo position convert failed'); - } else { - convertedMarkPoint = data.points[0]; - } - - setMaker(convertedMarkPoint); - }); - } else { - setMaker(markerPoint); - } -} - -export function removeBaiduMapCenterMaker(mapHolder) { - if (!mapHolder.baiduMapInstance || !mapHolder.baiduMapCenterMarker) { - return; - } - - mapHolder.baiduMapInstance.removeOverlay(mapHolder.baiduMapCenterMarker); - mapHolder.baiduMapCenterMarker = null; -} diff --git a/src/lib/map/baidumap.ts b/src/lib/map/baidumap.ts new file mode 100644 index 00000000..35209839 --- /dev/null +++ b/src/lib/map/baidumap.ts @@ -0,0 +1,189 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import type { MapProvider, MapInstance, MapInstanceInitOptions, MapPosition } from './base.ts'; + +import { asyncLoadAssets } from '@/lib/misc.ts'; +import services from '@/lib/services.js'; +import logger from '@/lib/logger.ts'; + +export class BaiduMapProvider implements MapProvider { + public static BMap: unknown = null; + public static BMAP_NAVIGATION_CONTROL_ZOOM: unknown = window.BMAP_NAVIGATION_CONTROL_ZOOM || 3; + public static BMAP_ANCHOR_TOP_LEFT: unknown = window.BMAP_ANCHOR_TOP_LEFT || 0; + public static COORDINATES_WGS84: unknown = window.COORDINATES_WGS84 || 1; + public static COORDINATES_BD09: unknown = window.COORDINATES_BD09 || 5; + + public getWebsite(): string { + return 'https://map.baidu.com'; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public asyncLoadAssets(language: string): Promise { + if (BaiduMapProvider.BMap) { + return Promise.resolve(); + } + + if (!window.onBMapCallback) { + window.onBMapCallback = () => { + BaiduMapProvider.BMap = window.BMap; + BaiduMapProvider.BMAP_NAVIGATION_CONTROL_ZOOM = window.BMAP_NAVIGATION_CONTROL_ZOOM || 3; + BaiduMapProvider.BMAP_ANCHOR_TOP_LEFT = window.BMAP_ANCHOR_TOP_LEFT || 0; + BaiduMapProvider.COORDINATES_WGS84 = window.COORDINATES_WGS84 || 1; + BaiduMapProvider.COORDINATES_BD09 = window.COORDINATES_BD09 || 5; + }; + } + + return asyncLoadAssets('js', services.generateBaiduMapJavascriptUrl('onBMapCallback')); + } + + public createMapInstance(): MapInstance | null { + return new BaiduMapInstance(); + } +} + +export class BaiduMapInstance implements MapInstance { + public dependencyLoaded: boolean = false; + public inited: boolean = false; + + public readonly defaultZoomLevel: number = 15; + public readonly minZoomLevel: number = 1; + + private baiduMapInstance: unknown = null; + private baiduMapConverter: unknown = null; + private baiduMapNavigationControl: unknown = null; + private baiduMapCenterPosition: unknown = null; + private baiduMapCenterMarker: unknown | null; + + constructor() { + this.dependencyLoaded = !!BaiduMapProvider.BMap; + } + + public initMapInstance(mapContainer: HTMLElement, options: MapInstanceInitOptions): void { + if (!BaiduMapProvider.BMap) { + return; + } + + const BMap = BaiduMapProvider.BMap; + const baiduMapInstance = new BMap.Map(mapContainer, { + maxZoom: 19 + }); + baiduMapInstance.enableScrollWheelZoom(); + + const baiduMapNavigationControl = new BMap.NavigationControl({ + type: BaiduMapProvider.BMAP_NAVIGATION_CONTROL_ZOOM, + anchor: BaiduMapProvider.BMAP_ANCHOR_TOP_LEFT + }); + baiduMapInstance.addControl(baiduMapNavigationControl); + baiduMapInstance.centerAndZoom(new BMap.Point(options.initCenter.longitude, options.initCenter.latitude), options.zoomLevel); + + this.baiduMapInstance = baiduMapInstance; + this.baiduMapConverter = new BMap.Convertor(); + this.baiduMapNavigationControl = baiduMapNavigationControl; + this.inited = true; + } + + public setMapCenterTo(center: MapPosition, zoomLevel: number): void { + if (!BaiduMapProvider.BMap || !this.baiduMapInstance) { + return; + } + + const BMap = BaiduMapProvider.BMap; + + if (this.baiduMapCenterPosition + && this.baiduMapCenterPosition.originalLongitude === center.longitude + && this.baiduMapCenterPosition.originalLatitude === center.latitude + && this.baiduMapCenterPosition.convertedLongitude + && this.baiduMapCenterPosition.convertedLatitude + ) { + this.baiduMapInstance.centerAndZoom(new BMap.Point(this.baiduMapCenterPosition.convertedLongitude, this.baiduMapCenterPosition.convertedLatitude), zoomLevel); + return; + } + + this.baiduMapCenterPosition = { + originalLongitude: center.longitude, + originalLatitude: center.latitude, + convertedLongitude: null, + convertedLatitude: null + }; + + const centerPoint = new BMap.Point(center.longitude, center.latitude); + + if (this.baiduMapConverter) { + this.baiduMapConverter.translate([ centerPoint ], BaiduMapProvider.COORDINATES_WGS84, BaiduMapProvider.COORDINATES_BD09, data => { + let convertedCenterPoint = centerPoint; + + if (data.status !== 0 || !data.points) { + logger.warn('baidu map geo position convert failed'); + } else { + convertedCenterPoint = data.points[0]; + this.baiduMapCenterPosition.convertedLongitude = convertedCenterPoint.lng; + this.baiduMapCenterPosition.convertedLatitude = convertedCenterPoint.lat; + } + + this.baiduMapInstance.centerAndZoom(convertedCenterPoint, zoomLevel); + }); + } else { + this.baiduMapInstance.centerAndZoom(centerPoint, zoomLevel); + } + } + + public setMapCenterMarker(position: MapPosition): void { + if (!BaiduMapProvider.BMap || !this.baiduMapInstance) { + return; + } + + const BMap = BaiduMapProvider.BMap; + + if (this.baiduMapCenterPosition + && this.baiduMapCenterPosition.originalLongitude === position.longitude + && this.baiduMapCenterPosition.originalLatitude === position.latitude + && this.baiduMapCenterPosition.convertedLongitude + && this.baiduMapCenterPosition.convertedLatitude + ) { + this.setMaker(new BMap.Point(this.baiduMapCenterPosition.convertedLongitude, this.baiduMapCenterPosition.convertedLatitude)); + return; + } + + const markerPoint = new BMap.Point(position.longitude, position.latitude); + + if (this.baiduMapConverter) { + this.baiduMapConverter.translate([ markerPoint ], BaiduMapProvider.COORDINATES_WGS84, BaiduMapProvider.COORDINATES_BD09, data => { + let convertedMarkPoint = markerPoint; + + if (data.status !== 0 || !data.points) { + logger.warn('baidu map geo position convert failed'); + } else { + convertedMarkPoint = data.points[0]; + } + + this.setMaker(convertedMarkPoint); + }); + } else { + this.setMaker(markerPoint); + } + } + + public removeMapCenterMarker(): void { + if (!this.baiduMapInstance || !this.baiduMapCenterMarker) { + return; + } + + this.baiduMapInstance.removeOverlay(this.baiduMapCenterMarker); + this.baiduMapCenterMarker = null; + } + + private setMaker(point: unknown): void { + if (!BaiduMapProvider.BMap || !this.baiduMapInstance) { + return; + } + + const BMap = BaiduMapProvider.BMap; + + if (!this.baiduMapCenterMarker) { + this.baiduMapCenterMarker = new BMap.Marker(point); + this.baiduMapInstance.addOverlay(this.baiduMapCenterMarker); + } else { + this.baiduMapCenterMarker.setPosition(point); + } + } +} diff --git a/src/lib/map/base.ts b/src/lib/map/base.ts new file mode 100644 index 00000000..a5bc8ed8 --- /dev/null +++ b/src/lib/map/base.ts @@ -0,0 +1,31 @@ +export interface MapProvider { + getWebsite(): string; + asyncLoadAssets(language: string): Promise; + createMapInstance(): MapInstance | null; +} + +export interface MapInstance { + dependencyLoaded: boolean; + inited: boolean; + readonly defaultZoomLevel: number; + readonly minZoomLevel: number; + initMapInstance(mapContainer: HTMLElement, options: MapInstanceInitOptions): void; + setMapCenterTo(center: MapPosition, zoomLevel: number): void; + setMapCenterMarker(position: MapPosition): void; + removeMapCenterMarker(): void; +} + +export interface MapInstanceInitOptions { + readonly language?: string; + readonly initCenter: MapPosition; + readonly zoomLevel: number; + readonly text: { + readonly zoomIn: string; + readonly zoomOut: string; + } +} + +export interface MapPosition { + latitude: number; + longitude: number; +} diff --git a/src/lib/map/googlemap.js b/src/lib/map/googlemap.js deleted file mode 100644 index 3b5795ba..00000000 --- a/src/lib/map/googlemap.js +++ /dev/null @@ -1,113 +0,0 @@ -import { asyncLoadAssets } from '@/lib/misc.ts'; -import services from '@/lib/services.js'; - -const googleMapHolder = { - googleMap: null, - ControlPosition: { - LEFT_TOP: (window.google && window.google.maps && window.google.maps.ControlPosition) ? window.google.maps.ControlPosition.LEFT_TOP : 5 - } -}; - -export function getGoogleMapWebsite() { - return 'https://maps.google.com'; -} - -export function loadGoogleMapAssets(language) { - if (googleMapHolder.googleMap) { - return; - } - - if (!window.onGoogleMapCallback) { - window.onGoogleMapCallback = () => { - if (window.google) { - googleMapHolder.googleMap = window.google.maps; - } - }; - } - - return asyncLoadAssets('js', services.generateGoogleMapJavascriptUrl(language, 'onGoogleMapCallback')); -} - -export function createGoogleMapHolder() { - return { - mapProvider: 'googlemap', - dependencyLoaded: !!googleMapHolder.googleMap, - inited: false, - defaultZoomLevel: 14, - minZoomLevel: 1, - googleMapInstance: null, - googleMapCenterMarker: null - }; -} - -export function createGoogleMapInstance(mapHolder, mapContainer, options) { - if (!googleMapHolder.googleMap) { - return null; - } - - const googleMap = googleMapHolder.googleMap; - - mapHolder.googleMapInstance = new googleMap.Map(mapContainer, { - zoom: options.zoomLevel, - center: { - lat: options.initCenter.latitude, - lng: options.initCenter.longitude - }, - maxZoom: 19, - zoomControl: true, - mapTypeControl: false, - scaleControl: false, - streetViewControl: false, - rotateControl: false, - fullscreenControl: false, - gestureHandling: 'greedy', - zoomControlOptions: { - position: googleMapHolder.ControlPosition.LEFT_TOP - } - }); - mapHolder.inited = true; -} - -export function setGoogleMapCenterTo(mapHolder, center, zoomLevel) { - if (!googleMapHolder.googleMap || !mapHolder.googleMapInstance) { - return; - } - - mapHolder.googleMapInstance.setCenter({ - lat: center.latitude, - lng: center.longitude - }); - mapHolder.googleMapInstance.setZoom(zoomLevel); -} - -export function setGoogleMapCenterMaker(mapHolder, position) { - if (!googleMapHolder.googleMap || !mapHolder.googleMapInstance) { - return; - } - - const googleMap = googleMapHolder.googleMap; - - if (!mapHolder.googleMapCenterMarker) { - mapHolder.googleMapCenterMarker = new googleMap.Marker({ - position: { - lat: position.latitude, - lng: position.longitude - }, - map: mapHolder.googleMapInstance - }); - } else { - mapHolder.googleMapCenterMarker.setPosition({ - lat: position.latitude, - lng: position.longitude - }); - } -} - -export function removeGoogleMapCenterMaker(mapHolder) { - if (!mapHolder.googleMapInstance || !mapHolder.googleMapCenterMarker) { - return; - } - - mapHolder.googleMapCenterMarker.setMap(null); - mapHolder.googleMapCenterMarker = null; -} diff --git a/src/lib/map/googlemap.ts b/src/lib/map/googlemap.ts new file mode 100644 index 00000000..a83549cf --- /dev/null +++ b/src/lib/map/googlemap.ts @@ -0,0 +1,125 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import type { MapProvider, MapInstance, MapInstanceInitOptions, MapPosition } from './base.ts'; + +import { asyncLoadAssets } from '@/lib/misc.ts'; +import services from '@/lib/services.js'; + +export class GoogleMapProvider implements MapProvider { + public static GoogleMap: unknown = null; + public static ControlPosition = { + LEFT_TOP: (window.google && window.google.maps && window.google.maps.ControlPosition) ? window.google.maps.ControlPosition.LEFT_TOP : 5 + }; + + public getWebsite(): string { + return 'https://maps.google.com'; + } + + public asyncLoadAssets(language: string): Promise { + if (GoogleMapProvider.GoogleMap) { + return Promise.resolve(); + } + + if (!window.onGoogleMapCallback) { + window.onGoogleMapCallback = () => { + if (window.google) { + GoogleMapProvider.GoogleMap = window.google.maps; + GoogleMapProvider.ControlPosition.LEFT_TOP = (window.google && window.google.maps && window.google.maps.ControlPosition) ? window.google.maps.ControlPosition.LEFT_TOP : 5; + } + }; + } + + return asyncLoadAssets('js', services.generateGoogleMapJavascriptUrl(language, 'onGoogleMapCallback')); + } + + public createMapInstance(): MapInstance | null { + return new GoogleMapInstance(); + } +} + +export class GoogleMapInstance implements MapInstance { + public dependencyLoaded: boolean = false; + public inited: boolean = false; + + public readonly defaultZoomLevel: number = 14; + public readonly minZoomLevel: number = 1; + + private googleMapInstance: unknown = null; + private googleMapCenterMarker: unknown | null; + + constructor() { + this.dependencyLoaded = !!GoogleMapProvider.GoogleMap; + } + + public initMapInstance(mapContainer: HTMLElement, options: MapInstanceInitOptions): void { + if (!GoogleMapProvider.GoogleMap) { + return; + } + + const googleMap = GoogleMapProvider.GoogleMap; + + this.googleMapInstance = new googleMap.Map(mapContainer, { + zoom: options.zoomLevel, + center: { + lat: options.initCenter.latitude, + lng: options.initCenter.longitude + }, + maxZoom: 19, + zoomControl: true, + mapTypeControl: false, + scaleControl: false, + streetViewControl: false, + rotateControl: false, + fullscreenControl: false, + gestureHandling: 'greedy', + zoomControlOptions: { + position: GoogleMapProvider.ControlPosition.LEFT_TOP + } + }); + this.inited = true; + } + + public setMapCenterTo(center: MapPosition, zoomLevel: number): void { + if (!GoogleMapProvider.GoogleMap || !this.googleMapInstance) { + return; + } + + this.googleMapInstance.setCenter({ + lat: center.latitude, + lng: center.longitude + }); + this.googleMapInstance.setZoom(zoomLevel); + } + + public setMapCenterMarker(position: MapPosition): void { + if (!GoogleMapProvider.GoogleMap || !this.googleMapInstance) { + return; + } + + const googleMap = GoogleMapProvider.GoogleMap; + + if (!this.googleMapCenterMarker) { + this.googleMapCenterMarker = new googleMap.Marker({ + position: { + lat: position.latitude, + lng: position.longitude + }, + map: this.googleMapInstance + }); + } else { + this.googleMapCenterMarker.setPosition({ + lat: position.latitude, + lng: position.longitude + }); + } + } + + public removeMapCenterMarker(): void { + if (!this.googleMapInstance || !this.googleMapCenterMarker) { + return; + } + + this.googleMapCenterMarker.setMap(null); + this.googleMapCenterMarker = null; + } +} diff --git a/src/lib/map/index.js b/src/lib/map/index.js deleted file mode 100644 index 3a3364d3..00000000 --- a/src/lib/map/index.js +++ /dev/null @@ -1,147 +0,0 @@ -import { LEAFLET_TILE_SOURCES } from '@/consts/map.ts'; -import { - getMapProvider -} from '@/lib/server_settings.ts'; - -import { - loadLeafletMapAssets, - createLeafletMapHolder, - createLeafletMapInstance, - setLeafletMapCenterTo, - setLeafletMapCenterMaker, - removeLeafletMapCenterMaker -} from './leaflet.js'; - -import { - getGoogleMapWebsite, - loadGoogleMapAssets, - createGoogleMapHolder, - createGoogleMapInstance, - setGoogleMapCenterTo, - setGoogleMapCenterMaker, - removeGoogleMapCenterMaker -} from './googlemap.js'; - -import { - getBaiduMapWebsite, - loadBaiduMapAssets, - createBaiduMapHolder, - createBaiduMapInstance, - setBaiduMapCenterTo, - setBaiduMapCenterMaker, - removeBaiduMapCenterMaker -} from './baidumap.js'; - -import { - getAmapWebsite, - loadAmapAssets, - createAmapHolder, - createAmapInstance, - setAmapCenterTo, - setAmapCenterMaker, - removeAmapCenterMaker -} from './amap.js'; - -export function getMapWebsite() { - if (getMapProvider() === 'custom') { - return ''; - } else if (LEAFLET_TILE_SOURCES[getMapProvider()]) { - return LEAFLET_TILE_SOURCES[getMapProvider()].website; - } else if (getMapProvider() === 'googlemap') { - return getGoogleMapWebsite(); - } else if (getMapProvider() === 'baidumap') { - return getBaiduMapWebsite(); - } else if (getMapProvider() === 'amap') { - return getAmapWebsite(); - } -} - -export function loadMapAssets(language) { - if (LEAFLET_TILE_SOURCES[getMapProvider()] || getMapProvider() === 'custom') { - return loadLeafletMapAssets(language); - } else if (getMapProvider() === 'googlemap') { - return loadGoogleMapAssets(language); - } else if (getMapProvider() === 'baidumap') { - return loadBaiduMapAssets(language); - } else if (getMapProvider() === 'amap') { - return loadAmapAssets(language); - } -} - -export function createMapHolder() { - if (LEAFLET_TILE_SOURCES[getMapProvider()] || getMapProvider() === 'custom') { - return createLeafletMapHolder(getMapProvider()); - } else if (getMapProvider() === 'googlemap') { - return createGoogleMapHolder(getMapProvider()); - } else if (getMapProvider() === 'baidumap') { - return createBaiduMapHolder(getMapProvider()); - } else if (getMapProvider() === 'amap') { - return createAmapHolder(getMapProvider()); - } else { - return null; - } -} - -export function initMapInstance(mapHolder, mapContainer, options) { - if (!mapHolder) { - return; - } - - if (LEAFLET_TILE_SOURCES[getMapProvider()] || getMapProvider() === 'custom') { - createLeafletMapInstance(mapHolder, mapContainer, options); - } else if (mapHolder.mapProvider === 'googlemap') { - createGoogleMapInstance(mapHolder, mapContainer, options); - } else if (mapHolder.mapProvider === 'baidumap') { - createBaiduMapInstance(mapHolder, mapContainer, options); - } else if (mapHolder.mapProvider === 'amap') { - createAmapInstance(mapHolder, mapContainer, options); - } -} - -export function setMapCenterTo(mapHolder, center, zoomLevel) { - if (!mapHolder) { - return; - } - - if (LEAFLET_TILE_SOURCES[getMapProvider()] || getMapProvider() === 'custom') { - setLeafletMapCenterTo(mapHolder, center, zoomLevel); - } else if (mapHolder.mapProvider === 'googlemap') { - setGoogleMapCenterTo(mapHolder, center, zoomLevel); - } else if (mapHolder.mapProvider === 'baidumap') { - setBaiduMapCenterTo(mapHolder, center, zoomLevel); - } else if (mapHolder.mapProvider === 'amap') { - setAmapCenterTo(mapHolder, center, zoomLevel); - } -} - -export function setMapCenterMarker(mapHolder, position) { - if (!mapHolder) { - return; - } - - if (LEAFLET_TILE_SOURCES[getMapProvider()] || getMapProvider() === 'custom') { - setLeafletMapCenterMaker(mapHolder, position); - } else if (mapHolder.mapProvider === 'googlemap') { - setGoogleMapCenterMaker(mapHolder, position); - } else if (mapHolder.mapProvider === 'baidumap') { - setBaiduMapCenterMaker(mapHolder, position); - } else if (mapHolder.mapProvider === 'amap') { - setAmapCenterMaker(mapHolder, position); - } -} - -export function removeMapCenterMarker(mapHolder) { - if (!mapHolder) { - return; - } - - if (LEAFLET_TILE_SOURCES[getMapProvider()] || getMapProvider() === 'custom') { - removeLeafletMapCenterMaker(mapHolder); - } else if (mapHolder.mapProvider === 'googlemap') { - removeGoogleMapCenterMaker(mapHolder); - } else if (mapHolder.mapProvider === 'baidumap') { - removeBaiduMapCenterMaker(mapHolder); - } else if (mapHolder.mapProvider === 'amap') { - removeAmapCenterMaker(mapHolder); - } -} diff --git a/src/lib/map/index.ts b/src/lib/map/index.ts new file mode 100644 index 00000000..62d1fa00 --- /dev/null +++ b/src/lib/map/index.ts @@ -0,0 +1,36 @@ +import { LEAFLET_TILE_SOURCES } from '@/consts/map.ts'; +import { getMapProvider } from '@/lib/server_settings.ts'; + +import type { MapProvider, MapInstance } from './base.ts'; +import { LeafletMapProvider } from './leaflet.ts'; +import { GoogleMapProvider } from './googlemap.ts'; +import { BaiduMapProvider } from './baidumap.ts'; +import { AmapMapProvider } from './amap.ts'; + +let mapProvider: MapProvider | null = null; + +export function initMapProvider(language: string): void { + const mapProviderType = getMapProvider(); + + if (LEAFLET_TILE_SOURCES[mapProviderType] || mapProviderType === 'custom') { + mapProvider = new LeafletMapProvider(mapProviderType); + } else if (mapProviderType === 'googlemap') { + mapProvider = new GoogleMapProvider(); + } else if (mapProviderType === 'baidumap') { + mapProvider = new BaiduMapProvider(); + } else if (mapProviderType === 'amap') { + mapProvider = new AmapMapProvider(); + } + + if (mapProvider) { + mapProvider.asyncLoadAssets(language); + } +} + +export function getMapWebsite(): string { + return mapProvider?.getWebsite() || ''; +} + +export function createMapInstance(): MapInstance | null { + return mapProvider?.createMapInstance() || null; +} diff --git a/src/lib/map/leaflet.js b/src/lib/map/leaflet.js deleted file mode 100644 index fc693b27..00000000 --- a/src/lib/map/leaflet.js +++ /dev/null @@ -1,199 +0,0 @@ -import { LEAFLET_TILE_SOURCES } from '@/consts/map.ts'; -import { - isMapDataFetchProxyEnabled, - getCustomMapTileLayerUrl, - getCustomMapAnnotationLayerUrl, - isCustomMapAnnotationLayerDataFetchProxyEnabled, - getCustomMapMinZoomLevel, - getCustomMapMaxZoomLevel, - getCustomMapDefaultZoomLevel, - getTomTomMapAPIKey, - getTianDiTuMapAPIKey -} from '@/lib/server_settings.ts'; -import services from '@/lib/services.js'; - -const leafletHolder = { - leaflet: null -}; - -export function loadLeafletMapAssets() { - return Promise.all([ - import('leaflet/dist/leaflet.css'), - import('leaflet/dist/leaflet-src.esm.js').then(leaflet => leafletHolder.leaflet = leaflet) - ]); -} - -export function createLeafletMapHolder(mapProvider) { - const mapTileSource = LEAFLET_TILE_SOURCES[mapProvider]; - - if (mapProvider !== 'custom' && !mapTileSource) { - return null; - } - - return { - mapProvider: mapProvider, - dependencyLoaded: !!leafletHolder.leaflet, - inited: false, - defaultZoomLevel: mapProvider !== 'custom' ? mapTileSource.defaultZoomLevel : getCustomMapDefaultZoomLevel(), - minZoomLevel: mapProvider !== 'custom' ? mapTileSource.minZoom : getCustomMapMinZoomLevel(), - leafletInstance: null, - leafletTileLayer: null, - leafletAnnotationLayer: null, - leafletZoomControl: null, - leafletAttribution: null, - leafletCenterMarker: null - }; -} - -export function createLeafletMapInstance(mapHolder, mapContainer, options) { - if (!leafletHolder.leaflet) { - return null; - } - - const leaflet = leafletHolder.leaflet; - const leafletInstance = leaflet.map(mapContainer, { - center: [ options.initCenter.latitude, options.initCenter.longitude ], - zoom: options.zoomLevel, - attributionControl: false, - zoomControl: false - }); - let mapTileSource = null; - - if (mapHolder.mapProvider !== 'custom') { - mapTileSource = Object.assign({}, LEAFLET_TILE_SOURCES[mapHolder.mapProvider]); - } else { - mapTileSource = createCustomMapSource(); - } - - if (isMapDataFetchProxyEnabled()) { - mapTileSource.tileUrlFormat = services.generateMapProxyTileImageUrl(mapHolder.mapProvider, options.language); - mapTileSource.tileUrlSubDomains = ''; - } else if (mapTileSource.tileUrlExtraParams) { - mapTileSource.tileUrlFormat = getFinalUrlFormat(mapTileSource.tileUrlFormat, mapTileSource.tileUrlExtraParams, options); - } - - const tileLayer = leaflet.tileLayer(mapTileSource.tileUrlFormat, { - subdomains: mapTileSource.tileUrlSubDomains, - maxZoom: mapTileSource.maxZoom, - minZoom: mapTileSource.minZoom - }); - tileLayer.addTo(leafletInstance); - - if (mapTileSource.annotationUrlFormat || (mapHolder.mapProvider === 'custom' && isCustomMapAnnotationLayerDataFetchProxyEnabled())) { - if (isMapDataFetchProxyEnabled()) { - mapTileSource.annotationUrlFormat = services.generateMapProxyAnnotationImageUrl(mapHolder.mapProvider, options.language); - mapTileSource.annotationUrlSubDomains = ''; - } else if (mapTileSource.annotationUrlExtraParams) { - mapTileSource.annotationUrlFormat = getFinalUrlFormat(mapTileSource.annotationUrlFormat, mapTileSource.annotationUrlExtraParams, options); - } - - const annotationLayer = leaflet.tileLayer(mapTileSource.annotationUrlFormat, { - subdomains: mapTileSource.annotationUrlSubDomains, - maxZoom: mapTileSource.maxZoom, - minZoom: mapTileSource.minZoom - }); - annotationLayer.addTo(leafletInstance); - - mapHolder.leafletAnnotationLayer = annotationLayer; - } - - const zoomControl = leaflet.control.zoom({ - zoomInTitle: options.text.zoomIn, - zoomOutTitle: options.text.zoomOut - }); - zoomControl.addTo(leafletInstance); - - if (mapTileSource.attribution) { - const attribution = leaflet.control.attribution({ - prefix: false - }); - attribution.addAttribution(mapTileSource.attribution); - attribution.addTo(leafletInstance); - mapHolder.leafletAttribution = attribution; - } - - mapHolder.leafletInstance = leafletInstance; - mapHolder.leafletTileLayer = tileLayer; - mapHolder.leafletZoomControl = zoomControl; - mapHolder.inited = true; -} - -export function setLeafletMapCenterTo(mapHolder, center, zoomLevel) { - if (!mapHolder.leafletInstance) { - return; - } - - mapHolder.leafletInstance.setView([ center.latitude, center.longitude ], zoomLevel); -} - -export function setLeafletMapCenterMaker(mapHolder, position) { - if (!leafletHolder.leaflet || !mapHolder.leafletInstance) { - return; - } - - const leaflet = leafletHolder.leaflet; - - if (!mapHolder.leafletCenterMarker) { - const markerIcon = leaflet.icon({ - iconUrl: 'img/map-marker-icon.png', - iconRetinaUrl: 'img/map-marker-icon-2x.png', - iconSize: [25, 32], - iconAnchor: [12, 32], - shadowUrl: 'img/map-marker-shadow.png', - shadowSize: [41, 32] - }); - mapHolder.leafletCenterMarker = leaflet.marker([ position.latitude, position.longitude ], { - icon: markerIcon - }); - mapHolder.leafletCenterMarker.addTo(mapHolder.leafletInstance); - } else { - mapHolder.leafletCenterMarker.setLatLng([ position.latitude, position.longitude ]); - } -} - -export function removeLeafletMapCenterMaker(mapHolder) { - if (!mapHolder.leafletInstance || !mapHolder.leafletCenterMarker) { - return; - } - - mapHolder.leafletCenterMarker.remove(); - mapHolder.leafletCenterMarker = null; -} - -function createCustomMapSource() { - return { - tileUrlFormat: getCustomMapTileLayerUrl(), - tileUrlSubDomains: '', - annotationUrlFormat: getCustomMapAnnotationLayerUrl(), - annotationUrlSubDomains: '', - minZoom: getCustomMapMinZoomLevel(), - maxZoom: getCustomMapMaxZoomLevel(), - defaultZoomLevel: getCustomMapDefaultZoomLevel() - }; -} - -function getFinalUrlFormat(urlFormat, urlExtraParams, options) { - const params = []; - - for (let i = 0; i < urlExtraParams.length; i++) { - const param = urlExtraParams[i]; - - if (param.paramValueType === 'tomtom_key') { - params.push(param.paramName + '=' + getTomTomMapAPIKey()); - } else if (param.paramValueType === 'tianditu_key') { - params.push(param.paramName + '=' + getTianDiTuMapAPIKey()); - } else if (param.paramValueType === 'language' && options.language) { - params.push(param.paramName + '=' + options.language); - } - } - - if (params.length) { - if (urlFormat.indexOf('?') >= 0) { - urlFormat = urlFormat + '&' + params.join('&'); - } else { - urlFormat = urlFormat + '?' + params.join('&'); - } - } - - return urlFormat; -} diff --git a/src/lib/map/leaflet.ts b/src/lib/map/leaflet.ts new file mode 100644 index 00000000..e41fdb07 --- /dev/null +++ b/src/lib/map/leaflet.ts @@ -0,0 +1,234 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { type LeafletTileSource, type LeafletTileSourceExtraParam, LEAFLET_TILE_SOURCES } from '@/consts/map.ts'; + +import type { MapProvider, MapInstance, MapInstanceInitOptions, MapPosition } from './base.ts'; + +import { + isMapDataFetchProxyEnabled, + getCustomMapTileLayerUrl, + getCustomMapAnnotationLayerUrl, + isCustomMapAnnotationLayerDataFetchProxyEnabled, + getCustomMapMinZoomLevel, + getCustomMapMaxZoomLevel, + getCustomMapDefaultZoomLevel, + getTomTomMapAPIKey, + getTianDiTuMapAPIKey +} from '@/lib/server_settings.ts'; +import services from '@/lib/services.js'; + +export class LeafletMapProvider implements MapProvider { + public static Leaflet: unknown = null; + private readonly mapProvider: string; + + public constructor(mapProvider: string) { + this.mapProvider = mapProvider; + } + + public getWebsite(): string { + if (this.mapProvider === 'custom') { + return ''; + } else if (LEAFLET_TILE_SOURCES[this.mapProvider]) { + return LEAFLET_TILE_SOURCES[this.mapProvider].website || ''; + } else { + return ''; + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public asyncLoadAssets(language: string): Promise { + return Promise.all([ + import('leaflet/dist/leaflet.css'), + import('leaflet/dist/leaflet-src.esm.js').then(leaflet => LeafletMapProvider.Leaflet = leaflet) + ]); + } + + public createMapInstance(): MapInstance | null { + const mapTileSource = LEAFLET_TILE_SOURCES[this.mapProvider]; + + if (this.mapProvider !== 'custom' && !mapTileSource) { + return null; + } + + return new LeafletMapInstance(this.mapProvider, mapTileSource); + } +} + +export class LeafletMapInstance implements MapInstance { + public dependencyLoaded: boolean = false; + public inited: boolean = false; + + public readonly defaultZoomLevel: number; + public readonly minZoomLevel: number; + + private readonly mapProvider: string; + private readonly presetMapTileSource: LeafletTileSource; + + private leafletInstance: unknown | null; + private leafletTileLayer: unknown | null; + private leafletAnnotationLayer: unknown | null; + private leafletZoomControl: unknown | null; + private leafletAttribution: unknown | null; + private leafletCenterMarker: unknown | null; + + constructor(mapProvider: string, mapTileSource: LeafletTileSource) { + this.dependencyLoaded = !!LeafletMapProvider.Leaflet; + + this.mapProvider = mapProvider; + this.presetMapTileSource = mapTileSource; + + this.defaultZoomLevel = this.presetMapTileSource?.defaultZoomLevel || getCustomMapDefaultZoomLevel(); + this.minZoomLevel = this.presetMapTileSource?.minZoom || getCustomMapMinZoomLevel(); + } + + public initMapInstance(mapContainer: HTMLElement, options: MapInstanceInitOptions): void { + if (!LeafletMapProvider.Leaflet) { + return; + } + + const leaflet = LeafletMapProvider.Leaflet; + const leafletInstance = leaflet.map(mapContainer, { + center: [ options.initCenter.latitude, options.initCenter.longitude ], + zoom: options.zoomLevel, + attributionControl: false, + zoomControl: false + }); + + let tileUrlFormat, tileUrlSubDomains, annotationUrlFormat, annotationUrlSubDomains: string | undefined; + let minZoom, maxZoom: number; + + if (this.mapProvider !== 'custom') { + tileUrlFormat = this.presetMapTileSource?.tileUrlFormat; + tileUrlSubDomains = this.presetMapTileSource?.tileUrlSubDomains; + annotationUrlFormat = this.presetMapTileSource?.annotationUrlFormat; + annotationUrlSubDomains = this.presetMapTileSource?.annotationUrlSubDomains; + minZoom = this.presetMapTileSource?.minZoom; + maxZoom = this.presetMapTileSource?.maxZoom; + } else { + tileUrlFormat = getCustomMapTileLayerUrl(); + annotationUrlFormat = getCustomMapAnnotationLayerUrl(); + minZoom = getCustomMapMinZoomLevel(); + maxZoom = getCustomMapMaxZoomLevel(); + } + + if (isMapDataFetchProxyEnabled()) { + tileUrlFormat = services.generateMapProxyTileImageUrl(this.mapProvider, options.language); + tileUrlSubDomains = ''; + } else if (this.presetMapTileSource && this.presetMapTileSource.tileUrlExtraParams) { + tileUrlFormat = this.getFinalUrlFormat(this.presetMapTileSource.tileUrlFormat as string, this.presetMapTileSource.tileUrlExtraParams, options); + } + + const tileLayer = leaflet.tileLayer(tileUrlFormat, { + subdomains: tileUrlSubDomains, + maxZoom: maxZoom, + minZoom: minZoom + }); + tileLayer.addTo(leafletInstance); + + if (annotationUrlFormat || (this.mapProvider === 'custom' && isCustomMapAnnotationLayerDataFetchProxyEnabled())) { + if (isMapDataFetchProxyEnabled()) { + annotationUrlFormat = services.generateMapProxyAnnotationImageUrl(this.mapProvider, options.language); + annotationUrlSubDomains = ''; + } else if (this.presetMapTileSource && this.presetMapTileSource.annotationUrlExtraParams) { + annotationUrlFormat = this.getFinalUrlFormat(this.presetMapTileSource.annotationUrlFormat as string, this.presetMapTileSource.annotationUrlExtraParams, options); + } + + const annotationLayer = leaflet.tileLayer(annotationUrlFormat, { + subdomains: annotationUrlSubDomains, + maxZoom: maxZoom, + minZoom: minZoom + }); + annotationLayer.addTo(leafletInstance); + + this.leafletAnnotationLayer = annotationLayer; + } + + const zoomControl = leaflet.control.zoom({ + zoomInTitle: options.text.zoomIn, + zoomOutTitle: options.text.zoomOut + }); + zoomControl.addTo(leafletInstance); + + if (this.presetMapTileSource && this.presetMapTileSource.attribution) { + const attribution = leaflet.control.attribution({ + prefix: false + }); + attribution.addAttribution(this.presetMapTileSource.attribution); + attribution.addTo(leafletInstance); + this.leafletAttribution = attribution; + } + + this.leafletInstance = leafletInstance; + this.leafletTileLayer = tileLayer; + this.leafletZoomControl = zoomControl; + this.inited = true; + } + + public setMapCenterTo(center: MapPosition, zoomLevel: number): void { + if (!this.leafletInstance) { + return; + } + + this.leafletInstance.setView([ center.latitude, center.longitude ], zoomLevel); + } + + public setMapCenterMarker(position: MapPosition): void { + if (!LeafletMapProvider.Leaflet || !this.leafletInstance) { + return; + } + + const leaflet = LeafletMapProvider.Leaflet; + + if (!this.leafletCenterMarker) { + const markerIcon = leaflet.icon({ + iconUrl: 'img/map-marker-icon.png', + iconRetinaUrl: 'img/map-marker-icon-2x.png', + iconSize: [25, 32], + iconAnchor: [12, 32], + shadowUrl: 'img/map-marker-shadow.png', + shadowSize: [41, 32] + }); + this.leafletCenterMarker = leaflet.marker([ position.latitude, position.longitude ], { + icon: markerIcon + }); + this.leafletCenterMarker.addTo(this.leafletInstance); + } else { + this.leafletCenterMarker.setLatLng([ position.latitude, position.longitude ]); + } + } + + public removeMapCenterMarker(): void { + if (!this.leafletInstance || !this.leafletCenterMarker) { + return; + } + + this.leafletCenterMarker.remove(); + this.leafletCenterMarker = null; + } + + private getFinalUrlFormat(urlFormat: string, urlExtraParams: LeafletTileSourceExtraParam[], options: MapInstanceInitOptions) { + const params: string[] = []; + + for (let i = 0; i < urlExtraParams.length; i++) { + const param = urlExtraParams[i]; + + if (param.paramValueType === 'tomtom_key') { + params.push(param.paramName + '=' + getTomTomMapAPIKey()); + } else if (param.paramValueType === 'tianditu_key') { + params.push(param.paramName + '=' + getTianDiTuMapAPIKey()); + } else if (param.paramValueType === 'language' && options.language) { + params.push(param.paramName + '=' + options.language); + } + } + + if (params.length) { + if (urlFormat.indexOf('?') >= 0) { + urlFormat = urlFormat + '&' + params.join('&'); + } else { + urlFormat = urlFormat + '?' + params.join('&'); + } + } + + return urlFormat; + } +} diff --git a/src/views/desktop/AboutPage.vue b/src/views/desktop/AboutPage.vue index 08cd7269..568a2e71 100644 --- a/src/views/desktop/AboutPage.vue +++ b/src/views/desktop/AboutPage.vue @@ -125,7 +125,7 @@ import { useUserStore } from '@/stores/user.js'; import { useExchangeRatesStore } from '@/stores/exchangeRates.js'; import { getMapProvider } from '@/lib/server_settings.ts'; -import { getMapWebsite } from '@/lib/map/index.js'; +import { getMapWebsite } from '@/lib/map/index.ts'; import { getLicense, getThirdPartyLicenses } from '@/lib/licenses.ts'; export default { diff --git a/src/views/mobile/AboutPage.vue b/src/views/mobile/AboutPage.vue index 38d32ff2..c718acd3 100644 --- a/src/views/mobile/AboutPage.vue +++ b/src/views/mobile/AboutPage.vue @@ -63,7 +63,7 @@ import { useUserStore } from '@/stores/user.js'; import { useExchangeRatesStore } from '@/stores/exchangeRates.js'; import { getMapProvider } from '@/lib/server_settings.ts'; -import { getMapWebsite } from '@/lib/map/index.js'; +import { getMapWebsite } from '@/lib/map/index.ts'; import { getLicense, getThirdPartyLicenses } from '@/lib/licenses.ts'; export default {