Improve subdomain notation, fix error catching for xyz in tile URL

This commit is contained in:
stopflock
2025-11-22 22:26:04 -06:00
parent 52af77e1ed
commit 3868236816
12 changed files with 106 additions and 35 deletions
+30 -1
View File
@@ -340,7 +340,36 @@ Most users should contribute to production; testing modes add complexity
bool get showUploadModeSelector => kDebugMode;
```
### 11. Navigation & Routing (Implemented, Awaiting Integration)
### 11. Tile Provider System & URL Templates
**Design approach:**
- **Flexible URL templates**: Support multiple coordinate systems and load-balancing patterns
- **Built-in providers**: Curated set of high-quality, reliable tile sources
- **Custom providers**: Users can add any tile service with full validation
- **API key management**: Secure storage with per-provider API keys
**Supported URL placeholders:**
```
{x}, {y}, {z} - Standard TMS tile coordinates
{quadkey} - Bing Maps quadkey format (alternative to x/y/z)
{0_3} - Subdomain 0-3 for load balancing
{1_4} - Subdomain 1-4 for providers using 1-based indexing
{api_key} - API key insertion point (optional)
```
**Built-in providers:**
- **OpenStreetMap**: Standard street map tiles, no API key required
- **Bing Maps**: High-quality satellite imagery using quadkey system, no API key required
- **Mapbox**: Satellite and street tiles, requires API key
- **OpenTopoMap**: Topographic maps, no API key required
**Validation logic:**
URL templates must contain either `{quadkey}` OR all of `{x}`, `{y}`, and `{z}`. This allows for both standard tile services and specialized formats like Bing Maps.
**Why this approach:**
Provides maximum flexibility while maintaining simplicity. Users can add any tile service without code changes, while built-in providers offer immediate functionality. The quadkey system enables access to high-quality satellite imagery without API key requirements.
### 12. Navigation & Routing (Implemented, Awaiting Integration)
**Current state:**
- **Search functionality**: Fully implemented and active
+3 -1
View File
@@ -2,7 +2,9 @@
"1.5.1": {
"content": [
"• NEW: Bing satellite imagery - high-quality satellite tiles used by the iD editor, no API key required",
"• IMPROVED: Added support for quadkey tile formats and multi-subdomain providers"
"• IMPROVED: Enhanced tile provider system with quadkey format support (for Bing Maps and similar providers)",
"• IMPROVED: Flexible subdomain patterns - supports both 0-3 and 1-4 subdomain ranges for load balancing",
"• IMPROVED: Tile URL validation now accepts either {quadkey} or {x}/{y}/{z} coordinate systems"
]
},
"1.5.0": {
+1 -1
View File
@@ -235,7 +235,7 @@
"urlTemplate": "URL-Vorlage",
"urlTemplateHint": "https://beispiel.com/{z}/{x}/{y}.png",
"urlTemplateRequired": "URL-Vorlage ist erforderlich",
"urlTemplatePlaceholders": "URL muss {z}, {x} und {y} Platzhalter enthalten",
"urlTemplatePlaceholders": "URL muss entweder {quadkey} oder {z}, {x} und {y} Platzhalter enthalten",
"attribution": "Zuschreibung",
"attributionHint": "© Karten-Anbieter",
"attributionRequired": "Zuschreibung ist erforderlich",
+1 -1
View File
@@ -267,7 +267,7 @@
"urlTemplate": "URL Template",
"urlTemplateHint": "https://example.com/{z}/{x}/{y}.png",
"urlTemplateRequired": "URL template is required",
"urlTemplatePlaceholders": "URL must contain {z}, {x}, and {y} placeholders",
"urlTemplatePlaceholders": "URL must contain either {quadkey} or {z}, {x}, and {y} placeholders",
"attribution": "Attribution",
"attributionHint": "© Map Provider",
"attributionRequired": "Attribution is required",
+1 -1
View File
@@ -267,7 +267,7 @@
"urlTemplate": "Plantilla de URL",
"urlTemplateHint": "https://ejemplo.com/{z}/{x}/{y}.png",
"urlTemplateRequired": "La plantilla de URL es requerida",
"urlTemplatePlaceholders": "La URL debe contener marcadores {z}, {x} y {y}",
"urlTemplatePlaceholders": "La URL debe contener marcadores {quadkey} o {z}, {x} y {y}",
"attribution": "Atribución",
"attributionHint": "© Proveedor de Mapas",
"attributionRequired": "La atribución es requerida",
+1 -1
View File
@@ -267,7 +267,7 @@
"urlTemplate": "Modèle d'URL",
"urlTemplateHint": "https://exemple.com/{z}/{x}/{y}.png",
"urlTemplateRequired": "Le modèle d'URL est requis",
"urlTemplatePlaceholders": "L'URL doit contenir les marqueurs {z}, {x} et {y}",
"urlTemplatePlaceholders": "L'URL doit contenir soit {quadkey} soit les marqueurs {z}, {x} et {y}",
"attribution": "Attribution",
"attributionHint": "© Fournisseur de Cartes",
"attributionRequired": "L'attribution est requise",
+1 -1
View File
@@ -267,7 +267,7 @@
"urlTemplate": "Template URL",
"urlTemplateHint": "https://esempio.com/{z}/{x}/{y}.png",
"urlTemplateRequired": "Il template URL è obbligatorio",
"urlTemplatePlaceholders": "L'URL deve contenere i segnaposto {z}, {x} e {y}",
"urlTemplatePlaceholders": "L'URL deve contenere o {quadkey} o i segnaposto {z}, {x} e {y}",
"attribution": "Attribuzione",
"attributionHint": "© Fornitore Mappe",
"attributionRequired": "L'attribuzione è obbligatoria",
+1 -1
View File
@@ -267,7 +267,7 @@
"urlTemplate": "Modelo de URL",
"urlTemplateHint": "https://exemplo.com/{z}/{x}/{y}.png",
"urlTemplateRequired": "Modelo de URL é obrigatório",
"urlTemplatePlaceholders": "URL deve conter os marcadores {z}, {x} e {y}",
"urlTemplatePlaceholders": "URL deve conter {quadkey} ou os marcadores {z}, {x} e {y}",
"attribution": "Atribuição",
"attributionHint": "© Provedor de Mapas",
"attributionRequired": "Atribuição é obrigatória",
+1 -1
View File
@@ -267,7 +267,7 @@
"urlTemplate": "URL 模板",
"urlTemplateHint": "https://example.com/{z}/{x}/{y}.png",
"urlTemplateRequired": "URL 模板为必填项",
"urlTemplatePlaceholders": "URL 必须包含 {z}、{x} 和 {y} 占位符",
"urlTemplatePlaceholders": "URL 必须包含 {quadkey} 或 {z}、{x} 和 {y} 占位符",
"attribution": "归属",
"attributionHint": "© 地图提供商",
"attributionRequired": "归属为必填项",
+17 -5
View File
@@ -20,6 +20,13 @@ class TileType {
});
/// Create URL for a specific tile, replacing template variables
///
/// Supported placeholders:
/// - {x}, {y}, {z}: Standard tile coordinates
/// - {quadkey}: Bing Maps quadkey format (alternative to x/y/z)
/// - {0_3}: Subdomain 0-3 for load balancing
/// - {1_4}: Subdomain 1-4 for providers that use 1-based indexing
/// - {api_key}: API key placeholder (optional)
String getTileUrl(int z, int x, int y, {String? apiKey}) {
String url = urlTemplate;
@@ -29,10 +36,15 @@ class TileType {
url = url.replaceAll('{quadkey}', quadkey);
}
// Handle subdomains (for Bing Maps and other multi-subdomain providers)
if (url.contains('{subdomain}')) {
final subdomain = (x + y) % 4; // Distribute across 0-3 subdomains
url = url.replaceAll('{subdomain}', subdomain.toString());
// Handle subdomains for load balancing
if (url.contains('{0_3}')) {
final subdomain = (x + y) % 4; // 0, 1, 2, 3
url = url.replaceAll('{0_3}', subdomain.toString());
}
if (url.contains('{1_4}')) {
final subdomain = ((x + y) % 4) + 1; // 1, 2, 3, 4
url = url.replaceAll('{1_4}', subdomain.toString());
}
// Standard x/y/z replacement
@@ -196,7 +208,7 @@ class DefaultTileProviders {
TileType(
id: 'bing_satellite',
name: 'Satellite',
urlTemplate: 'https://ecn.t{subdomain}.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=1&n=z',
urlTemplate: 'https://ecn.t{0_3}.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=1&n=z',
attribution: '© Microsoft Corporation',
maxZoom: 20,
),
+21 -6
View File
@@ -318,9 +318,15 @@ class _TileTypeDialogState extends State<_TileTypeDialog> {
),
validator: (value) {
if (value?.trim().isEmpty == true) return locService.t('tileTypeEditor.urlTemplateRequired');
if (!value!.contains('{z}') || !value.contains('{x}') || !value.contains('{y}')) {
// Check for either quadkey OR x+y+z placeholders
final hasQuadkey = value!.contains('{quadkey}');
final hasXYZ = value.contains('{x}') && value.contains('{y}') && value.contains('{z}');
if (!hasQuadkey && !hasXYZ) {
return locService.t('tileTypeEditor.urlTemplatePlaceholders');
}
return null;
},
),
@@ -403,11 +409,20 @@ class _TileTypeDialogState extends State<_TileTypeDialog> {
});
try {
// Use a sample tile from configured preview location
final url = _urlController.text
.replaceAll('{z}', kPreviewTileZoom.toString())
.replaceAll('{x}', kPreviewTileX.toString())
.replaceAll('{y}', kPreviewTileY.toString());
// Create a temporary TileType to use the getTileUrl method
final tempTileType = TileType(
id: 'preview',
name: 'Preview',
urlTemplate: _urlController.text.trim(),
attribution: 'Preview',
);
final url = tempTileType.getTileUrl(
kPreviewTileZoom,
kPreviewTileX,
kPreviewTileY,
apiKey: null, // Don't use API key for preview
);
final response = await http.get(Uri.parse(url));
+28 -15
View File
@@ -15,24 +15,37 @@ void main() {
expect(url, 'https://example.com/3/2/1.png');
});
test('getTileUrl handles subdomain replacement', () {
final tileType = TileType(
id: 'test',
name: 'Test',
urlTemplate: 'https://a{subdomain}.example.com/{z}/{x}/{y}.png',
test('getTileUrl handles subdomain patterns', () {
final tileType0_3 = TileType(
id: 'test_0_3',
name: 'Test 0-3',
urlTemplate: 'https://s{0_3}.example.com/{z}/{x}/{y}.png',
attribution: 'Test',
);
// Test subdomain distribution (should be consistent)
final url1 = tileType.getTileUrl(1, 2, 3);
final url2 = tileType.getTileUrl(1, 2, 3);
expect(url1, url2); // Same input should give same output
final tileType1_4 = TileType(
id: 'test_1_4',
name: 'Test 1-4',
urlTemplate: 'https://s{1_4}.example.com/{z}/{x}/{y}.png',
attribution: 'Test',
);
// Test that different tiles can get different subdomains
final url3 = tileType.getTileUrl(1, 0, 0);
final url4 = tileType.getTileUrl(1, 1, 1);
expect(url3, contains('a0.example.com'));
expect(url4, contains('a2.example.com'));
// Test 0-3 range
final url_0_3_a = tileType0_3.getTileUrl(1, 0, 0);
final url_0_3_b = tileType0_3.getTileUrl(1, 3, 0);
expect(url_0_3_a, contains('s0.example.com'));
expect(url_0_3_b, contains('s3.example.com'));
// Test 1-4 range
final url_1_4_a = tileType1_4.getTileUrl(1, 0, 0);
final url_1_4_b = tileType1_4.getTileUrl(1, 3, 0);
expect(url_1_4_a, contains('s1.example.com'));
expect(url_1_4_b, contains('s4.example.com'));
// Test consistency
final url1 = tileType0_3.getTileUrl(1, 2, 3);
final url2 = tileType0_3.getTileUrl(1, 2, 3);
expect(url1, url2); // Same input should give same output
});
test('getTileUrl handles Bing Maps quadkey conversion', () {
@@ -109,7 +122,7 @@ void main() {
expect(satelliteType.id, 'bing_satellite');
expect(satelliteType.name, 'Satellite');
expect(satelliteType.urlTemplate, contains('quadkey'));
expect(satelliteType.urlTemplate, contains('subdomain'));
expect(satelliteType.urlTemplate, contains('0_3'));
expect(satelliteType.requiresApiKey, isFalse);
expect(satelliteType.attribution, '© Microsoft Corporation');
});