mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: Modernize map selection and tile cache
This commit is contained in:
@@ -1,7 +1,35 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meshcore_sar_app/services/offline_map_caching_provider.dart';
|
||||
import 'package:meshcore_sar_app/services/offline_tile_cache_service.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
void main() {
|
||||
late Directory tempDir;
|
||||
final cache = OfflineTileCacheService.instance;
|
||||
|
||||
setUpAll(() {
|
||||
sqfliteFfiInit();
|
||||
databaseFactory = databaseFactoryFfi;
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
tempDir = await Directory.systemTemp.createTemp('offline_map_provider_test_');
|
||||
await cache.resetForTesting();
|
||||
cache.setBaseDirForTesting('${tempDir.path}/tiles');
|
||||
cache.setDatabasePathForTesting('${tempDir.path}/tiles.db');
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await cache.resetForTesting();
|
||||
if (await tempDir.exists()) {
|
||||
await tempDir.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
|
||||
test('parses query-style tile URLs used by Google layers', () {
|
||||
final coords = OfflineMapCachingProvider.parseTileUrlForTesting(
|
||||
'http://mt0.google.com/vt/lyrs=m&hl=en&x=4312&y=2810&z=13',
|
||||
@@ -35,4 +63,48 @@ void main() {
|
||||
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
||||
);
|
||||
});
|
||||
|
||||
test('returns cached raw bytes directly', () async {
|
||||
const url =
|
||||
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/9/173/267';
|
||||
final template = OfflineMapCachingProvider.extractUrlTemplateForTesting(url);
|
||||
final styleHash = cache.styleHashFromUrl(template);
|
||||
final bytes = Uint8List.fromList([1, 2, 3]);
|
||||
|
||||
await cache.saveStyleMeta(
|
||||
styleHash,
|
||||
displayName: 'Esri',
|
||||
urlTemplate: template,
|
||||
);
|
||||
await cache.putTile(
|
||||
styleHash,
|
||||
9,
|
||||
267,
|
||||
173,
|
||||
bytes,
|
||||
contentType: 'image/jpeg',
|
||||
sourceUrl: url,
|
||||
);
|
||||
|
||||
final provider = OfflineMapCachingProvider(_NullMapCachingProvider());
|
||||
final tile = await provider.getTile(url);
|
||||
|
||||
expect(tile, isNotNull);
|
||||
expect(tile!.bytes, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
class _NullMapCachingProvider implements MapCachingProvider {
|
||||
@override
|
||||
bool get isSupported => true;
|
||||
|
||||
@override
|
||||
Future<CachedMapTile?> getTile(String url) async => null;
|
||||
|
||||
@override
|
||||
Future<void> putTile({
|
||||
required String url,
|
||||
required CachedMapTileMetadata metadata,
|
||||
Uint8List? bytes,
|
||||
}) async {}
|
||||
}
|
||||
|
||||
118
test/services/offline_tile_cache_service_test.dart
Normal file
118
test/services/offline_tile_cache_service_test.dart
Normal file
@@ -0,0 +1,118 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meshcore_sar_app/services/offline_tile_cache_service.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
void main() {
|
||||
late Directory tempDir;
|
||||
final cache = OfflineTileCacheService.instance;
|
||||
|
||||
setUpAll(() {
|
||||
sqfliteFfiInit();
|
||||
databaseFactory = databaseFactoryFfi;
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
tempDir = await Directory.systemTemp.createTemp('offline_tile_cache_test_');
|
||||
await cache.resetForTesting();
|
||||
cache.setBaseDirForTesting('${tempDir.path}/tiles');
|
||||
cache.setDatabasePathForTesting('${tempDir.path}/tiles.db');
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await cache.resetForTesting();
|
||||
if (await tempDir.exists()) {
|
||||
await tempDir.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
|
||||
test('stores original bytes and content type without conversion', () async {
|
||||
const styleHash = 'abcdef123456';
|
||||
final bytes = Uint8List.fromList([1, 2, 3, 4]);
|
||||
|
||||
await cache.saveStyleMeta(
|
||||
styleHash,
|
||||
displayName: 'Test layer',
|
||||
urlTemplate: 'https://example.com/{z}/{x}/{y}.png',
|
||||
);
|
||||
await cache.putTile(
|
||||
styleHash,
|
||||
8,
|
||||
12,
|
||||
34,
|
||||
bytes,
|
||||
contentType: 'image/png; charset=binary',
|
||||
sourceUrl: 'https://example.com/8/12/34.png',
|
||||
);
|
||||
|
||||
final tile = await cache.getTileData(styleHash, 8, 12, 34);
|
||||
expect(tile, isNotNull);
|
||||
expect(tile!.bytes, bytes);
|
||||
expect(tile.contentType, 'image/png');
|
||||
expect(await cache.hasTile(styleHash, 8, 12, 34), isTrue);
|
||||
expect(await cache.getCacheSize(), bytes.length);
|
||||
|
||||
final styles = await cache.listStylesDetailed();
|
||||
expect(styles, hasLength(1));
|
||||
expect(styles.single.tileCount, 1);
|
||||
expect(styles.single.sizeBytes, bytes.length);
|
||||
});
|
||||
|
||||
test('hydrates existing disk cache into sqlite on first access', () async {
|
||||
const styleHash = 'fedcba654321';
|
||||
final base = Directory('${tempDir.path}/tiles/$styleHash/9/13');
|
||||
await base.create(recursive: true);
|
||||
final file = File('${base.path}/42.avif');
|
||||
await file.writeAsBytes([9, 8, 7], flush: true);
|
||||
await File('${tempDir.path}/tiles/$styleHash/meta.json').writeAsString(
|
||||
jsonEncode({
|
||||
'displayName': 'Legacy layer',
|
||||
'urlTemplate': 'https://example.com/{z}/{x}/{y}.avif',
|
||||
}),
|
||||
flush: true,
|
||||
);
|
||||
|
||||
final tiles = await cache.listTilesForStyle(styleHash);
|
||||
expect(tiles, hasLength(1));
|
||||
expect(tiles.single.z, 9);
|
||||
expect(tiles.single.x, 13);
|
||||
expect(tiles.single.y, 42);
|
||||
|
||||
final tile = await cache.getTileData(styleHash, 9, 13, 42);
|
||||
expect(tile, isNotNull);
|
||||
expect(tile!.bytes, Uint8List.fromList([9, 8, 7]));
|
||||
expect(tile.contentType, 'image/avif');
|
||||
|
||||
final styles = await cache.listStylesDetailed();
|
||||
expect(styles.single.displayName, 'Legacy layer');
|
||||
expect(styles.single.tileCount, 1);
|
||||
expect(styles.single.sizeBytes, 3);
|
||||
});
|
||||
|
||||
test('deletes style metadata and files', () async {
|
||||
const styleHash = 'aabbccddeeff';
|
||||
|
||||
await cache.saveStyleMeta(
|
||||
styleHash,
|
||||
displayName: 'Delete me',
|
||||
urlTemplate: 'https://example.com/{z}/{x}/{y}.jpg',
|
||||
);
|
||||
await cache.putTile(
|
||||
styleHash,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
Uint8List.fromList([5, 6]),
|
||||
contentType: 'image/jpeg',
|
||||
);
|
||||
|
||||
expect(await cache.getCacheSize(), 2);
|
||||
await cache.deleteStyle(styleHash);
|
||||
expect(await cache.getCacheSize(), 0);
|
||||
expect(await cache.getTileData(styleHash, 1, 2, 3), isNull);
|
||||
expect(await Directory('${tempDir.path}/tiles/$styleHash').exists(), isFalse);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user