mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-05-15 21:48:18 +02:00
Improve subdomain notation, fix error catching for xyz in tile URL
This commit is contained in:
+30
-1
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "归属为必填项",
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user