fix martin tile calls

This commit is contained in:
Shautvast 2026-03-31 10:31:59 +02:00
parent fa1de3dfd0
commit 6711aad7d7
9 changed files with 120 additions and 21 deletions

View file

@ -13,6 +13,7 @@ services:
environment:
HOST: "0.0.0.0"
PORT: "8080"
PUBLIC_URL: "http://192.168.2.59:8080"
MARTIN_URL: "http://martin:3001"
PHOTON_URL: "http://photon:2322"
OSRM_DRIVING_URL: "http://osrm-driving:5000"

View file

@ -4,6 +4,9 @@
pub struct AppConfig {
pub host: String,
pub port: u16,
/// Public-facing URL of this backend, used to rewrite tile URLs in style.json.
/// Set via PUBLIC_URL env var, defaults to http://localhost:8080.
pub public_url: String,
pub martin_url: String,
pub photon_url: String,
pub osrm_driving_url: String,
@ -24,6 +27,8 @@ impl AppConfig {
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(8080),
public_url: std::env::var("PUBLIC_URL")
.unwrap_or_else(|_| "http://localhost:8080".into()),
martin_url: std::env::var("MARTIN_URL")
.unwrap_or_else(|_| "http://martin:3000".into()),
photon_url: std::env::var("PHOTON_URL")

View file

@ -1,6 +1,7 @@
use actix_web::{web, HttpResponse};
use bytes::Bytes;
use crate::config::AppConfig;
use crate::errors::AppError;
use crate::services::cache::CacheService;
use crate::services::martin::MartinService;
@ -14,7 +15,13 @@ pub struct TilePath {
y: u32,
}
const VALID_LAYERS: &[&str] = &["openmaptiles", "terrain", "hillshade"];
const VALID_LAYERS: &[&str] = &[
"planet_osm_polygon",
"planet_osm_line",
"planet_osm_point",
"planet_osm_roads",
"pois",
];
/// GET /tiles/{layer}/{z}/{x}/{y}.pbf
///
@ -80,11 +87,64 @@ pub async fn get_tile(
/// GET /tiles/style.json
///
/// Proxies the Mapbox GL style JSON from Martin.
/// Returns a MapLibre GL style that uses this backend as the tile proxy,
/// so Flutter clients never need to reach Martin directly.
pub async fn get_style(
martin: web::Data<MartinService>,
config: web::Data<AppConfig>,
) -> Result<HttpResponse, AppError> {
let style = martin.get_style().await?;
let base = &config.public_url;
let style = serde_json::json!({
"version": 8,
"name": "Privacy Maps",
"sources": {
"planet_osm_polygon": {
"type": "vector",
"tiles": [format!("{base}/tiles/planet_osm_polygon/{{z}}/{{x}}/{{y}}.pbf")],
"minzoom": 0,
"maxzoom": 14
},
"planet_osm_line": {
"type": "vector",
"tiles": [format!("{base}/tiles/planet_osm_line/{{z}}/{{x}}/{{y}}.pbf")],
"minzoom": 0,
"maxzoom": 14
},
"planet_osm_point": {
"type": "vector",
"tiles": [format!("{base}/tiles/planet_osm_point/{{z}}/{{x}}/{{y}}.pbf")],
"minzoom": 0,
"maxzoom": 14
},
"planet_osm_roads": {
"type": "vector",
"tiles": [format!("{base}/tiles/planet_osm_roads/{{z}}/{{x}}/{{y}}.pbf")],
"minzoom": 0,
"maxzoom": 14
}
},
"layers": [
{ "id": "background", "type": "background",
"paint": { "background-color": "#f0ebe3" } },
{ "id": "landuse", "type": "fill", "source": "planet_osm_polygon",
"source-layer": "planet_osm_polygon",
"paint": { "fill-color": "#d4e5c9", "fill-opacity": 0.6 } },
{ "id": "water", "type": "fill", "source": "planet_osm_polygon",
"source-layer": "planet_osm_polygon",
"filter": ["==", "natural", "water"],
"paint": { "fill-color": "#a0c8f0" } },
{ "id": "roads-minor", "type": "line", "source": "planet_osm_line",
"source-layer": "planet_osm_line",
"paint": { "line-color": "#ccc", "line-width": 1 } },
{ "id": "roads-main", "type": "line", "source": "planet_osm_roads",
"source-layer": "planet_osm_roads",
"paint": { "line-color": "#f5a623", "line-width": 2 } },
{ "id": "buildings", "type": "fill", "source": "planet_osm_polygon",
"source-layer": "planet_osm_polygon",
"filter": ["has", "building"],
"paint": { "fill-color": "#d9d0c7", "fill-outline-color": "#bbb" } }
]
});
Ok(HttpResponse::Ok()
.content_type("application/json; charset=utf-8")

View file

@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View file

@ -110,6 +110,13 @@ class ApiClient {
}
/// Riverpod provider for the API client.
/// Initialized with the saved backend URL from the database, falling back to
/// the default if nothing has been saved yet.
final apiClientProvider = Provider<ApiClient>((ref) {
return ApiClient();
});
/// Override this in main() after loading the saved URL.
final initialBackendUrlProvider = Provider<String>((ref) {
return AppConstants.defaultBackendUrl;
});

View file

@ -1,10 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_cancellable_tile_provider/flutter_map_cancellable_tile_provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:vector_map_tiles/vector_map_tiles.dart';
import '../../../../core/api/api_client.dart';
import '../../providers/map_provider.dart';
import '../../providers/map_style_provider.dart';
import '../widgets/map_controls.dart';
import '../widgets/place_card.dart';
@ -34,7 +35,7 @@ class _MapScreenState extends ConsumerState<MapScreen> {
Widget build(BuildContext context) {
final mapState = ref.watch(mapProvider);
final apiClient = ref.watch(apiClientProvider);
final tileUrl = '${apiClient.baseUrl}/tiles/openmaptiles/{z}/{x}/{y}.pbf';
final styleAsync = ref.watch(mapStyleProvider(apiClient.baseUrl));
// Listen for zoom/center changes from the provider and move the map.
ref.listen<MapState>(mapProvider, (previous, next) {
@ -66,10 +67,13 @@ class _MapScreenState extends ConsumerState<MapScreen> {
},
),
children: [
TileLayer(
urlTemplate: tileUrl,
tileProvider: CancellableNetworkTileProvider(),
userAgentPackageName: 'com.privacymaps.app',
styleAsync.when(
data: (style) => VectorTileLayer(
tileProviders: style.providers,
theme: style.theme,
),
loading: () => const SizedBox.shrink(),
error: (e, _) => const SizedBox.shrink(),
),
if (mapState.currentLocation != null)
MarkerLayer(

View file

@ -0,0 +1,10 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:vector_map_tiles/vector_map_tiles.dart';
/// Loads and caches the MapLibre style from the backend.
/// Keyed by base URL so a URL change triggers a reload.
final mapStyleProvider =
FutureProvider.family<Style, String>((ref, baseUrl) async {
final styleUrl = '$baseUrl/tiles/style.json';
return StyleReader(uri: styleUrl).read();
});

View file

@ -37,14 +37,10 @@ class _RouteScreenState extends ConsumerState<RouteScreen> {
final notifier = ref.read(routingProvider.notifier);
final mapState = ref.read(mapProvider);
// Set origin from current location if available.
if (mapState.currentLocation != null) {
notifier.setOrigin(
mapState.currentLocation!.latitude,
mapState.currentLocation!.longitude,
'My Location',
);
}
// Set origin from GPS if available, otherwise use the map center.
final origin = mapState.currentLocation ?? mapState.center;
final originName = mapState.currentLocation != null ? 'My Location' : 'Map center';
notifier.setOrigin(origin.latitude, origin.longitude, originName);
// Set destination from route params.
if (widget.destinationLat != null && widget.destinationLon != null) {

View file

@ -1,12 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'app/app.dart';
import 'core/api/api_client.dart';
import 'core/constants.dart';
import 'core/database/app_database.dart';
void main() {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Load the saved backend URL before building the widget tree so the API
// client is initialized with the correct URL from the first request.
final db = AppDatabase();
final savedUrl = await db.getSetting(AppConstants.settingBackendUrl);
final backendUrl = savedUrl ?? AppConstants.defaultBackendUrl;
runApp(
const ProviderScope(
child: PrivacyMapsApp(),
ProviderScope(
overrides: [
apiClientProvider.overrideWithValue(ApiClient(baseUrl: backendUrl)),
],
child: const PrivacyMapsApp(),
),
);
}