From aae23c285e0db3b224624571d22dfb90a6feb641 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sun, 21 Jul 2024 19:10:58 +0800 Subject: [PATCH] map provider supports TianDiTu --- cmd/webserver.go | 5 ++ conf/ezbookkeeping.ini | 6 +- pkg/api/map_image_proxies.go | 86 +++++++++++++++-------- pkg/middlewares/server_settings_cookie.go | 5 ++ pkg/settings/setting.go | 5 ++ src/consts/map.js | 23 ++++++ src/lib/map/leaflet.js | 64 ++++++++++++----- src/lib/server_settings.js | 4 ++ src/lib/services.js | 10 +++ src/locales/en.js | 1 + src/locales/zh_Hans.js | 1 + 11 files changed, 164 insertions(+), 46 deletions(-) diff --git a/cmd/webserver.go b/cmd/webserver.go index be078704..e7ad1433 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -163,9 +163,14 @@ func startWebServer(c *cli.Context) error { config.MapProvider == settings.CyclOSMMapProvider || config.MapProvider == settings.CartoDBMapProvider || config.MapProvider == settings.TomTomMapProvider || + config.MapProvider == settings.TianDiTuProvider || config.MapProvider == settings.CustomProvider { proxyRoute.GET("/map/tile/:zoomLevel/:coordinateX/:fileName", bindProxy(api.MapImages.MapTileImageProxyHandler)) } + + if config.MapProvider == settings.TianDiTuProvider { + proxyRoute.GET("/map/annotation/:zoomLevel/:coordinateX/:fileName", bindProxy(api.MapImages.MapAnnotationImageProxyHandler)) + } } } diff --git a/conf/ezbookkeeping.ini b/conf/ezbookkeeping.ini index 7f761210..c62743db 100644 --- a/conf/ezbookkeeping.ini +++ b/conf/ezbookkeeping.ini @@ -170,6 +170,7 @@ enable_export = true # "cyclosm": https://www.cyclosm.org # "cartodb": https://carto.com/basemaps # "tomtom": https://www.tomtom.com +# "tianditu": https://www.tianditu.gov.cn # "googlemap": https://map.google.com # "baidumap": https://map.baidu.com # "amap": https://amap.com @@ -177,7 +178,7 @@ enable_export = true # Leave blank if you want to disable map map_provider = openstreetmap -# Set to true to use the ezbookkeeping server to proxy map data requests, for "openstreetmap", "openstreetmap_humanitarian", "opentopomap", "opnvkarte", "cyclosm", "cartodb", "tomtom" or "custom" +# Set to true to use the ezbookkeeping server to proxy map data requests, for "openstreetmap", "openstreetmap_humanitarian", "opentopomap", "opnvkarte", "cyclosm", "cartodb", "tomtom", "tianditu" or "custom" map_data_fetch_proxy = false # Proxy to request original map data when map_data_fetch_proxy is set to true, supports "system" (use system proxy), "none" (do not use proxy), or proxy URL which starts with "http://", "https://" or "socks5://", default is "system" @@ -186,6 +187,9 @@ proxy = system # For "tomtom" only, TomTom map API key, please visit https://developer.tomtom.com/how-to-get-tomtom-api-key for more information tomtom_map_api_key = +# For "tianditu" only, TianDiTu map application key, please visit https://console.tianditu.gov.cn/api/register for more information +tianditu_map_app_key = + # For "googlemap" only, Google map JavaScript API key, please visit https://developers.google.com/maps/get-started for more information google_map_api_key = diff --git a/pkg/api/map_image_proxies.go b/pkg/api/map_image_proxies.go index cacad318..ffcc4cf3 100644 --- a/pkg/api/map_image_proxies.go +++ b/pkg/api/map_image_proxies.go @@ -12,13 +12,15 @@ import ( "github.com/mayswind/ezbookkeeping/pkg/utils" ) -const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/{z}/{x}/{y}.png" // https://tile.openstreetmap.org/{z}/{x}/{y}.png -const openStreetMapHumanitarianStyleTileImageUrlFormat = "https://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png" // https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png -const openTopoMapTileImageUrlFormat = "https://tile.opentopomap.org/{z}/{x}/{y}.png" // https://tile.opentopomap.org/{z}/{x}/{y}.png -const opnvKarteMapTileImageUrlFormat = "https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png" // https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png -const cyclOSMMapTileImageUrlFormat = "https://a.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png" // https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png -const cartoDBMapTileImageUrlFormat = "https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{scale}.png" // https://{s}.basemaps.cartocdn.com/{style}/{z}/{x}/{y}{scale}.png -const tomtomMapTileImageUrlFormat = "https://api.tomtom.com/map/1/tile/basic/main/{z}/{x}/{y}.png" // https://api.tomtom.com/map/{versionNumber}/tile/{layer}/{style}/{z}/{x}/{y}.png?key={key}&language={language} +const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/{z}/{x}/{y}.png" // https://tile.openstreetmap.org/{z}/{x}/{y}.png +const openStreetMapHumanitarianStyleTileImageUrlFormat = "https://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png" // https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png +const openTopoMapTileImageUrlFormat = "https://tile.opentopomap.org/{z}/{x}/{y}.png" // https://tile.opentopomap.org/{z}/{x}/{y}.png +const opnvKarteMapTileImageUrlFormat = "https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png" // https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png +const cyclOSMMapTileImageUrlFormat = "https://a.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png" // https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png +const cartoDBMapTileImageUrlFormat = "https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{scale}.png" // https://{s}.basemaps.cartocdn.com/{style}/{z}/{x}/{y}{scale}.png +const tomtomMapTileImageUrlFormat = "https://api.tomtom.com/map/1/tile/basic/main/{z}/{x}/{y}.png" // https://api.tomtom.com/map/{versionNumber}/tile/{layer}/{style}/{z}/{x}/{y}.png?key={key}&language={language} +const tianDiTuMapTileImageUrlFormat = "https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" // https://t{s}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={key} +const tianDiTuMapAnnotationUrlFormat = "https://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" // https://t{s}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={key} // MapImageProxy represents map image proxy type MapImageProxy struct { @@ -31,6 +33,50 @@ var ( // MapTileImageProxyHandler returns map tile image func (p *MapImageProxy) MapTileImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) { + return p.mapImageProxyHandler(c, func(c *core.Context, mapProvider string) (string, *errs.Error) { + if mapProvider == settings.OpenStreetMapProvider { + return openStreetMapTileImageUrlFormat, nil + } else if mapProvider == settings.OpenStreetMapHumanitarianStyleProvider { + return openStreetMapHumanitarianStyleTileImageUrlFormat, nil + } else if mapProvider == settings.OpenTopoMapProvider { + return openTopoMapTileImageUrlFormat, nil + } else if mapProvider == settings.OPNVKarteMapProvider { + return opnvKarteMapTileImageUrlFormat, nil + } else if mapProvider == settings.CyclOSMMapProvider { + return cyclOSMMapTileImageUrlFormat, nil + } else if mapProvider == settings.CartoDBMapProvider { + return cartoDBMapTileImageUrlFormat, nil + } else if mapProvider == settings.TomTomMapProvider { + targetUrl := tomtomMapTileImageUrlFormat + "?key=" + settings.Container.Current.TomTomMapAPIKey + language := c.Query("language") + + if language != "" { + targetUrl = targetUrl + "&language=" + language + } + + return targetUrl, nil + } else if mapProvider == settings.TianDiTuProvider { + return tianDiTuMapTileImageUrlFormat + "&tk=" + settings.Container.Current.TianDiTuAPIKey, nil + } else if mapProvider == settings.CustomProvider { + return settings.Container.Current.CustomMapTileServerUrl, nil + } + + return "", errs.ErrParameterInvalid + }) +} + +// MapAnnotationImageProxyHandler returns map annotation image +func (p *MapImageProxy) MapAnnotationImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) { + return p.mapImageProxyHandler(c, func(c *core.Context, mapProvider string) (string, *errs.Error) { + if mapProvider == settings.TianDiTuProvider { + return tianDiTuMapAnnotationUrlFormat + "&tk=" + settings.Container.Current.TianDiTuAPIKey, nil + } + + return "", errs.ErrParameterInvalid + }) +} + +func (p *MapImageProxy) mapImageProxyHandler(c *core.Context, fn func(c *core.Context, mapProvider string) (string, *errs.Error)) (*httputil.ReverseProxy, *errs.Error) { mapProvider := strings.Replace(c.Query("provider"), "-", "_", -1) targetUrl := "" @@ -49,29 +95,11 @@ func (p *MapImageProxy) MapTileImageProxyHandler(c *core.Context) (*httputil.Rev return nil, errs.ErrImageExtensionNotSupported } - if mapProvider == settings.OpenStreetMapProvider { - targetUrl = openStreetMapTileImageUrlFormat - } else if mapProvider == settings.OpenStreetMapHumanitarianStyleProvider { - targetUrl = openStreetMapHumanitarianStyleTileImageUrlFormat - } else if mapProvider == settings.OpenTopoMapProvider { - targetUrl = openTopoMapTileImageUrlFormat - } else if mapProvider == settings.OPNVKarteMapProvider { - targetUrl = opnvKarteMapTileImageUrlFormat - } else if mapProvider == settings.CyclOSMMapProvider { - targetUrl = cyclOSMMapTileImageUrlFormat - } else if mapProvider == settings.CartoDBMapProvider { - targetUrl = cartoDBMapTileImageUrlFormat - } else if mapProvider == settings.TomTomMapProvider { - targetUrl = tomtomMapTileImageUrlFormat + "?key=" + settings.Container.Current.TomTomMapAPIKey - language := c.Query("language") + var err *errs.Error + targetUrl, err = fn(c, mapProvider) - if language != "" { - targetUrl = targetUrl + "&language=" + language - } - } else if mapProvider == settings.CustomProvider { - targetUrl = settings.Container.Current.CustomMapTileServerUrl - } else { - return nil, errs.ErrParameterInvalid + if err != nil { + return nil, err } transport := http.DefaultTransport.(*http.Transport).Clone() diff --git a/pkg/middlewares/server_settings_cookie.go b/pkg/middlewares/server_settings_cookie.go index 5290964b..695ccd89 100644 --- a/pkg/middlewares/server_settings_cookie.go +++ b/pkg/middlewares/server_settings_cookie.go @@ -31,6 +31,7 @@ func ServerSettingsCookie(config *settings.Config) core.MiddlewareHandlerFunc { config.MapProvider == settings.CyclOSMMapProvider || config.MapProvider == settings.CartoDBMapProvider || config.MapProvider == settings.TomTomMapProvider || + config.MapProvider == settings.TianDiTuProvider || config.MapProvider == settings.CustomProvider) { settingsArr = append(settingsArr, buildBooleanSetting("mp", config.EnableMapDataFetchProxy)) } @@ -47,6 +48,10 @@ func ServerSettingsCookie(config *settings.Config) core.MiddlewareHandlerFunc { settingsArr = append(settingsArr, buildEncodedStringSetting("tmak", config.TomTomMapAPIKey)) } + if config.MapProvider == settings.TianDiTuProvider && config.TianDiTuAPIKey != "" && !config.EnableMapDataFetchProxy { + settingsArr = append(settingsArr, buildEncodedStringSetting("tdak", config.TianDiTuAPIKey)) + } + if config.MapProvider == settings.GoogleMapProvider && config.GoogleMapAPIKey != "" { settingsArr = append(settingsArr, buildEncodedStringSetting("gmak", config.GoogleMapAPIKey)) } diff --git a/pkg/settings/setting.go b/pkg/settings/setting.go index 8ca4f981..49637956 100644 --- a/pkg/settings/setting.go +++ b/pkg/settings/setting.go @@ -81,6 +81,7 @@ const ( CyclOSMMapProvider string = "cyclosm" CartoDBMapProvider string = "cartodb" TomTomMapProvider string = "tomtom" + TianDiTuProvider string = "tianditu" GoogleMapProvider string = "googlemap" BaiduMapProvider string = "baidumap" AmapProvider string = "amap" @@ -243,6 +244,7 @@ type Config struct { EnableMapDataFetchProxy bool MapProxy string TomTomMapAPIKey string + TianDiTuAPIKey string GoogleMapAPIKey string BaiduMapAK string AmapApplicationKey string @@ -652,6 +654,8 @@ func loadMapConfiguration(config *Config, configFile *ini.File, sectionName stri config.MapProvider = CartoDBMapProvider } else if mapProvider == TomTomMapProvider { config.MapProvider = TomTomMapProvider + } else if mapProvider == TianDiTuProvider { + config.MapProvider = TianDiTuProvider } else if mapProvider == GoogleMapProvider { config.MapProvider = GoogleMapProvider } else if mapProvider == BaiduMapProvider { @@ -667,6 +671,7 @@ func loadMapConfiguration(config *Config, configFile *ini.File, sectionName stri config.EnableMapDataFetchProxy = getConfigItemBoolValue(configFile, sectionName, "map_data_fetch_proxy", false) config.MapProxy = getConfigItemStringValue(configFile, sectionName, "proxy", "system") config.TomTomMapAPIKey = getConfigItemStringValue(configFile, sectionName, "tomtom_map_api_key") + config.TianDiTuAPIKey = getConfigItemStringValue(configFile, sectionName, "tianditu_map_app_key") config.GoogleMapAPIKey = getConfigItemStringValue(configFile, sectionName, "google_map_api_key") config.BaiduMapAK = getConfigItemStringValue(configFile, sectionName, "baidu_map_ak") config.AmapApplicationKey = getConfigItemStringValue(configFile, sectionName, "amap_application_key") diff --git a/src/consts/map.js b/src/consts/map.js index e4138124..922a8af5 100644 --- a/src/consts/map.js +++ b/src/consts/map.js @@ -71,6 +71,29 @@ const leafletTileSources = { defaultZoomLevel: 14, website: 'https://tomtom.com', attribution : '© 1992 - 2023 TomTom.' + }, + 'tianditu': { + tileUrlFormat: 'https://t{s}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}', + tileUrlSubDomains: '01234567', + tileUrlExtraParams: [ + { + paramName: 'tk', + paramValueType: 'tianditu_key' + } + ], + annotationUrlFormat: 'https://t{s}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}', + annotationUrlSubDomains: '01234567', + annotationUrlExtraParams: [ + { + paramName: 'tk', + paramValueType: 'tianditu_key' + } + ], + minZoom: 1, + maxZoom: 18, + defaultZoomLevel: 14, + website: 'https://www.tianditu.gov.cn', + attribution : '天地图' } } diff --git a/src/lib/map/leaflet.js b/src/lib/map/leaflet.js index b58ad79e..35a38a0a 100644 --- a/src/lib/map/leaflet.js +++ b/src/lib/map/leaflet.js @@ -5,7 +5,8 @@ import { getCustomMapMinZoomLevel, getCustomMapMaxZoomLevel, getCustomMapDefaultZoomLevel, - getTomTomMapAPIKey + getTomTomMapAPIKey, + getTianDiTuMapAPIKey } from '@/lib/server_settings.js'; import services from '@/lib/services.js'; @@ -35,6 +36,7 @@ export function createLeafletMapHolder(mapProvider) { minZoomLevel: mapProvider !== 'custom' ? mapTileSource.minZoom : getCustomMapMinZoomLevel(), leafletInstance: null, leafletTileLayer: null, + leafletAnnotationLayer: null, leafletZoomControl: null, leafletAttribution: null, leafletCenterMarker: null @@ -65,21 +67,7 @@ export function createLeafletMapInstance(mapHolder, mapContainer, options) { mapTileSource.tileUrlFormat = services.generateMapProxyTileImageUrl(mapHolder.mapProvider, options.language); mapTileSource.tileUrlSubDomains = ''; } else if (mapTileSource.tileUrlExtraParams) { - const params = []; - - for (let i = 0; i < mapTileSource.tileUrlExtraParams.length; i++) { - const param = mapTileSource.tileUrlExtraParams[i]; - - if (param.paramValueType === 'tomtom_key') { - params.push(param.paramName + '=' + getTomTomMapAPIKey()); - } else if (param.paramValueType === 'language' && options.language) { - params.push(param.paramName + '=' + options.language); - } - } - - if (params.length) { - mapTileSource.tileUrlFormat = mapTileSource.tileUrlFormat + '?' + params.join('&'); - } + mapTileSource.tileUrlFormat = getFinalUrlFormat(mapTileSource.tileUrlFormat, mapTileSource.tileUrlExtraParams, options); } const tileLayer = leaflet.tileLayer(mapTileSource.tileUrlFormat, { @@ -89,6 +77,24 @@ export function createLeafletMapInstance(mapHolder, mapContainer, options) { }); tileLayer.addTo(leafletInstance); + if (mapTileSource.annotationUrlFormat) { + 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 @@ -161,3 +167,29 @@ function createCustomMapSource() { 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/server_settings.js b/src/lib/server_settings.js index 1a327b7e..63c94299 100644 --- a/src/lib/server_settings.js +++ b/src/lib/server_settings.js @@ -76,6 +76,10 @@ export function getTomTomMapAPIKey() { return getServerDecodedSetting('tmak'); } +export function getTianDiTuMapAPIKey() { + return getServerDecodedSetting('tdak'); +} + export function getGoogleMapAPIKey() { return getServerDecodedSetting('gmak'); } diff --git a/src/lib/services.js b/src/lib/services.js index 18aaa9f8..4510eec0 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -525,6 +525,16 @@ export default { return url; }, + generateMapProxyAnnotationImageUrl: (mapProvider, language) => { + const token = userState.getToken(); + let url = `${apiConstants.baseProxyUrlPath}/map/annotation/{z}/{x}/{y}.png?provider=${mapProvider}&token=${token}`; + + if (language) { + url = url + `&language=${language}`; + } + + return url; + }, generateGoogleMapJavascriptUrl: (language, callbackFnName) => { let url = `${apiConstants.googleMapJavascriptUrl}?key=${getGoogleMapAPIKey()}&libraries=core,marker&callback=${callbackFnName}`; diff --git a/src/locales/en.js b/src/locales/en.js index e8f3cc38..47ecddc9 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -580,6 +580,7 @@ export default { 'cyclosm': 'CyclOSM', 'cartodb': 'CARTO', 'tomtom': 'TomTom', + 'tianditu': 'TianDiTu', 'googlemap': 'Google Map', 'baidumap': 'Baidu Map', 'amap': 'Amap', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index cebfd986..4fe3efed 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -580,6 +580,7 @@ export default { 'cyclosm': 'CyclOSM', 'cartodb': 'CARTO', 'tomtom': 'TomTom', + 'tianditu': '天地图', 'googlemap': 'Google 地图', 'baidumap': '百度地图', 'amap': '高德地图',