-
Notifications
You must be signed in to change notification settings - Fork 14
Candy ace main : TMS support #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mhassanch
wants to merge
9
commits into
main
Choose a base branch
from
CandyACE-main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
50ac8e1
Initial plan
Copilot 05f6c6a
Add TMS provider support with coordinate flipping
Copilot 83af6b5
Update README with TMS provider documentation
Copilot bafcf32
Merge pull request #1 from CandyACE/copilot/add-tms-support
CandyACE 890081f
Initial plan
Copilot 2e45d00
Add placeholder support for TMS baseUrl with {x}, {y}, {z}
Copilot 36385c4
Fix: Use replaceAll() and improve test comments per code review
Copilot 638f628
Merge pull request #2 from CandyACE/copilot/update-tms-baseurl-placeh…
CandyACE 2d08224
Reverted : formatting
mhassanch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| # TMS (Tile Map Service) Provider | ||
|
|
||
| The TMS provider allows you to use custom tile services with GeoAI.js. It supports both Web Mercator (XYZ) and traditional TMS tile schemes. | ||
|
|
||
| ## Tile Schemes | ||
|
|
||
| ### WebMercator (Default) | ||
|
|
||
| The Web Mercator scheme (also known as XYZ or Google Maps scheme) uses: | ||
| - **Top-left origin**: Coordinates start from the top-left corner | ||
| - **Y axis**: Increases downward | ||
| - **Default for**: Most modern tile services including Cesium, Mapbox, Google Maps | ||
|
|
||
| ```typescript | ||
| import { Tms } from "geoai"; | ||
|
|
||
| const provider = new Tms({ | ||
| baseUrl: "https://example.com/tiles/{z}/{x}/{y}.png", | ||
| scheme: "WebMercator", // This is the default | ||
| }); | ||
| ``` | ||
|
|
||
| ### TMS (Traditional) | ||
|
|
||
| The traditional TMS scheme uses: | ||
| - **Bottom-left origin**: Coordinates start from the bottom-left corner | ||
| - **Y axis**: Increases upward | ||
| - **Default for**: Traditional TMS services | ||
|
|
||
| ```typescript | ||
| import { Tms } from "geoai"; | ||
|
|
||
| const provider = new Tms({ | ||
| baseUrl: "https://example.com/tms/{z}/{x}/{y}.png", | ||
| scheme: "TMS", // Use this for traditional TMS services | ||
| }); | ||
| ``` | ||
|
|
||
| ## Usage with Cesium | ||
|
|
||
| When using with Cesium's `UrlTemplateImageryProvider`, use the `WebMercator` scheme (default): | ||
|
|
||
| ```typescript | ||
| import { Tms } from "geoai"; | ||
| import { UrlTemplateImageryProvider } from "cesium"; | ||
|
|
||
| // Create the TMS provider for GeoAI.js | ||
| const geoaiProvider = new Tms({ | ||
| baseUrl: "https://example.com/tiles/{z}/{x}/{y}.png", | ||
| scheme: "WebMercator", // Match Cesium's default | ||
| }); | ||
|
|
||
| // Create the Cesium imagery provider | ||
| const cesiumProvider = new UrlTemplateImageryProvider({ | ||
| url: "https://example.com/tiles/{z}/{x}/{y}.png", | ||
| // Cesium uses WebMercator by default | ||
| }); | ||
| ``` | ||
|
|
||
| ## Configuration Options | ||
|
|
||
| ```typescript | ||
| interface TmsConfig { | ||
| baseUrl: string; // Tile URL template | ||
| extension?: string; // File extension (default: "png") | ||
| apiKey?: string; // API key (added as query parameter) | ||
| attribution?: string; // Attribution text (default: "TMS Provider") | ||
| tileSize?: number; // Tile size in pixels (default: 256) | ||
| headers?: Record<string, string>; // Custom headers | ||
| scheme?: "WebMercator" | "TMS"; // Tile scheme (default: "WebMercator") | ||
| } | ||
| ``` | ||
|
|
||
| ## Examples | ||
|
|
||
| ### URL Template with Placeholders | ||
|
|
||
| ```typescript | ||
| const provider = new Tms({ | ||
| baseUrl: "https://tiles.example.com/{z}/{x}/{y}.png", | ||
| attribution: "© Example Tiles", | ||
| }); | ||
| ``` | ||
|
|
||
| ### Traditional Path Construction | ||
|
|
||
| ```typescript | ||
| const provider = new Tms({ | ||
| baseUrl: "https://tiles.example.com", | ||
| extension: "jpg", | ||
| attribution: "© Example Tiles", | ||
| }); | ||
| // Generates: https://tiles.example.com/{z}/{x}/{y}.jpg | ||
| ``` | ||
|
|
||
| ### With API Key | ||
|
|
||
| ```typescript | ||
| const provider = new Tms({ | ||
| baseUrl: "https://tiles.example.com/{z}/{x}/{y}.png", | ||
| apiKey: "your-api-key-here", | ||
| }); | ||
| // Generates: https://tiles.example.com/{z}/{x}/{y}.png?apikey=your-api-key-here | ||
| ``` | ||
|
|
||
| ### TMS Scheme Example | ||
|
|
||
| ```typescript | ||
| const provider = new Tms({ | ||
| baseUrl: "https://tms.example.com/{z}/{x}/{y}.png", | ||
| scheme: "TMS", // Use traditional TMS coordinates | ||
| attribution: "© TMS Provider", | ||
| }); | ||
| ``` | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### Tiles appear flipped or in wrong positions | ||
|
|
||
| If your tiles appear in the wrong position, you may need to switch the tile scheme: | ||
|
|
||
| - If using with Cesium, Mapbox, or most modern services: use `"WebMercator"` (default) | ||
| - If using a traditional TMS service: use `"TMS"` | ||
|
|
||
| ### Getting incorrect tile coordinates | ||
|
|
||
| The GeoAI.js library uses the Web Mercator scheme by default. Make sure your `scheme` configuration matches your tile service's coordinate system. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -106,12 +106,47 @@ export const getImageFromTiles = async ( | |
| const tileUrlsGrid = tilesGrid.map((row: any) => | ||
| row.map((tile: any) => tile.tileUrl) | ||
| ); | ||
| // Load all images in parallel | ||
| const tileImages: RawImage[][] = await Promise.all( | ||
| // Load all images in parallel with error handling | ||
| const tileImages: (RawImage | null)[][] = await Promise.all( | ||
| tileUrlsGrid.map((row: any) => | ||
| Promise.all(row.map(async (url: string) => await load_image(url))) | ||
| Promise.all( | ||
| row.map(async (url: string) => { | ||
| try { | ||
| return await load_image(url); | ||
| } catch (error) { | ||
| console.warn(`Failed to load image from ${url}:`, error); | ||
| return null; | ||
| } | ||
| }) | ||
| ) | ||
| ) | ||
| ); | ||
|
|
||
| // Check if any images failed to load | ||
| const failedTiles: string[] = []; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to loop twice over the tiles array - the failed list can be created in the catch above |
||
| tileImages.forEach((row, rowIndex) => { | ||
| row.forEach((image, colIndex) => { | ||
| if (image === null) { | ||
| failedTiles.push(tileUrlsGrid[rowIndex][colIndex]); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| // If all tiles failed, throw an error | ||
| if (failedTiles.length === tileImages.flat().length) { | ||
| throw new GeobaseError( | ||
| ErrorType.ImageLoadFailed, | ||
| `Failed to load all tiles. Please check your network connection and tile URLs.` | ||
| ); | ||
| } | ||
|
|
||
| // If some tiles failed, log a warning but continue | ||
| if (failedTiles.length > 0) { | ||
| console.warn( | ||
| `Failed to load ${failedTiles.length} out of ${tileImages.flat().length} tiles. Continuing with available tiles.` | ||
| ); | ||
| } | ||
|
|
||
| const cornerTiles = [ | ||
| tilesGrid[0][0], // Top-left | ||
| tilesGrid[0][tilesGrid[0].length - 1], // Top-right | ||
|
|
@@ -131,25 +166,31 @@ export const getImageFromTiles = async ( | |
| west: Math.min(...cornerTiles.map((tile: any) => tile.tileGeoJson.bbox[0])), | ||
| }; | ||
| if (stitch) { | ||
| return GeoRawImage.fromPatches(tileImages, bounds, "EPSG:4326"); | ||
| // Filter out null values before passing to fromPatches | ||
| const validTileImages: RawImage[][] = tileImages.map(row => | ||
| row.filter((img): img is RawImage => img !== null) | ||
| ); | ||
| return GeoRawImage.fromPatches(validTileImages, bounds, "EPSG:4326"); | ||
| } | ||
|
|
||
| // If not stitching, set bounds for each individual GeoRawImage | ||
| const geoRawImages: GeoRawImage[][] = tilesGrid.map( | ||
| (row: any, rowIndex: number) => | ||
| row.map((tile: any, colIndex: number) => { | ||
| const tileBounds = { | ||
| north: tile.tileGeoJson.bbox[1], | ||
| south: tile.tileGeoJson.bbox[3], | ||
| east: tile.tileGeoJson.bbox[2], | ||
| west: tile.tileGeoJson.bbox[0], | ||
| }; | ||
| return GeoRawImage.fromRawImage( | ||
| tileImages[rowIndex][colIndex], | ||
| tileBounds, | ||
| "EPSG:4326" | ||
| ); | ||
| }) | ||
| (row: { tileGeoJson: { bbox: number[] } }[], rowIndex: number) => | ||
| row | ||
| .map((tile, colIndex: number) => { | ||
| const image = tileImages[rowIndex][colIndex]; | ||
| if (image === null) { | ||
| return null; | ||
| } | ||
| const tileBounds = { | ||
| north: tile.tileGeoJson.bbox[1], | ||
| south: tile.tileGeoJson.bbox[3], | ||
| east: tile.tileGeoJson.bbox[2], | ||
| west: tile.tileGeoJson.bbox[0], | ||
| }; | ||
| return GeoRawImage.fromRawImage(image, tileBounds, "EPSG:4326"); | ||
| }) | ||
| .filter((img): img is GeoRawImage => img !== null) | ||
| ); | ||
|
|
||
| return geoRawImages; | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can add an actual example from OAM or host our own demo TMS file we can try with this tiny image:
URL: https://map.openaerialmap.org/#/174.70429748296738,-36.87926874318667,18/latest/69294f5891e3ca80b4465833?_k=tb2u8v
TMS: https://tiles.openaerialmap.org/69294d4b91e3ca80b44653c2/0/69294d4b91e3ca80b44653c3/{z}/{x}/{y}