diff --git a/README.md b/README.md index 65e34d8..36e9045 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,9 @@ This project provides a utility for converting Esri Vector Tile Styles to Mapbox ## Current Status -- ✅ The conversion from Esri to Mapbox style specifications is fully supported and operational. +- ✅ The conversion from Esri to Mapbox style specifications is partially supported and operational ( open GitHub issue if you find any issues). - ❌ The conversion from Mapbox to Esri style specifications is not yet implemented. + - ArcGIS Vector Tile Server service endpoint is not yet supported (the mapbox works in ArcGIS Map SDK but not in ArcGIS Online or ArcGIS Pro so it needs to emulate the service endpoint). ## How to Use @@ -54,4 +55,4 @@ Contributions are welcome, especially in implementing the conversion from Mapbox ## License -This project is licensed under the MIT License - see the LICENSE file for details. +This project is licensed under the MIT License - see the LICENSE file for details. - this project is for Education purpose only. diff --git a/example/src/App.svelte b/example/src/App.svelte index 84c9470..ae9d36c 100644 --- a/example/src/App.svelte +++ b/example/src/App.svelte @@ -1,6 +1,5 @@ diff --git a/src/esriToMapbox.ts b/src/esriToMapbox.ts index 249491b..5cdcca0 100644 --- a/src/esriToMapbox.ts +++ b/src/esriToMapbox.ts @@ -1,5 +1,5 @@ -async function fetchEsriStyleJson(baseUrl: string): Promise { - const styleUrl = `${baseUrl}/resources/styles/root.json`; +async function fetchEsriStyleJson(baseUrl: string, token: string | null): Promise { + const styleUrl = `${baseUrl}/resources/styles/root.json${token ? `?token=${token}` : ''}`; const response = await fetch(styleUrl); if (!response.ok) { throw new Error(`Failed to fetch Esri style JSON: ${response.statusText}`); @@ -7,6 +7,7 @@ async function fetchEsriStyleJson(baseUrl: string): Promise { return response.json(); } + function resolveUrlPath(url: string): string { const urlObj = new URL(url); const pathSegments = urlObj.pathname.split('/').filter(segment => segment !== ''); @@ -27,14 +28,27 @@ function resolveUrlPath(url: string): string { return decodeURI(urlObj.toString()); } +function appendTokenToUrl(url: string, token: string | null): string { + if (token) { + const urlObj = new URL(url); + urlObj.searchParams.append('token', token); + return urlObj.toString(); + } + return url; +} async function constructMapboxStyleFromEsri(baseUrl: string, esriStyleJson?: any): Promise { - // Normalize the base URL to ensure it does not end with a slash - const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; + // Extract the token from the base URL + const urlObj = new URL(baseUrl); + const token = urlObj.searchParams.get('token'); + + // Remove the token from the base URL + urlObj.searchParams.delete('token'); + const normalizedBaseUrl = urlObj.toString().replace(/\/$/, ''); // Fetch the Esri style JSON if it's not provided if (!esriStyleJson) { - esriStyleJson = await fetchEsriStyleJson(normalizedBaseUrl); + esriStyleJson = await fetchEsriStyleJson(normalizedBaseUrl, token); } // Clone the Esri style JSON to avoid mutating the original object @@ -48,7 +62,7 @@ async function constructMapboxStyleFromEsri(baseUrl: string, esriStyleJson?: any // Preserve all source properties const updatedSource = { ...source }; // Update tiles URL, remove 'url' property - updatedSource.tiles = [`${normalizedBaseUrl}/tile/{z}/{y}/{x}.pbf`]; + updatedSource.tiles = [`${normalizedBaseUrl}/tile/{z}/{y}/{x}.pbf${token ? `?token=${token}` : ''}`]; delete updatedSource.url; // Update the source with the new properties @@ -59,32 +73,38 @@ async function constructMapboxStyleFromEsri(baseUrl: string, esriStyleJson?: any // Correct the sprite and glyphs paths if (mapboxStyle.sprite) { - mapboxStyle.sprite = `${normalizedBaseUrl}/resources/${mapboxStyle.sprite.replace('../', '')}`; + mapboxStyle.sprite = appendTokenToUrl(`${normalizedBaseUrl}/resources/${mapboxStyle.sprite.replace('../', '')}`, token); } if (mapboxStyle.glyphs) { - mapboxStyle.glyphs = `${normalizedBaseUrl}/resources/${mapboxStyle.glyphs.replace('../', '')}`; + mapboxStyle.glyphs = appendTokenToUrl(`${normalizedBaseUrl}/resources/${mapboxStyle.glyphs.replace('../', '')}`, token); } return mapboxStyle; } + async function resolveEsriRelativePaths(baseUrl: string, esriStyleJson?: any): Promise { - // Normalize the base URL to ensure it does not end with a slash - const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; + // Extract the token from the base URL + const urlObj = new URL(baseUrl); + const token = urlObj.searchParams.get('token'); + + // Remove the token from the base URL + urlObj.searchParams.delete('token'); + const normalizedBaseUrl = urlObj.toString().replace(/\/$/, ''); // Fetch the Esri style JSON if it's not provided if (!esriStyleJson) { - esriStyleJson = await fetchEsriStyleJson(normalizedBaseUrl); + esriStyleJson = await fetchEsriStyleJson(normalizedBaseUrl, token); } + // Resolve the sprite path if (esriStyleJson.sprite) { - esriStyleJson.sprite = `${normalizedBaseUrl}/resources/${esriStyleJson.sprite.replace('../', '')}`; + esriStyleJson.sprite = decodeURI(appendTokenToUrl(`${normalizedBaseUrl}/resources/${esriStyleJson.sprite.replace('../', '')}`, token)); } - // Resolve the glyphs path if (esriStyleJson.glyphs) { - esriStyleJson.glyphs = `${normalizedBaseUrl}/resources/${esriStyleJson.glyphs.replace('../', '')}`; + esriStyleJson.glyphs = decodeURI(appendTokenToUrl(`${normalizedBaseUrl}/resources/${esriStyleJson.glyphs.replace('../', '')}`, token)); } // Resolve the source URL @@ -92,7 +112,7 @@ async function resolveEsriRelativePaths(baseUrl: string, esriStyleJson?: any): P for (const sourceKey in esriStyleJson.sources) { const source = esriStyleJson.sources[sourceKey]; if (source.type === 'vector' && source.url) { - source.url = `${normalizedBaseUrl}/${source.url.replace('../../', '')}`; + source.url = decodeURI(appendTokenToUrl(`${normalizedBaseUrl}/${source.url.replace('../../', '')}`, token)); } } } @@ -100,20 +120,23 @@ async function resolveEsriRelativePaths(baseUrl: string, esriStyleJson?: any): P return esriStyleJson; } + async function constructMapboxStyleFromEsriAbsolute(esriStyleJson?: any): Promise { - // Normalize the base URL to ensure it does not end with a slash // Fetch the Esri style JSON if it's not provided if (!esriStyleJson) { - // error throw new Error(`Failed to fetch Esri style JSON`); } + // Extract the token from the sprite URL + const spriteUrlObj = new URL(esriStyleJson.sprite); + const token = spriteUrlObj.searchParams.get('token'); + // Resolve paths in 'sprite' and 'glyphs' if (esriStyleJson.sprite) { - esriStyleJson.sprite = resolveUrlPath(esriStyleJson.sprite); + esriStyleJson.sprite = decodeURI(resolveUrlPath(appendTokenToUrl(esriStyleJson.sprite, token))); } if (esriStyleJson.glyphs) { - esriStyleJson.glyphs = resolveUrlPath(esriStyleJson.glyphs); + esriStyleJson.glyphs = decodeURI(resolveUrlPath(appendTokenToUrl(esriStyleJson.glyphs, token))); } // Adjust the 'sources' to use the correct tile URL pattern while preserving other properties @@ -121,19 +144,23 @@ async function constructMapboxStyleFromEsriAbsolute(esriStyleJson?: any): Promis for (const sourceKey in esriStyleJson.sources) { const source = esriStyleJson.sources[sourceKey]; if (source.type === 'vector' && source.url) { - // Resolve path in source URL - const resolvedSourceUrl = resolveUrlPath(source.url); + const resolvedSourceUrl = decodeURI(resolveUrlPath(source.url)); + const baseTileUrl = resolvedSourceUrl.endsWith('/') ? resolvedSourceUrl : `${resolvedSourceUrl}/`; - // Preserve all source properties - const updatedSource = { ...source }; + // Remove the token query parameter from the base tile URL + const baseTileUrlObj = new URL(baseTileUrl); + const token = baseTileUrlObj.searchParams.get('token'); + baseTileUrlObj.searchParams.delete('token'); + let baseUrlWithoutToken = baseTileUrlObj.toString(); - // Ensure the resolved URL ends with a slash before appending 'tile/{z}/{y}/{x}.pbf' - const baseTileUrl = resolvedSourceUrl.endsWith('/') ? resolvedSourceUrl : `${resolvedSourceUrl}/`; - updatedSource.tiles = [`${baseTileUrl}tile/{z}/{y}/{x}.pbf`]; + // Add a trailing slash if missing + if (!baseUrlWithoutToken.endsWith('/')) { + baseUrlWithoutToken += '/'; + } + const updatedSource = { ...source }; + updatedSource.tiles = [`${baseUrlWithoutToken}tile/{z}/{y}/{x}.pbf${token ? `?token=${token}` : ''}`]; delete updatedSource.url; - - // Update the source with the new properties esriStyleJson.sources[sourceKey] = updatedSource; } }