mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-12 16:52:51 +00:00
252 lines
7.1 KiB
Dart
252 lines
7.1 KiB
Dart
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
/// A specific tile type within a provider
|
|
class TileType {
|
|
final String id;
|
|
final String name;
|
|
final String urlTemplate;
|
|
final String attribution;
|
|
final Uint8List? previewTile; // Single tile image data for preview
|
|
final int maxZoom; // Maximum zoom level for this tile type
|
|
|
|
const TileType({
|
|
required this.id,
|
|
required this.name,
|
|
required this.urlTemplate,
|
|
required this.attribution,
|
|
this.previewTile,
|
|
this.maxZoom = 18, // Default max zoom level
|
|
});
|
|
|
|
/// 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;
|
|
|
|
// Handle Bing Maps quadkey conversion
|
|
if (url.contains('{quadkey}')) {
|
|
final quadkey = _convertToQuadkey(x, y, z);
|
|
url = url.replaceAll('{quadkey}', quadkey);
|
|
}
|
|
|
|
// 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
|
|
url = url
|
|
.replaceAll('{z}', z.toString())
|
|
.replaceAll('{x}', x.toString())
|
|
.replaceAll('{y}', y.toString());
|
|
|
|
if (apiKey != null && apiKey.isNotEmpty) {
|
|
url = url.replaceAll('{api_key}', apiKey);
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
/// Convert x, y, z to Bing Maps quadkey format
|
|
String _convertToQuadkey(int x, int y, int z) {
|
|
final quadkey = StringBuffer();
|
|
for (int i = z; i > 0; i--) {
|
|
int digit = 0;
|
|
final mask = 1 << (i - 1);
|
|
if ((x & mask) != 0) digit++;
|
|
if ((y & mask) != 0) digit += 2;
|
|
quadkey.write(digit);
|
|
}
|
|
return quadkey.toString();
|
|
}
|
|
|
|
/// Check if this tile type needs an API key
|
|
bool get requiresApiKey => urlTemplate.contains('{api_key}');
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'name': name,
|
|
'urlTemplate': urlTemplate,
|
|
'attribution': attribution,
|
|
'previewTile': previewTile != null ? base64Encode(previewTile!) : null,
|
|
'maxZoom': maxZoom,
|
|
};
|
|
|
|
static TileType fromJson(Map<String, dynamic> json) => TileType(
|
|
id: json['id'],
|
|
name: json['name'],
|
|
urlTemplate: json['urlTemplate'],
|
|
attribution: json['attribution'],
|
|
previewTile: json['previewTile'] != null
|
|
? base64Decode(json['previewTile'])
|
|
: null,
|
|
maxZoom: json['maxZoom'] ?? 18, // Default to 18 if not specified
|
|
);
|
|
|
|
TileType copyWith({
|
|
String? id,
|
|
String? name,
|
|
String? urlTemplate,
|
|
String? attribution,
|
|
Uint8List? previewTile,
|
|
int? maxZoom,
|
|
}) => TileType(
|
|
id: id ?? this.id,
|
|
name: name ?? this.name,
|
|
urlTemplate: urlTemplate ?? this.urlTemplate,
|
|
attribution: attribution ?? this.attribution,
|
|
previewTile: previewTile ?? this.previewTile,
|
|
maxZoom: maxZoom ?? this.maxZoom,
|
|
);
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is TileType && runtimeType == other.runtimeType && id == other.id;
|
|
|
|
@override
|
|
int get hashCode => id.hashCode;
|
|
}
|
|
|
|
/// A tile provider containing multiple tile types
|
|
class TileProvider {
|
|
final String id;
|
|
final String name;
|
|
final String? apiKey;
|
|
final List<TileType> tileTypes;
|
|
|
|
const TileProvider({
|
|
required this.id,
|
|
required this.name,
|
|
this.apiKey,
|
|
required this.tileTypes,
|
|
});
|
|
|
|
/// Check if this provider is usable (has API key if any tile types need it)
|
|
bool get isUsable {
|
|
final needsKey = tileTypes.any((type) => type.requiresApiKey);
|
|
return !needsKey || (apiKey != null && apiKey!.isNotEmpty);
|
|
}
|
|
|
|
/// Get available tile types (those that don't need API key or have one)
|
|
List<TileType> get availableTileTypes {
|
|
return tileTypes.where((type) => !type.requiresApiKey || isUsable).toList();
|
|
}
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'name': name,
|
|
'apiKey': apiKey,
|
|
'tileTypes': tileTypes.map((type) => type.toJson()).toList(),
|
|
};
|
|
|
|
static TileProvider fromJson(Map<String, dynamic> json) => TileProvider(
|
|
id: json['id'],
|
|
name: json['name'],
|
|
apiKey: json['apiKey'],
|
|
tileTypes: (json['tileTypes'] as List)
|
|
.map((typeJson) => TileType.fromJson(typeJson))
|
|
.toList(),
|
|
);
|
|
|
|
TileProvider copyWith({
|
|
String? id,
|
|
String? name,
|
|
String? apiKey,
|
|
List<TileType>? tileTypes,
|
|
}) => TileProvider(
|
|
id: id ?? this.id,
|
|
name: name ?? this.name,
|
|
apiKey: apiKey ?? this.apiKey,
|
|
tileTypes: tileTypes ?? this.tileTypes,
|
|
);
|
|
|
|
@override
|
|
bool operator ==(Object other) =>
|
|
identical(this, other) ||
|
|
other is TileProvider && runtimeType == other.runtimeType && id == other.id;
|
|
|
|
@override
|
|
int get hashCode => id.hashCode;
|
|
}
|
|
|
|
/// Factory for creating default tile providers
|
|
class DefaultTileProviders {
|
|
/// Create the default set of tile providers
|
|
static List<TileProvider> createDefaults() {
|
|
return [
|
|
TileProvider(
|
|
id: 'openstreetmap',
|
|
name: 'OpenStreetMap',
|
|
tileTypes: [
|
|
TileType(
|
|
id: 'osm_street',
|
|
name: 'Street Map',
|
|
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
attribution: '© OpenStreetMap contributors',
|
|
maxZoom: 19,
|
|
),
|
|
],
|
|
),
|
|
TileProvider(
|
|
id: 'bing',
|
|
name: 'Bing Maps',
|
|
tileTypes: [
|
|
TileType(
|
|
id: 'bing_satellite',
|
|
name: 'Satellite',
|
|
urlTemplate: 'https://ecn.t{0_3}.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=1&n=z',
|
|
attribution: '© Microsoft Corporation',
|
|
maxZoom: 20,
|
|
),
|
|
],
|
|
),
|
|
TileProvider(
|
|
id: 'mapbox',
|
|
name: 'Mapbox',
|
|
tileTypes: [
|
|
TileType(
|
|
id: 'mapbox_satellite',
|
|
name: 'Satellite',
|
|
urlTemplate: 'https://api.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}@2x.jpg90?access_token={api_key}',
|
|
attribution: '© Mapbox © Maxar',
|
|
),
|
|
TileType(
|
|
id: 'mapbox_streets',
|
|
name: 'Streets',
|
|
urlTemplate: 'https://api.mapbox.com/styles/v1/mapbox/streets-v12/tiles/{z}/{x}/{y}?access_token={api_key}',
|
|
attribution: '© Mapbox © OpenStreetMap',
|
|
),
|
|
],
|
|
),
|
|
TileProvider(
|
|
id: 'opentopomap_memomaps',
|
|
name: 'OpenTopoMap/Memomaps',
|
|
tileTypes: [
|
|
TileType(
|
|
id: 'opentopomap_topo',
|
|
name: 'Topographic',
|
|
urlTemplate: 'https://tile.memomaps.de/tilegen/{z}/{x}/{y}.png',
|
|
attribution: 'Kartendaten: © OpenStreetMap-Mitwirkende, SRTM | Kartendarstellung: © OpenTopoMap (CC-BY-SA)',
|
|
maxZoom: 18,
|
|
),
|
|
],
|
|
),
|
|
];
|
|
}
|
|
}
|
|
|