max zoom per tile type

This commit is contained in:
stopflock
2025-10-05 13:04:48 -05:00
parent 5568173c6e
commit 5976ab4bab
5 changed files with 34 additions and 1 deletions
+5
View File
@@ -191,6 +191,11 @@
"attribution": "Attribution", "attribution": "Attribution",
"attributionHint": "© Map Provider", "attributionHint": "© Map Provider",
"attributionRequired": "Attribution is required", "attributionRequired": "Attribution is required",
"maxZoom": "Max Zoom Level",
"maxZoomHint": "Maximum zoom level (1-19)",
"maxZoomRequired": "Max zoom is required",
"maxZoomInvalid": "Max zoom must be a number",
"maxZoomRange": "Max zoom must be between {} and {}",
"fetchPreview": "Fetch Preview", "fetchPreview": "Fetch Preview",
"previewTileLoaded": "Preview tile loaded successfully", "previewTileLoaded": "Preview tile loaded successfully",
"previewTileFailed": "Failed to fetch preview: {}", "previewTileFailed": "Failed to fetch preview: {}",
+7
View File
@@ -8,6 +8,7 @@ class TileType {
final String urlTemplate; final String urlTemplate;
final String attribution; final String attribution;
final Uint8List? previewTile; // Single tile image data for preview final Uint8List? previewTile; // Single tile image data for preview
final int maxZoom; // Maximum zoom level for this tile type
const TileType({ const TileType({
required this.id, required this.id,
@@ -15,6 +16,7 @@ class TileType {
required this.urlTemplate, required this.urlTemplate,
required this.attribution, required this.attribution,
this.previewTile, this.previewTile,
this.maxZoom = 18, // Default max zoom level
}); });
/// Create URL for a specific tile, replacing template variables /// Create URL for a specific tile, replacing template variables
@@ -40,6 +42,7 @@ class TileType {
'urlTemplate': urlTemplate, 'urlTemplate': urlTemplate,
'attribution': attribution, 'attribution': attribution,
'previewTile': previewTile != null ? base64Encode(previewTile!) : null, 'previewTile': previewTile != null ? base64Encode(previewTile!) : null,
'maxZoom': maxZoom,
}; };
static TileType fromJson(Map<String, dynamic> json) => TileType( static TileType fromJson(Map<String, dynamic> json) => TileType(
@@ -50,6 +53,7 @@ class TileType {
previewTile: json['previewTile'] != null previewTile: json['previewTile'] != null
? base64Decode(json['previewTile']) ? base64Decode(json['previewTile'])
: null, : null,
maxZoom: json['maxZoom'] ?? 18, // Default to 18 if not specified
); );
TileType copyWith({ TileType copyWith({
@@ -58,12 +62,14 @@ class TileType {
String? urlTemplate, String? urlTemplate,
String? attribution, String? attribution,
Uint8List? previewTile, Uint8List? previewTile,
int? maxZoom,
}) => TileType( }) => TileType(
id: id ?? this.id, id: id ?? this.id,
name: name ?? this.name, name: name ?? this.name,
urlTemplate: urlTemplate ?? this.urlTemplate, urlTemplate: urlTemplate ?? this.urlTemplate,
attribution: attribution ?? this.attribution, attribution: attribution ?? this.attribution,
previewTile: previewTile ?? this.previewTile, previewTile: previewTile ?? this.previewTile,
maxZoom: maxZoom ?? this.maxZoom,
); );
@override @override
@@ -151,6 +157,7 @@ class DefaultTileProviders {
name: 'Street Map', name: 'Street Map',
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
attribution: '© OpenStreetMap contributors', attribution: '© OpenStreetMap contributors',
maxZoom: 19,
), ),
], ],
), ),
@@ -256,6 +256,7 @@ class _TileTypeDialogState extends State<_TileTypeDialog> {
late final TextEditingController _nameController; late final TextEditingController _nameController;
late final TextEditingController _urlController; late final TextEditingController _urlController;
late final TextEditingController _attributionController; late final TextEditingController _attributionController;
late final TextEditingController _maxZoomController;
Uint8List? _previewTile; Uint8List? _previewTile;
bool _isLoadingPreview = false; bool _isLoadingPreview = false;
@@ -266,6 +267,7 @@ class _TileTypeDialogState extends State<_TileTypeDialog> {
_nameController = TextEditingController(text: tileType?.name ?? ''); _nameController = TextEditingController(text: tileType?.name ?? '');
_urlController = TextEditingController(text: tileType?.urlTemplate ?? ''); _urlController = TextEditingController(text: tileType?.urlTemplate ?? '');
_attributionController = TextEditingController(text: tileType?.attribution ?? ''); _attributionController = TextEditingController(text: tileType?.attribution ?? '');
_maxZoomController = TextEditingController(text: (tileType?.maxZoom ?? 18).toString());
_previewTile = tileType?.previewTile; _previewTile = tileType?.previewTile;
} }
@@ -274,6 +276,7 @@ class _TileTypeDialogState extends State<_TileTypeDialog> {
_nameController.dispose(); _nameController.dispose();
_urlController.dispose(); _urlController.dispose();
_attributionController.dispose(); _attributionController.dispose();
_maxZoomController.dispose();
super.dispose(); super.dispose();
} }
@@ -326,6 +329,22 @@ class _TileTypeDialogState extends State<_TileTypeDialog> {
validator: (value) => value?.trim().isEmpty == true ? locService.t('tileTypeEditor.attributionRequired') : null, validator: (value) => value?.trim().isEmpty == true ? locService.t('tileTypeEditor.attributionRequired') : null,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextFormField(
controller: _maxZoomController,
decoration: InputDecoration(
labelText: locService.t('tileTypeEditor.maxZoom'),
hintText: locService.t('tileTypeEditor.maxZoomHint'),
),
keyboardType: TextInputType.number,
validator: (value) {
if (value?.trim().isEmpty == true) return locService.t('tileTypeEditor.maxZoomRequired');
final zoom = int.tryParse(value!);
if (zoom == null) return locService.t('tileTypeEditor.maxZoomInvalid');
if (zoom < 1 || zoom > kAbsoluteMaxZoom) return locService.t('tileTypeEditor.maxZoomRange', params: ['1', kAbsoluteMaxZoom.toString()]);
return null;
},
),
const SizedBox(height: 16),
Row( Row(
children: [ children: [
TextButton.icon( TextButton.icon(
@@ -425,6 +444,7 @@ class _TileTypeDialogState extends State<_TileTypeDialog> {
urlTemplate: _urlController.text.trim(), urlTemplate: _urlController.text.trim(),
attribution: _attributionController.text.trim(), attribution: _attributionController.text.trim(),
previewTile: _previewTile, previewTile: _previewTile,
maxZoom: int.parse(_maxZoomController.text.trim()),
); );
widget.onSave(tileType); widget.onSave(tileType);
+1
View File
@@ -78,6 +78,7 @@ class TileLayerManager {
return TileLayer( return TileLayer(
urlTemplate: urlTemplate, urlTemplate: urlTemplate,
userAgentPackageName: 'me.deflock.deflockapp', userAgentPackageName: 'me.deflock.deflockapp',
maxZoom: selectedTileType?.maxZoom?.toDouble() ?? 18.0,
tileProvider: NetworkTileProvider( tileProvider: NetworkTileProvider(
httpClient: _tileHttpClient, httpClient: _tileHttpClient,
// Enable flutter_map caching - cache busting handled by URL changes and FlutterMap key // Enable flutter_map caching - cache busting handled by URL changes and FlutterMap key
+1 -1
View File
@@ -452,7 +452,7 @@ class MapViewState extends State<MapView> {
options: MapOptions( options: MapOptions(
initialCenter: _gpsController.currentLocation ?? _positionManager.initialLocation ?? LatLng(37.7749, -122.4194), initialCenter: _gpsController.currentLocation ?? _positionManager.initialLocation ?? LatLng(37.7749, -122.4194),
initialZoom: _positionManager.initialZoom ?? 15, initialZoom: _positionManager.initialZoom ?? 15,
maxZoom: 19, maxZoom: (appState.selectedTileType?.maxZoom ?? 18).toDouble(),
onPositionChanged: (pos, gesture) { onPositionChanged: (pos, gesture) {
setState(() {}); // Instant UI update for zoom, etc. setState(() {}); // Instant UI update for zoom, etc.
if (gesture) widget.onUserGesture(); if (gesture) widget.onUserGesture();