Skip to content

Commit 638f628

Browse files
authored
Merge pull request #2 from CandyACE/copilot/update-tms-baseurl-placeholder
Add placeholder support for TMS baseUrl with {x}, {y}, {z} tokens
2 parents bafcf32 + 36385c4 commit 638f628

File tree

3 files changed

+91
-1
lines changed

3 files changed

+91
-1
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,16 @@ GeoAI.js supports multiple map tile providers:
166166
TMS is a tile-based map specification that uses a bottom-left origin coordinate system. Perfect for custom tile servers and OpenAerialMap.
167167

168168
```javascript
169+
// Option 1: Using URL template with placeholders (recommended)
170+
const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
171+
provider: "tms",
172+
baseUrl: "https://tile.example.com/tiles/{z}/{x}/{y}.png",
173+
apiKey: "your-api-key", // optional
174+
tileSize: 256, // optional, defaults to 256
175+
attribution: "Custom TMS Provider", // optional
176+
});
177+
178+
// Option 2: Using base URL (legacy format, still supported)
169179
const pipeline = await geoai.pipeline([{ task: "object-detection" }], {
170180
provider: "tms",
171181
baseUrl: "https://tile.example.com/tiles",

src/data_providers/tms.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,19 @@ export class Tms extends MapSource {
3535
// TMS uses bottom-left origin, so we need to flip Y coordinate
3636
const tmsY = Math.pow(2, z) - 1 - y;
3737

38-
let url = `${instance.baseUrl}/${z}/${x}/${tmsY}.${instance.extension}`;
38+
let url: string;
39+
40+
// Check if baseUrl contains placeholders {x}, {y}, {z}
41+
if (instance.baseUrl.includes('{x}') || instance.baseUrl.includes('{y}') || instance.baseUrl.includes('{z}')) {
42+
// Use placeholder-based URL template
43+
url = instance.baseUrl
44+
.replaceAll('{z}', z.toString())
45+
.replaceAll('{x}', x.toString())
46+
.replaceAll('{y}', tmsY.toString());
47+
} else {
48+
// Use traditional baseUrl + path construction for backward compatibility
49+
url = `${instance.baseUrl}/${z}/${x}/${tmsY}.${instance.extension}`;
50+
}
3951

4052
// Add API key as query parameter if provided
4153
if (instance.apiKey) {

test/tms.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,74 @@ describe('Tms', () => {
146146

147147
expect(customTms.tileSize).toBe(512);
148148
});
149+
150+
describe('Placeholder URL format', () => {
151+
it('should support {x}, {y}, {z} placeholders in baseUrl', () => {
152+
const placeholderTms = new Tms({
153+
baseUrl: 'https://example.com/tiles/{z}/{x}/{y}.png',
154+
attribution: 'Example',
155+
});
156+
157+
const getTileUrl = placeholderTms.getTileUrlFromTileCoords.bind(placeholderTms);
158+
const url = getTileUrl([123, 456, 18], placeholderTms);
159+
160+
// For TMS, Y coordinate should be flipped: tmsY = (2^18 - 1) - 456 = 262143 - 456 = 261687
161+
expect(url).toBe('https://example.com/tiles/18/123/261687.png');
162+
});
163+
164+
it('should support placeholder format with different extension in URL', () => {
165+
const placeholderTms = new Tms({
166+
baseUrl: 'https://example.com/tiles/{z}/{x}/{y}.jpg',
167+
attribution: 'Example',
168+
});
169+
170+
const getTileUrl = placeholderTms.getTileUrlFromTileCoords.bind(placeholderTms);
171+
const url = getTileUrl([100, 200, 15], placeholderTms);
172+
173+
// For z=15, y=200: tmsY = (2^15 - 1) - 200 = 32767 - 200 = 32567
174+
expect(url).toBe('https://example.com/tiles/15/100/32567.jpg');
175+
});
176+
177+
it('should support placeholder format with API key', () => {
178+
const placeholderTms = new Tms({
179+
baseUrl: 'https://example.com/tiles/{z}/{x}/{y}.png',
180+
apiKey: 'test-key-456',
181+
attribution: 'Example',
182+
});
183+
184+
const getTileUrl = placeholderTms.getTileUrlFromTileCoords.bind(placeholderTms);
185+
const url = getTileUrl([10, 20, 5], placeholderTms);
186+
187+
// For z=5, y=20: tmsY = (2^5 - 1) - 20 = 11
188+
expect(url).toBe('https://example.com/tiles/5/10/11.png?apikey=test-key-456');
189+
});
190+
191+
it('should support placeholder format with custom order', () => {
192+
const placeholderTms = new Tms({
193+
baseUrl: 'https://example.com/map/{x}/{y}/{z}.png',
194+
attribution: 'Example',
195+
});
196+
197+
const getTileUrl = placeholderTms.getTileUrlFromTileCoords.bind(placeholderTms);
198+
const url = getTileUrl([50, 100, 10], placeholderTms);
199+
200+
// For z=10, y=100: tmsY = (2^10 - 1) - 100 = 1023 - 100 = 923
201+
expect(url).toBe('https://example.com/map/50/923/10.png');
202+
});
203+
204+
it('should support placeholder format without extension in URL', () => {
205+
const placeholderTms = new Tms({
206+
baseUrl: 'https://example.com/tiles/{z}/{x}/{y}',
207+
attribution: 'Example',
208+
});
209+
210+
const getTileUrl = placeholderTms.getTileUrlFromTileCoords.bind(placeholderTms);
211+
const url = getTileUrl([5, 10, 3], placeholderTms);
212+
213+
// For z=3, y=10: tmsY = (2^3 - 1) - 10 = 7 - 10 = -3
214+
expect(url).toBe('https://example.com/tiles/3/5/-3');
215+
});
216+
});
149217
});
150218

151219
describe('Configuration', () => {

0 commit comments

Comments
 (0)