mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-13 01:03:03 +00:00
sorta works for tiles from osm and cams from overpass
This commit is contained in:
BIN
assets/black_1x1.png
Normal file
BIN
assets/black_1x1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 B |
BIN
assets/transparent_1x1.png
Normal file
BIN
assets/transparent_1x1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 B |
@@ -23,12 +23,16 @@ Future<List<int>> fetchOSMTile({
|
||||
print('[fetchOSMTile] FETCH $z/$x/$y');
|
||||
attempt++;
|
||||
final resp = await http.get(Uri.parse(url));
|
||||
if (resp.statusCode == 200) {
|
||||
print('[fetchOSMTile] HTTP ${resp.statusCode} for $z/$x/$y, length=${resp.bodyBytes.length}');
|
||||
if (resp.statusCode == 200 && resp.bodyBytes.isNotEmpty) {
|
||||
print('[fetchOSMTile] SUCCESS $z/$x/$y');
|
||||
return resp.bodyBytes;
|
||||
} else {
|
||||
print('[fetchOSMTile] FAIL $z/$x/$y: code=${resp.statusCode}, bytes=${resp.bodyBytes.length}');
|
||||
throw HttpException('Failed to fetch tile $z/$x/$y: status ${resp.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
print('[fetchOSMTile] Exception $z/$x/$y: $e');
|
||||
if (attempt >= maxAttempts) {
|
||||
print("[fetchOSMTile] Failed for $z/$x/$y after $attempt attempts: $e");
|
||||
rethrow;
|
||||
|
||||
@@ -19,61 +19,7 @@ import '../services/offline_area_service.dart';
|
||||
import '../models/osm_camera_node.dart';
|
||||
import 'debouncer.dart';
|
||||
import 'camera_tag_sheet.dart';
|
||||
|
||||
class DataProviderTileProvider extends TileProvider {
|
||||
@override
|
||||
ImageProvider getImage(TileCoordinates coords, TileLayer options) {
|
||||
print('[DataProviderTileProvider] getImage called for \\${coords.z}/\\${coords.x}/\\${coords.y}');
|
||||
return DataProviderImage(coords, options);
|
||||
}
|
||||
}
|
||||
|
||||
class DataProviderImage extends ImageProvider<DataProviderImage> {
|
||||
final TileCoordinates coords;
|
||||
final TileLayer options;
|
||||
DataProviderImage(this.coords, this.options);
|
||||
|
||||
@override
|
||||
Future<DataProviderImage> obtainKey(ImageConfiguration configuration) {
|
||||
return SynchronousFuture<DataProviderImage>(this);
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter load(
|
||||
DataProviderImage key,
|
||||
Future<ui.Codec> Function(Uint8List, {int? cacheWidth, int? cacheHeight}) decode) {
|
||||
return MultiFrameImageStreamCompleter(
|
||||
codec: _loadAsync(key, decode),
|
||||
scale: 1.0,
|
||||
);
|
||||
}
|
||||
|
||||
Future<ui.Codec> _loadAsync(DataProviderImage key, Future<ui.Codec> Function(Uint8List, {int? cacheWidth, int? cacheHeight}) decode) async {
|
||||
final z = key.coords.z;
|
||||
final x = key.coords.x;
|
||||
final y = key.coords.y;
|
||||
print('[_loadAsync] Called for $z/$x/$y');
|
||||
try {
|
||||
final bytes = await MapDataProvider().getTile(z: z, x: x, y: y);
|
||||
print('[_loadAsync] Got bytes for $z/$x/$y: length=\\${bytes.length}');
|
||||
if (bytes.isEmpty) throw Exception("Empty image bytes for $z/$x/$y");
|
||||
return await decode(Uint8List.fromList(bytes));
|
||||
} catch (e) {
|
||||
// Optionally: provide an error tile
|
||||
print('[MapView] Failed to load OSM tile for $z/$x/$y: $e');
|
||||
// Return a blank pixel or a fallback error tile of your design
|
||||
return await decode(Uint8List.fromList(_transparentPng));
|
||||
}
|
||||
}
|
||||
|
||||
// A tiny 1x1 transparent PNG
|
||||
static const List<int> _transparentPng = [
|
||||
137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,
|
||||
0,0,0,1,0,0,0,1,8,6,0,0,0,31,21,196,137,
|
||||
0,0,0,10,73,68,65,84,8,153,99,0,1,0,0,5,
|
||||
0,1,13,10,42,100,0,0,0,0,73,69,78,68,174,66,
|
||||
96,130];
|
||||
}
|
||||
import 'tile_provider_with_cache.dart';
|
||||
|
||||
// --- Smart marker widget for camera with single/double tap distinction
|
||||
class _CameraMapMarker extends StatefulWidget {
|
||||
@@ -292,10 +238,32 @@ class _MapViewState extends State<MapView> {
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
tileProvider: DataProviderTileProvider(),
|
||||
urlTemplate: 'unused-{z}-{x}-{y}', // Required by flutter_map for tile addressing
|
||||
tileProvider: TileProviderWithCache(
|
||||
onTileCacheUpdated: () { if (mounted) setState(() {}); },
|
||||
),
|
||||
urlTemplate: 'unused-{z}-{x}-{y}',
|
||||
tileSize: 256,
|
||||
// Any other TileLayer customization as needed
|
||||
tileBuilder: (ctx, tileWidget, tileImage) {
|
||||
try {
|
||||
final str = tileImage.toString();
|
||||
final regex = RegExp(r'TileCoordinate\((\d+), (\d+), (\d+)\)');
|
||||
final match = regex.firstMatch(str);
|
||||
if (match != null) {
|
||||
final x = match.group(1);
|
||||
final y = match.group(2);
|
||||
final z = match.group(3);
|
||||
final key = '$z/$x/$y';
|
||||
final bytes = TileProviderWithCache.tileCache[key];
|
||||
if (bytes != null && bytes.isNotEmpty) {
|
||||
return Image.memory(bytes, gaplessPlayback: true, fit: BoxFit.cover);
|
||||
}
|
||||
}
|
||||
return Image.asset('assets/transparent_1x1.png', gaplessPlayback: true, fit: BoxFit.cover);
|
||||
} catch (e) {
|
||||
print('tileBuilder error: $e for tileImage: ${tileImage.toString()}');
|
||||
return tileWidget;
|
||||
}
|
||||
}
|
||||
),
|
||||
PolygonLayer(polygons: overlays),
|
||||
MarkerLayer(markers: markers),
|
||||
|
||||
45
lib/widgets/tile_provider_with_cache.dart
Normal file
45
lib/widgets/tile_provider_with_cache.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import '../services/map_data_provider.dart';
|
||||
|
||||
/// Singleton in-memory tile cache and async provider for custom tiles.
|
||||
class TileProviderWithCache extends TileProvider {
|
||||
static final Map<String, Uint8List> _tileCache = {};
|
||||
static Map<String, Uint8List> get tileCache => _tileCache;
|
||||
final VoidCallback? onTileCacheUpdated;
|
||||
|
||||
TileProviderWithCache({this.onTileCacheUpdated});
|
||||
|
||||
@override
|
||||
ImageProvider getImage(TileCoordinates coords, TileLayer options) {
|
||||
final key = '${coords.z}/${coords.x}/${coords.y}';
|
||||
if (_tileCache.containsKey(key)) {
|
||||
return MemoryImage(_tileCache[key]!);
|
||||
} else {
|
||||
_fetchAndCacheTile(coords, key);
|
||||
// Return a transparent PNG until the tile is available.
|
||||
return const AssetImage('assets/transparent_1x1.png');
|
||||
}
|
||||
}
|
||||
|
||||
void _fetchAndCacheTile(TileCoordinates coords, String key) async {
|
||||
// Don't fire multiple fetches for the same tile simultaneously
|
||||
if (_tileCache.containsKey(key)) return;
|
||||
try {
|
||||
final bytes = await MapDataProvider().getTile(z: coords.z, x: coords.x, y: coords.y);
|
||||
if (bytes.isNotEmpty) {
|
||||
_tileCache[key] = Uint8List.fromList(bytes);
|
||||
print('[TileProviderWithCache] Cached tile $key, bytes=${bytes.length}');
|
||||
if (onTileCacheUpdated != null) {
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) => onTileCacheUpdated!());
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
print('[TileProviderWithCache] Error fetching tile $key: $e');
|
||||
// Optionally: fall back to a different asset, or record failures
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,3 +32,5 @@ flutter:
|
||||
|
||||
assets:
|
||||
- assets/info.txt
|
||||
- assets/transparent_1x1.png
|
||||
- assets/black_1x1.png
|
||||
|
||||
Reference in New Issue
Block a user