mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-08-19 01:17:12 +02:00
clean up overpass fetching
This commit is contained in:
@@ -50,8 +50,7 @@ class MapDataProvider {
|
|||||||
bounds: bounds,
|
bounds: bounds,
|
||||||
profiles: profiles,
|
profiles: profiles,
|
||||||
uploadMode: uploadMode,
|
uploadMode: uploadMode,
|
||||||
pageSize: AppState.instance.maxCameras,
|
maxResults: AppState.instance.maxCameras,
|
||||||
fetchAllPages: false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +75,7 @@ class MapDataProvider {
|
|||||||
bounds: bounds,
|
bounds: bounds,
|
||||||
profiles: profiles,
|
profiles: profiles,
|
||||||
uploadMode: uploadMode,
|
uploadMode: uploadMode,
|
||||||
pageSize: AppState.instance.maxCameras,
|
maxResults: AppState.instance.maxCameras,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('[MapDataProvider] Remote node fetch failed, error: $e. Falling back to local.');
|
debugPrint('[MapDataProvider] Remote node fetch failed, error: $e. Falling back to local.');
|
||||||
@@ -106,9 +105,7 @@ class MapDataProvider {
|
|||||||
bounds: bounds,
|
bounds: bounds,
|
||||||
profiles: profiles,
|
profiles: profiles,
|
||||||
uploadMode: uploadMode,
|
uploadMode: uploadMode,
|
||||||
fetchAllPages: true,
|
maxResults: pageSize,
|
||||||
pageSize: pageSize,
|
|
||||||
maxTries: maxTries,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,76 +9,84 @@ import '../../models/osm_camera_node.dart';
|
|||||||
import '../../app_state.dart';
|
import '../../app_state.dart';
|
||||||
import '../network_status.dart';
|
import '../network_status.dart';
|
||||||
|
|
||||||
/// Fetches cameras from the Overpass OSM API for the given bounds and profiles.
|
/// Fetches surveillance nodes from the Overpass OSM API for the given bounds and profiles.
|
||||||
/// If fetchAllPages is true, returns all possible cameras using multiple API calls (paging with pageSize).
|
|
||||||
/// If false (the default), returns only the first page of up to pageSize results.
|
|
||||||
Future<List<OsmCameraNode>> camerasFromOverpass({
|
Future<List<OsmCameraNode>> camerasFromOverpass({
|
||||||
required LatLngBounds bounds,
|
required LatLngBounds bounds,
|
||||||
required List<CameraProfile> profiles,
|
required List<CameraProfile> profiles,
|
||||||
UploadMode uploadMode = UploadMode.production,
|
UploadMode uploadMode = UploadMode.production,
|
||||||
int pageSize = 500, // Used for both default limit and paging chunk
|
required int maxResults,
|
||||||
bool fetchAllPages = false, // True for offline area download, else just grabs first chunk
|
|
||||||
int maxTries = 3,
|
|
||||||
}) async {
|
}) async {
|
||||||
if (profiles.isEmpty) return [];
|
if (profiles.isEmpty) return [];
|
||||||
const String prodEndpoint = 'https://overpass-api.de/api/interpreter';
|
|
||||||
|
|
||||||
final nodeClauses = profiles.map((profile) {
|
const String overpassEndpoint = 'https://overpass-api.de/api/interpreter';
|
||||||
final tagFilters = profile.tags.entries
|
|
||||||
.map((e) => '["${e.key}"="${e.value}"]')
|
|
||||||
.join('\n ');
|
|
||||||
return '''node\n $tagFilters\n (${bounds.southWest.latitude},${bounds.southWest.longitude},\n ${bounds.northEast.latitude},${bounds.northEast.longitude});''';
|
|
||||||
}).join('\n ');
|
|
||||||
|
|
||||||
// Helper for one Overpass chunk fetch
|
// Build the Overpass query
|
||||||
Future<List<OsmCameraNode>> fetchChunk() async {
|
final query = _buildOverpassQuery(bounds, profiles, maxResults);
|
||||||
final outLine = fetchAllPages ? 'out body;' : 'out body $pageSize;';
|
|
||||||
final query = '''
|
|
||||||
[out:json][timeout:25];
|
|
||||||
(
|
|
||||||
$nodeClauses
|
|
||||||
);
|
|
||||||
$outLine
|
|
||||||
''';
|
|
||||||
try {
|
|
||||||
print('[camerasFromOverpass] Querying Overpass...');
|
|
||||||
print('[camerasFromOverpass] Query:\n$query');
|
|
||||||
final resp = await http.post(Uri.parse(prodEndpoint), body: {'data': query.trim()});
|
|
||||||
// Only log errors
|
|
||||||
if (resp.statusCode != 200) {
|
|
||||||
debugPrint('[camerasFromOverpass] Overpass failed: ${resp.body}');
|
|
||||||
NetworkStatus.instance.reportOverpassIssue();
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
final data = jsonDecode(resp.body) as Map<String, dynamic>;
|
|
||||||
final elements = data['elements'] as List<dynamic>;
|
|
||||||
|
|
||||||
// Only log if many cameras found or if it's a bulk download
|
try {
|
||||||
if (elements.length > 20 || fetchAllPages) {
|
debugPrint('[camerasFromOverpass] Querying Overpass for surveillance nodes...');
|
||||||
debugPrint('[camerasFromOverpass] Retrieved ${elements.length} cameras');
|
debugPrint('[camerasFromOverpass] Query:\n$query');
|
||||||
}
|
|
||||||
NetworkStatus.instance.reportOverpassSuccess();
|
|
||||||
return elements.whereType<Map<String, dynamic>>().map((e) {
|
|
||||||
return OsmCameraNode(
|
|
||||||
id: e['id'],
|
|
||||||
coord: LatLng(e['lat'], e['lon']),
|
|
||||||
tags: Map<String, String>.from(e['tags'] ?? {}),
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
} catch (e) {
|
|
||||||
print('[camerasFromOverpass] Overpass exception: $e');
|
|
||||||
|
|
||||||
// Report network issues on connection errors
|
final response = await http.post(
|
||||||
if (e.toString().contains('Connection refused') ||
|
Uri.parse(overpassEndpoint),
|
||||||
e.toString().contains('Connection timed out') ||
|
body: {'data': query.trim()}
|
||||||
e.toString().contains('Connection reset')) {
|
);
|
||||||
NetworkStatus.instance.reportOverpassIssue();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
debugPrint('[camerasFromOverpass] Overpass API error: ${response.body}');
|
||||||
|
NetworkStatus.instance.reportOverpassIssue();
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// All paths just use a single fetch now; paging logic no longer required.
|
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||||
return await fetchChunk();
|
final elements = data['elements'] as List<dynamic>;
|
||||||
|
|
||||||
|
if (elements.length > 20) {
|
||||||
|
debugPrint('[camerasFromOverpass] Retrieved ${elements.length} surveillance nodes');
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkStatus.instance.reportOverpassSuccess();
|
||||||
|
|
||||||
|
return elements.whereType<Map<String, dynamic>>().map((element) {
|
||||||
|
return OsmCameraNode(
|
||||||
|
id: element['id'],
|
||||||
|
coord: LatLng(element['lat'], element['lon']),
|
||||||
|
tags: Map<String, String>.from(element['tags'] ?? {}),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[camerasFromOverpass] Exception: $e');
|
||||||
|
|
||||||
|
// Report network issues for connection errors
|
||||||
|
if (e.toString().contains('Connection refused') ||
|
||||||
|
e.toString().contains('Connection timed out') ||
|
||||||
|
e.toString().contains('Connection reset')) {
|
||||||
|
NetworkStatus.instance.reportOverpassIssue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds an Overpass API query for surveillance nodes matching the given profiles within bounds.
|
||||||
|
String _buildOverpassQuery(LatLngBounds bounds, List<CameraProfile> profiles, int maxResults) {
|
||||||
|
// Build node clauses for each profile
|
||||||
|
final nodeClauses = profiles.map((profile) {
|
||||||
|
// Convert profile tags to Overpass filter format
|
||||||
|
final tagFilters = profile.tags.entries
|
||||||
|
.map((entry) => '["${entry.key}"="${entry.value}"]')
|
||||||
|
.join();
|
||||||
|
|
||||||
|
// Build the node query with tag filters and bounding box
|
||||||
|
return 'node$tagFilters(${bounds.southWest.latitude},${bounds.southWest.longitude},${bounds.northEast.latitude},${bounds.northEast.longitude});';
|
||||||
|
}).join('\n ');
|
||||||
|
|
||||||
|
return '''
|
||||||
|
[out:json][timeout:25];
|
||||||
|
(
|
||||||
|
$nodeClauses
|
||||||
|
);
|
||||||
|
out body $maxResults;
|
||||||
|
''';
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user