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 const TileType({ required this.id, required this.name, required this.urlTemplate, required this.attribution, this.previewTile, }); /// Create URL for a specific tile, replacing template variables String getTileUrl(int z, int x, int y, {String? apiKey}) { String url = urlTemplate .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; } /// Check if this tile type needs an API key bool get requiresApiKey => urlTemplate.contains('{api_key}'); Map toJson() => { 'id': id, 'name': name, 'urlTemplate': urlTemplate, 'attribution': attribution, 'previewTile': previewTile != null ? base64Encode(previewTile!) : null, }; static TileType fromJson(Map json) => TileType( id: json['id'], name: json['name'], urlTemplate: json['urlTemplate'], attribution: json['attribution'], previewTile: json['previewTile'] != null ? base64Decode(json['previewTile']) : null, ); TileType copyWith({ String? id, String? name, String? urlTemplate, String? attribution, Uint8List? previewTile, }) => TileType( id: id ?? this.id, name: name ?? this.name, urlTemplate: urlTemplate ?? this.urlTemplate, attribution: attribution ?? this.attribution, previewTile: previewTile ?? this.previewTile, ); @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 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 get availableTileTypes { return tileTypes.where((type) => !type.requiresApiKey || isUsable).toList(); } Map toJson() => { 'id': id, 'name': name, 'apiKey': apiKey, 'tileTypes': tileTypes.map((type) => type.toJson()).toList(), }; static TileProvider fromJson(Map 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? 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 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', ), ], ), TileProvider( id: 'google', name: 'Google', tileTypes: [ TileType( id: 'google_hybrid', name: 'Satellite + Roads', urlTemplate: 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', attribution: '© Google', ), TileType( id: 'google_satellite', name: 'Satellite Only', urlTemplate: 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}', attribution: '© Google', ), TileType( id: 'google_roadmap', name: 'Road Map', urlTemplate: 'https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}', attribution: '© Google', ), ], ), TileProvider( id: 'esri', name: 'Esri', tileTypes: [ TileType( id: 'esri_satellite', name: 'Satellite Imagery', urlTemplate: 'https://services.arcgisonline.com/ArcGis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}.png', attribution: '© Esri © Maxar', ), ], ), 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', ), ], ), ]; } }