add backend server setup with environment configuration, tile caching, and bus stop fetching functionality

This commit is contained in:
ImBenji 2026-03-31 18:51:47 +01:00
parent 49fc04591b
commit 80ce6a9b6d
4 changed files with 761 additions and 268 deletions

1
.gitignore vendored
View file

@ -67,7 +67,6 @@
**/ios/Runner/GeneratedPluginRegistrant.*
# Web related
**/web/**/lib/generated_plugin_registrant.dart
# Service account files
svc-keyfile.json

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/gestures.dart'
show
PointerDeviceKind,
@ -288,12 +289,14 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
}
}
int _idealTileZoom(double zoomValue, {required double devicePixelRatio}) {
final ratio = devicePixelRatio.clamp(1.0, 4.0).toDouble();
// Bias toward higher-detail tiles on high-DPI screens so the visible
// raster layer matches the viewport density more closely.
final adjusted = zoomValue + (math.log(ratio) / math.ln2);
return adjusted.round().clamp(_minTileZoom, _maxTileZoom);
int _idealTileZoom(double zoomValue) {
// Raster tiles are already rendered at device-pixel-ratio-aware
// resolutions, so boosting source tile zoom by DPR double-counts detail
// and explodes the number of tiles mobile has to keep visible at once.
// We still snap by logical zoom so the next tile level starts loading as
// 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) {
@ -314,10 +317,7 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
_zoom = nextZoom;
_mapCenter = _worldToLatLng(centerWorldNext, nextZoom);
_activeTileZoom = _idealTileZoom(
_zoom,
devicePixelRatio: _devicePixelRatio,
);
_activeTileZoom = _idealTileZoom(_zoom);
if (_activeTileZoom != previousTileZoom) {
_fallbackTileZoom = previousTileZoom;
}
@ -390,6 +390,17 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
void _updatePainterViewport() {
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
..mapWidth = _mapSize.width
..mapHeight = _mapSize.height
@ -399,8 +410,10 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
..activeTileZoom = _activeTileZoom
..fallbackTileZoom = _fallbackTileZoom
..devicePixelRatio = _devicePixelRatio
..interactionActive = _interactionIdleTimer?.isActive ?? false;
..interactionActive = interactionActive;
if (viewportChanged) {
_pyramidPainter.markDirty();
}
_maybeScheduleBusStopFetch();
}
@ -911,6 +924,14 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
child: _PerformanceHud(
fps: _displayFps,
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),
@ -1166,10 +1187,15 @@ class _DebugInspectPanel 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 frameMs;
final String? tileDebugSummary;
@override
Widget build(BuildContext context) {
@ -1203,6 +1229,15 @@ class _PerformanceHud extends StatelessWidget {
color: Color(0xFF4B5563),
),
),
if (tileDebugSummary != null)
Text(
tileDebugSummary!,
style: const TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: Color(0xFF6B7280),
),
),
],
),
),

View file

@ -64,6 +64,17 @@ class TilePyramidCache {
static bool isInFlight(int z, int x, int 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.
// render() produces the image; onReady() is called after it's stored.
static void enqueue(

File diff suppressed because it is too large Load diff