Files
flutterApp/lib/features/home/presentation/widgets/map/base_map_config.dart
2026-11-06 08:34:19 +08:00

103 lines
3.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
/// 底图来源:中文用户用 ESRI,非中文用户用 Google
enum BaseMapProvider { esri, google }
class BaseMapConfig {
const BaseMapConfig({
required this.provider,
required this.maxNativeZoom,
this.urlTemplate,
this.googleLyr,
});
final BaseMapProvider provider;
/// 原生最大层级:超出后由 flutter_map 用低级别瓦片拉伸,避免整片空白
final int maxNativeZoom;
/// ESRI XYZ 模板(WGS84,与设备 GPS 同一坐标系)
final String? urlTemplate;
/// Google 瓦片图层:s=纯卫星 / h=地名 / y=卫星+地名
final String? googleLyr;
static const BaseMapConfig esri = BaseMapConfig(
provider: BaseMapProvider.esri,
maxNativeZoom: 19,
urlTemplate:
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
);
/// lyrs=y 让境外用户一张图同时拿到卫星影像 + 英文地名(hl=en)
static const BaseMapConfig google = BaseMapConfig(
provider: BaseMapProvider.google,
maxNativeZoom: 20,
googleLyr: 'y',
);
/// 规则:中文 → ESRI;其余语言 → Google
static BaseMapConfig of(Locale locale) =>
locale.languageCode == 'zh' ? esri : google;
}
/// Google 瓦片可达性探测:失败后允许定期重试,避免一次探测失败就永久降级
class GoogleTileAvailability {
/// 失败后的重试冷却时长:开/关 VPN、切换网络后无需重启 App
static const Duration retryAfter = Duration(seconds: 30);
static Future<bool>? _cached;
static bool? _lastResult;
static DateTime? _lastFailAt;
static Future<bool> reachable() {
final cached = _cached;
if (cached != null) {
final failedAt = _lastFailAt;
final shouldRetry = _lastResult == false &&
failedAt != null &&
DateTime.now().difference(failedAt) >= retryAfter;
// 继续复用同一个 Future 对象,避免 FutureBuilder 因 future 变化而反复重建
if (!shouldRetry) return cached;
}
final future = _probe();
_cached = future;
return future;
}
/// 手动重试(例如用户点「刷新地图」、App 从后台恢复)
static void reset() {
_cached = null;
_lastResult = null;
_lastFailAt = null;
}
static Future<bool> _probe() async {
final sw = Stopwatch()..start();
bool ok;
try {
final res = await http
.get(Uri.parse('https://mt0.google.com/vt/lyrs=s&hl=en&x=1&y=0&z=1'))
.timeout(const Duration(seconds: 3));
ok = res.statusCode == 200 && res.bodyBytes.isNotEmpty;
debugPrint(
'🗺️ [BaseMap] Google 探测: http=${res.statusCode} '
'bytes=${res.bodyBytes.length} ${sw.elapsedMilliseconds}ms '
'→ ${ok ? '可用,英文底图用 Google' : '不可用,回退 ESRI'}',
);
} catch (e) {
ok = false;
debugPrint(
'🗺️ [BaseMap] Google 探测异常: ${e.runtimeType} '
'${sw.elapsedMilliseconds}ms → 回退 ESRI',
);
}
_lastResult = ok;
_lastFailAt = ok ? null : DateTime.now();
return ok;
}
}