mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 08:20:36 +00:00
fix: Prefer cached map tiles offline ref:
This commit is contained in:
@@ -5,22 +5,22 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.00023">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000235">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.355123">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.408391">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="126.848356">
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="120.175289">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_app_store" time="300.595005">
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_app_store" time="1035.867128">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
|
||||
import 'offline_tile_cache_service.dart';
|
||||
import 'tile_sharing_service.dart';
|
||||
|
||||
/// A [MapCachingProvider] that checks the offline AVIF tile cache (and
|
||||
/// optionally peers) before falling through to the built-in cache.
|
||||
/// A [MapCachingProvider] that checks the offline AVIF tile cache before
|
||||
/// falling through to the built-in cache/network path.
|
||||
///
|
||||
/// This allows preloaded tiles to be served during normal map browsing.
|
||||
class OfflineMapCachingProvider implements MapCachingProvider {
|
||||
final MapCachingProvider _delegate;
|
||||
final OfflineTileCacheService _cache = OfflineTileCacheService.instance;
|
||||
final TileSharingService _sharing = TileSharingService.instance;
|
||||
|
||||
OfflineMapCachingProvider(this._delegate);
|
||||
|
||||
@@ -40,28 +36,6 @@ class OfflineMapCachingProvider implements MapCachingProvider {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Try peers
|
||||
if (_sharing.discoveredPeers.isNotEmpty) {
|
||||
final avifBytes = await _sharing.fetchFromAnyPeer(
|
||||
styleHash, coords.z, coords.x, coords.y);
|
||||
if (avifBytes != null) {
|
||||
// Cache locally for next time
|
||||
await _cache.putRawTile(
|
||||
styleHash, coords.z, coords.x, coords.y, avifBytes);
|
||||
final decoded = await OfflineTileCacheService.getTileAsPngStatic(avifBytes);
|
||||
if (decoded != null) {
|
||||
return (
|
||||
bytes: decoded,
|
||||
metadata: CachedMapTileMetadata(
|
||||
staleAt: DateTime.now().add(const Duration(days: 365)),
|
||||
lastModified: null,
|
||||
etag: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall through to delegate (built-in cache)
|
||||
@@ -78,9 +52,54 @@ class OfflineMapCachingProvider implements MapCachingProvider {
|
||||
return _delegate.putTile(url: url, metadata: metadata, bytes: bytes);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static ({int z, int x, int y})? parseTileUrlForTesting(String url) {
|
||||
final coords = _parseTileUrl(url);
|
||||
if (coords == null) {
|
||||
return null;
|
||||
}
|
||||
return (z: coords.z, x: coords.x, y: coords.y);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static String extractUrlTemplateForTesting(String url) {
|
||||
return _extractUrlTemplate(url);
|
||||
}
|
||||
|
||||
/// Parse z/x/y from a tile URL.
|
||||
static _TileCoords? _parseTileUrl(String url) {
|
||||
// Match common patterns: /{z}/{x}/{y}.png, /tile/{z}/{y}/{x}, etc.
|
||||
final queryStyleMatch = RegExp(
|
||||
r'(?:\?|&|/)(?:[^#]*&)?x=(\d+)&y=(\d+)&z=(\d+)(?:&|$)',
|
||||
).firstMatch(url);
|
||||
if (queryStyleMatch != null) {
|
||||
return _TileCoords(
|
||||
z: int.parse(queryStyleMatch.group(3)!),
|
||||
x: int.parse(queryStyleMatch.group(1)!),
|
||||
y: int.parse(queryStyleMatch.group(2)!),
|
||||
);
|
||||
}
|
||||
|
||||
final uri = Uri.tryParse(url);
|
||||
if (uri != null) {
|
||||
final z = int.tryParse(uri.queryParameters['z'] ?? '');
|
||||
final x = int.tryParse(uri.queryParameters['x'] ?? '');
|
||||
final y = int.tryParse(uri.queryParameters['y'] ?? '');
|
||||
if (z != null && x != null && y != null) {
|
||||
return _TileCoords(z: z, x: x, y: y);
|
||||
}
|
||||
}
|
||||
|
||||
// Match known path formats.
|
||||
final yxTilePattern = RegExp(r'/tile/(\d+)/(\d+)/(\d+)$');
|
||||
final yxTileMatch = yxTilePattern.firstMatch(url);
|
||||
if (yxTileMatch != null) {
|
||||
return _TileCoords(
|
||||
z: int.parse(yxTileMatch.group(1)!),
|
||||
x: int.parse(yxTileMatch.group(3)!),
|
||||
y: int.parse(yxTileMatch.group(2)!),
|
||||
);
|
||||
}
|
||||
|
||||
final patterns = [
|
||||
RegExp(r'/(\d+)/(\d+)/(\d+)\.(?:png|jpg|jpeg|webp)'),
|
||||
RegExp(r'/(\d+)/(\d+)/(\d+)$'),
|
||||
@@ -101,7 +120,53 @@ class OfflineMapCachingProvider implements MapCachingProvider {
|
||||
|
||||
/// Extract a URL template from a concrete URL by replacing coordinates.
|
||||
static String _extractUrlTemplate(String url) {
|
||||
// Replace the last three numeric path segments with placeholders
|
||||
final queryStylePattern = RegExp(r'([?&])x=\d+&y=\d+&z=\d+');
|
||||
if (queryStylePattern.hasMatch(url)) {
|
||||
return url.replaceAllMapped(
|
||||
queryStylePattern,
|
||||
(match) => '${match.group(1)}x={x}&y={y}&z={z}',
|
||||
);
|
||||
}
|
||||
|
||||
final pathStyleQueryPattern = RegExp(r'(&)x=\d+&y=\d+&z=\d+');
|
||||
if (pathStyleQueryPattern.hasMatch(url)) {
|
||||
return url.replaceAllMapped(
|
||||
pathStyleQueryPattern,
|
||||
(match) => '${match.group(1)}x={x}&y={y}&z={z}',
|
||||
);
|
||||
}
|
||||
|
||||
final uri = Uri.tryParse(url);
|
||||
if (uri != null) {
|
||||
final query = Map<String, String>.from(uri.queryParameters);
|
||||
var replacedQuery = false;
|
||||
if (query.containsKey('z')) {
|
||||
query['z'] = '{z}';
|
||||
replacedQuery = true;
|
||||
}
|
||||
if (query.containsKey('x')) {
|
||||
query['x'] = '{x}';
|
||||
replacedQuery = true;
|
||||
}
|
||||
if (query.containsKey('y')) {
|
||||
query['y'] = '{y}';
|
||||
replacedQuery = true;
|
||||
}
|
||||
if (replacedQuery) {
|
||||
return uri.replace(queryParameters: query).toString();
|
||||
}
|
||||
}
|
||||
|
||||
final yxTilePattern = RegExp(r'/tile/(\d+)/(\d+)/(\d+)$');
|
||||
final yxTileMatch = yxTilePattern.firstMatch(url);
|
||||
if (yxTileMatch != null) {
|
||||
return url.replaceRange(
|
||||
yxTileMatch.start,
|
||||
yxTileMatch.end,
|
||||
'/tile/{z}/{y}/{x}',
|
||||
);
|
||||
}
|
||||
|
||||
return url.replaceAllMapped(
|
||||
RegExp(r'/(\d+)/(\d+)/(\d+)(\.(?:png|jpg|jpeg|webp))?$'),
|
||||
(m) => '/{z}/{x}/{y}${m.group(4) ?? ''}',
|
||||
|
||||
38
test/services/offline_map_caching_provider_test.dart
Normal file
38
test/services/offline_map_caching_provider_test.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meshcore_sar_app/services/offline_map_caching_provider.dart';
|
||||
|
||||
void main() {
|
||||
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',
|
||||
);
|
||||
|
||||
expect(coords, isNotNull);
|
||||
expect(coords!.z, 13);
|
||||
expect(coords.x, 4312);
|
||||
expect(coords.y, 2810);
|
||||
expect(
|
||||
OfflineMapCachingProvider.extractUrlTemplateForTesting(
|
||||
'http://mt0.google.com/vt/lyrs=m&hl=en&x=4312&y=2810&z=13',
|
||||
),
|
||||
'http://mt0.google.com/vt/lyrs=m&hl=en&x={x}&y={y}&z={z}',
|
||||
);
|
||||
});
|
||||
|
||||
test('parses Esri tile URLs and preserves y/x order in template', () {
|
||||
final coords = OfflineMapCachingProvider.parseTileUrlForTesting(
|
||||
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/9/173/267',
|
||||
);
|
||||
|
||||
expect(coords, isNotNull);
|
||||
expect(coords!.z, 9);
|
||||
expect(coords.x, 267);
|
||||
expect(coords.y, 173);
|
||||
expect(
|
||||
OfflineMapCachingProvider.extractUrlTemplateForTesting(
|
||||
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/9/173/267',
|
||||
),
|
||||
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user