Spaces:
Running
Running
| const DEFAULT_MAPBOX_STYLE_ID = "mapbox/light-v11"; | |
| const DEFAULT_CITY_TILE_STYLES = [ | |
| { id: "light", label: "浅色", styleId: "mapbox/light-v11" }, | |
| { id: "streets", label: "街道", styleId: "mapbox/streets-v12" }, | |
| { id: "outdoors", label: "地形", styleId: "mapbox/outdoors-v12" }, | |
| { id: "satellite", label: "卫星", styleId: "mapbox/satellite-streets-v12" }, | |
| { id: "custom", label: "自定义", styleId: "" }, | |
| ]; | |
| const OSM_PROVIDER = { | |
| name: "osm", | |
| attribution: "© OpenStreetMap", | |
| tileSize: 256, | |
| }; | |
| const CARTO_LIGHT_PROVIDER = { | |
| name: "cartoLight", | |
| attribution: "© OpenStreetMap © CARTO", | |
| tileSize: 256, | |
| }; | |
| const LOCAL_CITY_TILES_ATTRIBUTION = "© Mapbox © OpenStreetMap"; | |
| function normalizeProviderName(name) { | |
| return String(name || "osm").replace(/[-_\s]/g, "").toLowerCase(); | |
| } | |
| function normalizeMapboxStyleId(styleId = DEFAULT_MAPBOX_STYLE_ID) { | |
| const cleaned = String(styleId || DEFAULT_MAPBOX_STYLE_ID) | |
| .replace(/^styles\/v1\//, "") | |
| .replace(/^\/+|\/+$/g, ""); | |
| const stylePath = cleaned.includes("/") ? cleaned : `mapbox/${cleaned}`; | |
| return stylePath.split("/").map((part) => encodeURIComponent(part)).join("/"); | |
| } | |
| function normalizeStyleOption(style) { | |
| const id = String(style?.id || "").trim(); | |
| const label = String(style?.label || id).trim(); | |
| const styleId = String(style?.styleId || style?.mapboxStyleId || "").trim(); | |
| if (!id || !label || (id !== "custom" && !styleId)) return null; | |
| return { id, label, styleId }; | |
| } | |
| function normalizeTileBounds(bounds) { | |
| if (!bounds || typeof bounds !== "object") return null; | |
| const normalized = { | |
| z: Number(bounds.z), | |
| minX: Number(bounds.minX), | |
| maxX: Number(bounds.maxX), | |
| minY: Number(bounds.minY), | |
| maxY: Number(bounds.maxY), | |
| }; | |
| if (Object.values(normalized).some((value) => !Number.isInteger(value))) return null; | |
| if (normalized.minX > normalized.maxX || normalized.minY > normalized.maxY) return null; | |
| return normalized; | |
| } | |
| function tileIsInsideBounds(bounds, { z, x, y }) { | |
| if (!bounds) return true; | |
| return z === bounds.z | |
| && x >= bounds.minX | |
| && x <= bounds.maxX | |
| && y >= bounds.minY | |
| && y <= bounds.maxY; | |
| } | |
| export function normalizeCityTileStyles(runtimeConfig = {}) { | |
| const configured = Array.isArray(runtimeConfig.mapboxStyles) | |
| ? runtimeConfig.mapboxStyles.map(normalizeStyleOption).filter(Boolean) | |
| : []; | |
| const stylesById = new Map(); | |
| for (const style of configured) stylesById.set(style.id, style); | |
| for (const style of DEFAULT_CITY_TILE_STYLES) { | |
| if (!stylesById.has(style.id)) stylesById.set(style.id, style); | |
| } | |
| return Array.from(stylesById.values()); | |
| } | |
| function resolveMapboxStyleId(special, runtimeConfig) { | |
| if (special.mapboxStyleId) return special.mapboxStyleId; | |
| const styles = normalizeCityTileStyles(runtimeConfig); | |
| const activeStyleId = special.activeMapStyleId | |
| || runtimeConfig.activeMapStyleId | |
| || runtimeConfig.defaultMapStyleId | |
| || runtimeConfig.defaultMapboxStyleKey | |
| || runtimeConfig.defaultStyleId | |
| || ""; | |
| if (!activeStyleId) { | |
| return runtimeConfig.mapboxStyleId | |
| || runtimeConfig.styleId | |
| || runtimeConfig.mapbox?.styleId | |
| || styles[0]?.styleId | |
| || DEFAULT_MAPBOX_STYLE_ID; | |
| } | |
| if (activeStyleId === "custom") { | |
| return runtimeConfig.customMapboxStyleId | |
| || runtimeConfig.mapboxStyleId | |
| || runtimeConfig.styleId | |
| || runtimeConfig.mapbox?.styleId | |
| || DEFAULT_MAPBOX_STYLE_ID; | |
| } | |
| return styles.find((style) => style.id === activeStyleId)?.styleId | |
| || runtimeConfig.mapboxStyleId | |
| || runtimeConfig.styleId | |
| || runtimeConfig.mapbox?.styleId | |
| || DEFAULT_MAPBOX_STYLE_ID; | |
| } | |
| export function resolveCityTileProvider(special = {}, runtimeConfig = {}) { | |
| const providerName = normalizeProviderName( | |
| special.tileProvider | |
| || runtimeConfig.cityTileProvider | |
| || runtimeConfig.tileProvider | |
| || runtimeConfig.provider, | |
| ); | |
| if (providerName === "mapbox" || providerName === "mapboxstatic" || providerName === "mapboxtiles") { | |
| const token = special.mapboxAccessToken | |
| || special.mapboxToken | |
| || runtimeConfig.mapboxAccessToken | |
| || runtimeConfig.mapboxToken | |
| || runtimeConfig.mapbox?.accessToken | |
| || runtimeConfig.mapbox?.token | |
| || ""; | |
| if (!token) return { ...CARTO_LIGHT_PROVIDER, fallbackReason: "missing-mapbox-token" }; | |
| return { | |
| name: "mapboxStatic", | |
| attribution: "© Mapbox © OpenStreetMap", | |
| tileSize: Number(special.tileSize || runtimeConfig.tileSize || 256) || 256, | |
| mapboxStyleId: normalizeMapboxStyleId(resolveMapboxStyleId(special, runtimeConfig)), | |
| mapboxAccessToken: token, | |
| retina: special.retinaTiles ?? runtimeConfig.retinaTiles ?? true, | |
| }; | |
| } | |
| if (providerName === "carto" || providerName === "cartolight" || providerName === "cartopositron") { | |
| return { ...CARTO_LIGHT_PROVIDER }; | |
| } | |
| if (providerName === "local" || providerName === "localcitytiles" || providerName === "localtiles") { | |
| const baseUrl = String( | |
| special.localTileBaseUrl | |
| || special.tileBaseUrl | |
| || runtimeConfig.localTileBaseUrl | |
| || runtimeConfig.localCityTileBaseUrl | |
| || "", | |
| ).replace(/\/+$/g, ""); | |
| if (!baseUrl) return { ...CARTO_LIGHT_PROVIDER, fallbackReason: "missing-local-tile-base-url" }; | |
| return { | |
| name: "localCityTiles", | |
| attribution: special.localTileAttribution || runtimeConfig.localTileAttribution || LOCAL_CITY_TILES_ATTRIBUTION, | |
| tileSize: Number(special.tileSize || runtimeConfig.tileSize || 256) || 256, | |
| baseUrl, | |
| tileBounds: normalizeTileBounds(special.localTileBounds || runtimeConfig.localTileBounds), | |
| version: String(special.localTileVersion || runtimeConfig.localTileVersion || "").trim(), | |
| }; | |
| } | |
| return { ...OSM_PROVIDER }; | |
| } | |
| export function buildCityTileUrl(provider, { z, x, y }) { | |
| if (provider?.name === "localCityTiles") { | |
| if (!tileIsInsideBounds(provider.tileBounds, { z, x, y })) return ""; | |
| const versionSuffix = provider.version ? `?v=${encodeURIComponent(provider.version)}` : ""; | |
| return `${provider.baseUrl}/${z}/${x}/${y}.png${versionSuffix}`; | |
| } | |
| if (provider?.name === "mapboxStatic") { | |
| const scaleSuffix = provider.retina === false ? "" : "@2x"; | |
| const params = new URLSearchParams({ access_token: provider.mapboxAccessToken }); | |
| return `https://api.mapbox.com/styles/v1/${provider.mapboxStyleId}/tiles/${provider.tileSize}/${z}/${x}/${y}${scaleSuffix}?${params.toString()}`; | |
| } | |
| if (provider?.name === "cartoLight") { | |
| const scaleSuffix = provider.retina === false ? "" : "@2x"; | |
| return `https://a.basemaps.cartocdn.com/light_all/${z}/${x}/${y}${scaleSuffix}.png`; | |
| } | |
| return `https://tile.openstreetmap.org/${z}/${x}/${y}.png`; | |
| } | |
| export function runtimeCityTileConfig() { | |
| return globalThis.CITYBOOK_MAP_CONFIG || {}; | |
| } | |