diff --git a/lib/services/map_data_provider.dart b/lib/services/map_data_provider.dart index 58117ef..1ed5810 100644 --- a/lib/services/map_data_provider.dart +++ b/lib/services/map_data_provider.dart @@ -50,8 +50,7 @@ class MapDataProvider { bounds: bounds, profiles: profiles, uploadMode: uploadMode, - pageSize: AppState.instance.maxCameras, - fetchAllPages: false, + maxResults: AppState.instance.maxCameras, ); } @@ -76,7 +75,7 @@ class MapDataProvider { bounds: bounds, profiles: profiles, uploadMode: uploadMode, - pageSize: AppState.instance.maxCameras, + maxResults: AppState.instance.maxCameras, ); } catch (e) { debugPrint('[MapDataProvider] Remote node fetch failed, error: $e. Falling back to local.'); @@ -106,9 +105,7 @@ class MapDataProvider { bounds: bounds, profiles: profiles, uploadMode: uploadMode, - fetchAllPages: true, - pageSize: pageSize, - maxTries: maxTries, + maxResults: pageSize, ); } diff --git a/lib/services/map_data_submodules/cameras_from_overpass.dart b/lib/services/map_data_submodules/cameras_from_overpass.dart index 9e94d15..c9a6dc9 100644 --- a/lib/services/map_data_submodules/cameras_from_overpass.dart +++ b/lib/services/map_data_submodules/cameras_from_overpass.dart @@ -9,76 +9,84 @@ import '../../models/osm_camera_node.dart'; import '../../app_state.dart'; import '../network_status.dart'; -/// Fetches cameras 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. +/// Fetches surveillance nodes from the Overpass OSM API for the given bounds and profiles. Future> camerasFromOverpass({ required LatLngBounds bounds, required List profiles, UploadMode uploadMode = UploadMode.production, - int pageSize = 500, // Used for both default limit and paging chunk - bool fetchAllPages = false, // True for offline area download, else just grabs first chunk - int maxTries = 3, + required int maxResults, }) async { if (profiles.isEmpty) return []; - const String prodEndpoint = 'https://overpass-api.de/api/interpreter'; - - final nodeClauses = profiles.map((profile) { - 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 - Future> fetchChunk() async { - 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; - final elements = data['elements'] as List; - - // Only log if many cameras found or if it's a bulk download - if (elements.length > 20 || fetchAllPages) { - debugPrint('[camerasFromOverpass] Retrieved ${elements.length} cameras'); - } - NetworkStatus.instance.reportOverpassSuccess(); - return elements.whereType>().map((e) { - return OsmCameraNode( - id: e['id'], - coord: LatLng(e['lat'], e['lon']), - tags: Map.from(e['tags'] ?? {}), - ); - }).toList(); - } catch (e) { - print('[camerasFromOverpass] Overpass exception: $e'); - - // Report network issues on connection errors - if (e.toString().contains('Connection refused') || - e.toString().contains('Connection timed out') || - e.toString().contains('Connection reset')) { - NetworkStatus.instance.reportOverpassIssue(); - } - + + const String overpassEndpoint = 'https://overpass-api.de/api/interpreter'; + + // Build the Overpass query + final query = _buildOverpassQuery(bounds, profiles, maxResults); + + try { + debugPrint('[camerasFromOverpass] Querying Overpass for surveillance nodes...'); + debugPrint('[camerasFromOverpass] Query:\n$query'); + + final response = await http.post( + Uri.parse(overpassEndpoint), + body: {'data': query.trim()} + ); + + if (response.statusCode != 200) { + debugPrint('[camerasFromOverpass] Overpass API error: ${response.body}'); + NetworkStatus.instance.reportOverpassIssue(); return []; } + + final data = jsonDecode(response.body) as Map; + final elements = data['elements'] as List; + + if (elements.length > 20) { + debugPrint('[camerasFromOverpass] Retrieved ${elements.length} surveillance nodes'); + } + + NetworkStatus.instance.reportOverpassSuccess(); + + return elements.whereType>().map((element) { + return OsmCameraNode( + id: element['id'], + coord: LatLng(element['lat'], element['lon']), + tags: Map.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 []; } - - // All paths just use a single fetch now; paging logic no longer required. - return await fetchChunk(); } + +/// Builds an Overpass API query for surveillance nodes matching the given profiles within bounds. +String _buildOverpassQuery(LatLngBounds bounds, List 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; +'''; +} \ No newline at end of file