fix martin tile calls
This commit is contained in:
parent
fa1de3dfd0
commit
6711aad7d7
9 changed files with 120 additions and 21 deletions
|
|
@ -13,6 +13,7 @@ services:
|
||||||
environment:
|
environment:
|
||||||
HOST: "0.0.0.0"
|
HOST: "0.0.0.0"
|
||||||
PORT: "8080"
|
PORT: "8080"
|
||||||
|
PUBLIC_URL: "http://192.168.2.59:8080"
|
||||||
MARTIN_URL: "http://martin:3001"
|
MARTIN_URL: "http://martin:3001"
|
||||||
PHOTON_URL: "http://photon:2322"
|
PHOTON_URL: "http://photon:2322"
|
||||||
OSRM_DRIVING_URL: "http://osrm-driving:5000"
|
OSRM_DRIVING_URL: "http://osrm-driving:5000"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub port: u16,
|
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 martin_url: String,
|
||||||
pub photon_url: String,
|
pub photon_url: String,
|
||||||
pub osrm_driving_url: String,
|
pub osrm_driving_url: String,
|
||||||
|
|
@ -24,6 +27,8 @@ impl AppConfig {
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|v| v.parse().ok())
|
.and_then(|v| v.parse().ok())
|
||||||
.unwrap_or(8080),
|
.unwrap_or(8080),
|
||||||
|
public_url: std::env::var("PUBLIC_URL")
|
||||||
|
.unwrap_or_else(|_| "http://localhost:8080".into()),
|
||||||
martin_url: std::env::var("MARTIN_URL")
|
martin_url: std::env::var("MARTIN_URL")
|
||||||
.unwrap_or_else(|_| "http://martin:3000".into()),
|
.unwrap_or_else(|_| "http://martin:3000".into()),
|
||||||
photon_url: std::env::var("PHOTON_URL")
|
photon_url: std::env::var("PHOTON_URL")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
||||||
|
use crate::config::AppConfig;
|
||||||
use crate::errors::AppError;
|
use crate::errors::AppError;
|
||||||
use crate::services::cache::CacheService;
|
use crate::services::cache::CacheService;
|
||||||
use crate::services::martin::MartinService;
|
use crate::services::martin::MartinService;
|
||||||
|
|
@ -14,7 +15,13 @@ pub struct TilePath {
|
||||||
y: u32,
|
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
|
/// GET /tiles/{layer}/{z}/{x}/{y}.pbf
|
||||||
///
|
///
|
||||||
|
|
@ -80,11 +87,64 @@ pub async fn get_tile(
|
||||||
|
|
||||||
/// GET /tiles/style.json
|
/// 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(
|
pub async fn get_style(
|
||||||
martin: web::Data<MartinService>,
|
config: web::Data<AppConfig>,
|
||||||
) -> Result<HttpResponse, AppError> {
|
) -> 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()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json; charset=utf-8")
|
.content_type("application/json; charset=utf-8")
|
||||||
|
|
|
||||||
3
mobile/devtools_options.yaml
Normal file
3
mobile/devtools_options.yaml
Normal 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:
|
||||||
|
|
@ -110,6 +110,13 @@ class ApiClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Riverpod provider for the API client.
|
/// 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) {
|
final apiClientProvider = Provider<ApiClient>((ref) {
|
||||||
return ApiClient();
|
return ApiClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Override this in main() after loading the saved URL.
|
||||||
|
final initialBackendUrlProvider = Provider<String>((ref) {
|
||||||
|
return AppConstants.defaultBackendUrl;
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.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:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:vector_map_tiles/vector_map_tiles.dart';
|
||||||
import '../../../../core/api/api_client.dart';
|
import '../../../../core/api/api_client.dart';
|
||||||
import '../../providers/map_provider.dart';
|
import '../../providers/map_provider.dart';
|
||||||
|
import '../../providers/map_style_provider.dart';
|
||||||
import '../widgets/map_controls.dart';
|
import '../widgets/map_controls.dart';
|
||||||
import '../widgets/place_card.dart';
|
import '../widgets/place_card.dart';
|
||||||
|
|
||||||
|
|
@ -34,7 +35,7 @@ class _MapScreenState extends ConsumerState<MapScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final mapState = ref.watch(mapProvider);
|
final mapState = ref.watch(mapProvider);
|
||||||
final apiClient = ref.watch(apiClientProvider);
|
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.
|
// Listen for zoom/center changes from the provider and move the map.
|
||||||
ref.listen<MapState>(mapProvider, (previous, next) {
|
ref.listen<MapState>(mapProvider, (previous, next) {
|
||||||
|
|
@ -66,10 +67,13 @@ class _MapScreenState extends ConsumerState<MapScreen> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
TileLayer(
|
styleAsync.when(
|
||||||
urlTemplate: tileUrl,
|
data: (style) => VectorTileLayer(
|
||||||
tileProvider: CancellableNetworkTileProvider(),
|
tileProviders: style.providers,
|
||||||
userAgentPackageName: 'com.privacymaps.app',
|
theme: style.theme,
|
||||||
|
),
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
|
error: (e, _) => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
if (mapState.currentLocation != null)
|
if (mapState.currentLocation != null)
|
||||||
MarkerLayer(
|
MarkerLayer(
|
||||||
|
|
|
||||||
10
mobile/lib/features/map/providers/map_style_provider.dart
Normal file
10
mobile/lib/features/map/providers/map_style_provider.dart
Normal 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();
|
||||||
|
});
|
||||||
|
|
@ -37,14 +37,10 @@ class _RouteScreenState extends ConsumerState<RouteScreen> {
|
||||||
final notifier = ref.read(routingProvider.notifier);
|
final notifier = ref.read(routingProvider.notifier);
|
||||||
final mapState = ref.read(mapProvider);
|
final mapState = ref.read(mapProvider);
|
||||||
|
|
||||||
// Set origin from current location if available.
|
// Set origin from GPS if available, otherwise use the map center.
|
||||||
if (mapState.currentLocation != null) {
|
final origin = mapState.currentLocation ?? mapState.center;
|
||||||
notifier.setOrigin(
|
final originName = mapState.currentLocation != null ? 'My Location' : 'Map center';
|
||||||
mapState.currentLocation!.latitude,
|
notifier.setOrigin(origin.latitude, origin.longitude, originName);
|
||||||
mapState.currentLocation!.longitude,
|
|
||||||
'My Location',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set destination from route params.
|
// Set destination from route params.
|
||||||
if (widget.destinationLat != null && widget.destinationLon != null) {
|
if (widget.destinationLat != null && widget.destinationLon != null) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,25 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'app/app.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();
|
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(
|
runApp(
|
||||||
const ProviderScope(
|
ProviderScope(
|
||||||
child: PrivacyMapsApp(),
|
overrides: [
|
||||||
|
apiClientProvider.overrideWithValue(ApiClient(baseUrl: backendUrl)),
|
||||||
|
],
|
||||||
|
child: const PrivacyMapsApp(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue