add backend server setup with environment configuration, tile caching, and bus stop fetching functionality
This commit is contained in:
parent
49fc04591b
commit
80ce6a9b6d
4 changed files with 761 additions and 268 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -67,7 +67,6 @@
|
||||||
**/ios/Runner/GeneratedPluginRegistrant.*
|
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
# Web related
|
# Web related
|
||||||
**/web/**/lib/generated_plugin_registrant.dart
|
|
||||||
|
|
||||||
# Service account files
|
# Service account files
|
||||||
svc-keyfile.json
|
svc-keyfile.json
|
||||||
|
|
@ -87,4 +86,4 @@ yarn.lock
|
||||||
|
|
||||||
.claude/
|
.claude/
|
||||||
logs/
|
logs/
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/gestures.dart'
|
import 'package:flutter/gestures.dart'
|
||||||
show
|
show
|
||||||
PointerDeviceKind,
|
PointerDeviceKind,
|
||||||
|
|
@ -288,12 +289,14 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int _idealTileZoom(double zoomValue, {required double devicePixelRatio}) {
|
int _idealTileZoom(double zoomValue) {
|
||||||
final ratio = devicePixelRatio.clamp(1.0, 4.0).toDouble();
|
// Raster tiles are already rendered at device-pixel-ratio-aware
|
||||||
// Bias toward higher-detail tiles on high-DPI screens so the visible
|
// resolutions, so boosting source tile zoom by DPR double-counts detail
|
||||||
// raster layer matches the viewport density more closely.
|
// and explodes the number of tiles mobile has to keep visible at once.
|
||||||
final adjusted = zoomValue + (math.log(ratio) / math.ln2);
|
// We still snap by logical zoom so the next tile level starts loading as
|
||||||
return adjusted.round().clamp(_minTileZoom, _maxTileZoom);
|
// you cross the midpoint, instead of waiting for the next whole integer.
|
||||||
|
final snapped = zoomValue.round();
|
||||||
|
return snapped.clamp(_minTileZoom, _maxTileZoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setZoomAroundFocal(double requestedZoom, Offset focal) {
|
void _setZoomAroundFocal(double requestedZoom, Offset focal) {
|
||||||
|
|
@ -314,10 +317,7 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
|
||||||
|
|
||||||
_zoom = nextZoom;
|
_zoom = nextZoom;
|
||||||
_mapCenter = _worldToLatLng(centerWorldNext, nextZoom);
|
_mapCenter = _worldToLatLng(centerWorldNext, nextZoom);
|
||||||
_activeTileZoom = _idealTileZoom(
|
_activeTileZoom = _idealTileZoom(_zoom);
|
||||||
_zoom,
|
|
||||||
devicePixelRatio: _devicePixelRatio,
|
|
||||||
);
|
|
||||||
if (_activeTileZoom != previousTileZoom) {
|
if (_activeTileZoom != previousTileZoom) {
|
||||||
_fallbackTileZoom = previousTileZoom;
|
_fallbackTileZoom = previousTileZoom;
|
||||||
}
|
}
|
||||||
|
|
@ -390,6 +390,17 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
|
||||||
|
|
||||||
void _updatePainterViewport() {
|
void _updatePainterViewport() {
|
||||||
final cw = _latLngToWorld(_mapCenter, _activeTileZoom.toDouble());
|
final cw = _latLngToWorld(_mapCenter, _activeTileZoom.toDouble());
|
||||||
|
final interactionActive = _interactionIdleTimer?.isActive ?? false;
|
||||||
|
final viewportChanged =
|
||||||
|
_pyramidPainter.mapWidth != _mapSize.width ||
|
||||||
|
_pyramidPainter.mapHeight != _mapSize.height ||
|
||||||
|
_pyramidPainter.zoom != _zoom ||
|
||||||
|
_pyramidPainter.centerWorldX != cw.x ||
|
||||||
|
_pyramidPainter.centerWorldY != cw.y ||
|
||||||
|
_pyramidPainter.activeTileZoom != _activeTileZoom ||
|
||||||
|
_pyramidPainter.fallbackTileZoom != _fallbackTileZoom ||
|
||||||
|
_pyramidPainter.devicePixelRatio != _devicePixelRatio ||
|
||||||
|
_pyramidPainter.interactionActive != interactionActive;
|
||||||
_pyramidPainter
|
_pyramidPainter
|
||||||
..mapWidth = _mapSize.width
|
..mapWidth = _mapSize.width
|
||||||
..mapHeight = _mapSize.height
|
..mapHeight = _mapSize.height
|
||||||
|
|
@ -399,8 +410,10 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
|
||||||
..activeTileZoom = _activeTileZoom
|
..activeTileZoom = _activeTileZoom
|
||||||
..fallbackTileZoom = _fallbackTileZoom
|
..fallbackTileZoom = _fallbackTileZoom
|
||||||
..devicePixelRatio = _devicePixelRatio
|
..devicePixelRatio = _devicePixelRatio
|
||||||
..interactionActive = _interactionIdleTimer?.isActive ?? false;
|
..interactionActive = interactionActive;
|
||||||
_pyramidPainter.markDirty();
|
if (viewportChanged) {
|
||||||
|
_pyramidPainter.markDirty();
|
||||||
|
}
|
||||||
_maybeScheduleBusStopFetch();
|
_maybeScheduleBusStopFetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -911,6 +924,14 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
|
||||||
child: _PerformanceHud(
|
child: _PerformanceHud(
|
||||||
fps: _displayFps,
|
fps: _displayFps,
|
||||||
frameMs: _displayFrameMs,
|
frameMs: _displayFrameMs,
|
||||||
|
tileDebugSummary: kDebugMode
|
||||||
|
? 'z$_activeTileZoom'
|
||||||
|
'${_fallbackTileZoom != null ? ' f$_fallbackTileZoom' : ''} '
|
||||||
|
'c${TilePyramidCache.debugCacheCount} '
|
||||||
|
'q${TilePyramidCache.debugQueueCount} '
|
||||||
|
'i${TilePyramidCache.debugInFlightCount} '
|
||||||
|
'${TilePyramidCache.debugApproxMegabytes.toStringAsFixed(0)}MB'
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
|
|
@ -1166,10 +1187,15 @@ class _DebugInspectPanel extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PerformanceHud extends StatelessWidget {
|
class _PerformanceHud extends StatelessWidget {
|
||||||
const _PerformanceHud({required this.fps, required this.frameMs});
|
const _PerformanceHud({
|
||||||
|
required this.fps,
|
||||||
|
required this.frameMs,
|
||||||
|
this.tileDebugSummary,
|
||||||
|
});
|
||||||
|
|
||||||
final double fps;
|
final double fps;
|
||||||
final double frameMs;
|
final double frameMs;
|
||||||
|
final String? tileDebugSummary;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -1203,6 +1229,15 @@ class _PerformanceHud extends StatelessWidget {
|
||||||
color: Color(0xFF4B5563),
|
color: Color(0xFF4B5563),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (tileDebugSummary != null)
|
||||||
|
Text(
|
||||||
|
tileDebugSummary!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF6B7280),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,17 @@ class TilePyramidCache {
|
||||||
static bool isInFlight(int z, int x, int y) =>
|
static bool isInFlight(int z, int x, int y) =>
|
||||||
_inFlight.contains(_TileCoord(z, x, y));
|
_inFlight.contains(_TileCoord(z, x, y));
|
||||||
|
|
||||||
|
static bool contains(int z, int x, int y) =>
|
||||||
|
_cache.containsKey(_TileCoord(z, x, y));
|
||||||
|
|
||||||
|
static int get debugCacheCount => _cache.length;
|
||||||
|
|
||||||
|
static int get debugInFlightCount => _inFlight.length;
|
||||||
|
|
||||||
|
static int get debugQueueCount => _queue.length;
|
||||||
|
|
||||||
|
static double get debugApproxMegabytes => _approxBytes / (1024 * 1024);
|
||||||
|
|
||||||
// Queue a render for (z, x, y). No-op if already in flight.
|
// Queue a render for (z, x, y). No-op if already in flight.
|
||||||
// render() produces the image; onReady() is called after it's stored.
|
// render() produces the image; onReady() is called after it's stored.
|
||||||
static void enqueue(
|
static void enqueue(
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue