diff --git a/lib/services/location_service.dart b/lib/services/location_service.dart index 2da7092e4..1f35000b9 100644 --- a/lib/services/location_service.dart +++ b/lib/services/location_service.dart @@ -1,11 +1,14 @@ import "dart:convert"; +import "dart:io"; import "dart:math"; +import "package:computer/computer.dart"; import "package:logging/logging.dart"; import "package:photos/core/constants.dart"; import "package:photos/core/event_bus.dart"; import "package:photos/events/location_tag_updated_event.dart"; import "package:photos/models/api/entity/type.dart"; +import "package:photos/models/file/file.dart"; import "package:photos/models/local_entity_data.dart"; import "package:photos/models/location/location.dart"; import 'package:photos/models/location_tag/location_tag.dart'; @@ -17,7 +20,7 @@ import "package:shared_preferences/shared_preferences.dart"; class LocationService { late SharedPreferences prefs; final Logger _logger = Logger((LocationService).toString()); - final List _cities = []; + final Computer _computer = Computer.shared(); LocationService._privateConstructor(); @@ -25,6 +28,8 @@ class LocationService { static const kCitiesRemotePath = "https://assets.ente.io/world_cities.json"; + List _cities = []; + void init(SharedPreferences preferences) { prefs = preferences; if (FeatureFlagService.instance.isInternalUserOrDebugBuild()) { @@ -39,8 +44,19 @@ class LocationService { ); } - List getAllCities() { - return _cities; + Future>> getFilesInCity( + List allFiles, + String query, + ) async { + final result = await _computer.compute( + getCityResults, + param: { + "query": query, + "cities": _cities, + "files": allFiles, + }, + ); + return result; } Future>> getLocationTags() { @@ -77,14 +93,6 @@ class LocationService { } } - ///The area bounded by the location tag becomes more elliptical with increase - ///in the magnitude of the latitude on the caritesian plane. When latitude is - ///0 degrees, the ellipse is a circle with a = b = r. When latitude incrases, - ///the major axis (a) has to be scaled by the secant of the latitude. - double _scaleFactor(double lat) { - return 1 / cos(lat * (pi / 180)); - } - Future>> enclosingLocationTags( Location fileCoordinates, ) async { @@ -110,22 +118,6 @@ class LocationService { } } - bool isFileInsideLocationTag( - Location centerPoint, - Location fileCoordinates, - double radius, - ) { - final a = - (radius * _scaleFactor(centerPoint.latitude!)) / kilometersPerDegree; - final b = radius / kilometersPerDegree; - final x = centerPoint.latitude! - fileCoordinates.latitude!; - final y = centerPoint.longitude! - fileCoordinates.longitude!; - if ((x * x) / (a * a) + (y * y) / (b * b) <= 1) { - return true; - } - return false; - } - /// returns [lat, lng] List? convertLocationToDMS(Location centerPoint) { if (centerPoint.latitude == null || centerPoint.longitude == null) { @@ -218,14 +210,15 @@ class LocationService { Future _loadCities() async { try { - final data = + final file = await RemoteAssetsService.instance.getAsset(kCitiesRemotePath); - final citiesJson = json.decode(await data.readAsString()); - final List jsonData = citiesJson['data']; - final cities = - jsonData.map((jsonItem) => City.fromMap(jsonItem)).toList(); - _cities.clear(); - _cities.addAll(cities); + final startTime = DateTime.now(); + _cities = + await _computer.compute(parseCities, param: {"filePath": file.path}); + final endTime = DateTime.now(); + _logger.info( + "Loaded cities in ${(endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch)}ms", + ); _logger.info("Loaded cities"); } catch (e, s) { _logger.severe("Failed to load cities", e, s); @@ -233,6 +226,71 @@ class LocationService { } } +Future> parseCities(Map args) async { + final file = File(args["filePath"]); + final citiesJson = json.decode(await file.readAsString()); + + final List jsonData = citiesJson['data']; + final cities = + jsonData.map((jsonItem) => City.fromMap(jsonItem)).toList(); + return cities; +} + +Map> getCityResults(Map args) { + final query = (args["query"] as String).toLowerCase(); + final cities = args["cities"] as List; + final files = args["files"] as List; + + final matchingCities = cities.where( + (city) => city.city.toLowerCase().contains(query), + ); + + final Map> results = {}; + for (final city in matchingCities) { + final List matchingFiles = []; + final cityLocation = Location(latitude: city.lat, longitude: city.lng); + for (final file in files) { + if (file.hasLocation) { + if (isFileInsideLocationTag( + cityLocation, + file.location!, + defaultCityRadius, + )) { + matchingFiles.add(file); + } + } + } + if (matchingFiles.isNotEmpty) { + results[city] = matchingFiles; + } + } + return results; +} + +bool isFileInsideLocationTag( + Location centerPoint, + Location fileCoordinates, + double radius, +) { + final a = + (radius * _scaleFactor(centerPoint.latitude!)) / kilometersPerDegree; + final b = radius / kilometersPerDegree; + final x = centerPoint.latitude! - fileCoordinates.latitude!; + final y = centerPoint.longitude! - fileCoordinates.longitude!; + if ((x * x) / (a * a) + (y * y) / (b * b) <= 1) { + return true; + } + return false; +} + +///The area bounded by the location tag becomes more elliptical with increase +///in the magnitude of the latitude on the caritesian plane. When latitude is +///0 degrees, the ellipse is a circle with a = b = r. When latitude incrases, +///the major axis (a) has to be scaled by the secant of the latitude. +double _scaleFactor(double lat) { + return 1 / cos(lat * (pi / 180)); +} + class City { final String city; final String country; diff --git a/lib/services/search_service.dart b/lib/services/search_service.dart index 7b831480a..97fb16dbe 100644 --- a/lib/services/search_service.dart +++ b/lib/services/search_service.dart @@ -3,7 +3,6 @@ import "dart:math"; import "package:flutter/cupertino.dart"; import "package:intl/intl.dart"; import 'package:logging/logging.dart'; -import "package:photos/core/constants.dart"; import 'package:photos/core/event_bus.dart'; import 'package:photos/data/holidays.dart'; import 'package:photos/data/months.dart'; @@ -18,7 +17,6 @@ import "package:photos/models/file/extensions/file_props.dart"; import 'package:photos/models/file/file.dart'; import 'package:photos/models/file/file_type.dart'; import "package:photos/models/local_entity_data.dart"; -import "package:photos/models/location/location.dart"; import "package:photos/models/location_tag/location_tag.dart"; import 'package:photos/models/search/album_search_result.dart'; import 'package:photos/models/search/generic_search_result.dart'; @@ -620,7 +618,7 @@ class SearchService { for (EnteFile file in allFiles) { if (file.hasLocation) { for (LocalEntity tag in result.keys) { - if (LocationService.instance.isFileInsideLocationTag( + if (isFileInsideLocationTag( tag.item.centerPoint, file.location!, tag.item.radius, @@ -639,7 +637,7 @@ class SearchService { return false; } for (LocalEntity tag in locationTagEntities) { - if (LocationService.instance.isFileInsideLocationTag( + if (isFileInsideLocationTag( tag.item.centerPoint, file.location!, tag.item.radius, @@ -684,36 +682,9 @@ class SearchService { } Future> getCityResults(String query) async { - final startTime = DateTime.now().microsecondsSinceEpoch; - final List searchResults = []; - final cities = LocationService.instance.getAllCities(); - final matchingCities = []; - final queryLower = query.toLowerCase(); - for (City city in cities) { - if (city.city.toLowerCase().startsWith(queryLower)) { - matchingCities.add(city); - } - } final files = await getAllFiles(); - final Map> results = {}; - for (final city in matchingCities) { - final List matchingFiles = []; - final cityLocation = Location(latitude: city.lat, longitude: city.lng); - for (final file in files) { - if (file.hasLocation) { - if (LocationService.instance.isFileInsideLocationTag( - cityLocation, - file.location!, - defaultCityRadius, - )) { - matchingFiles.add(file); - } - } - } - if (matchingFiles.isNotEmpty) { - results[city] = matchingFiles; - } - } + final results = await LocationService.instance.getFilesInCity(files, query); + final List searchResults = []; for (final entry in results.entries) { searchResults.add( GenericSearchResult( @@ -723,9 +694,6 @@ class SearchService { ), ); } - final endTime = DateTime.now().microsecondsSinceEpoch; - _logger - .info("Time taken " + ((endTime - startTime) / 1000).toString() + "ms"); return searchResults; } @@ -745,7 +713,7 @@ class SearchService { for (EnteFile file in allFiles) { if (file.hasLocation) { for (LocalEntity tag in tagToItemsMap.keys) { - if (LocationService.instance.isFileInsideLocationTag( + if (isFileInsideLocationTag( tag.item.centerPoint, file.location!, tag.item.radius, diff --git a/lib/services/semantic_search/embedding_store.dart b/lib/services/semantic_search/embedding_store.dart index de10fec60..6aedf8547 100644 --- a/lib/services/semantic_search/embedding_store.dart +++ b/lib/services/semantic_search/embedding_store.dart @@ -133,12 +133,12 @@ class EmbeddingStore { return; } final inputs = []; + final fileMap = await FilesDB.instance + .getFilesFromIDs(remoteEmbeddings.map((e) => e.fileID).toList()); + for (final embedding in remoteEmbeddings) { - final file = await FilesDB.instance.getAnyUploadedFile(embedding.fileID); - if (file == null) { - continue; - } - final fileKey = getFileKey(file); + final file = fileMap[embedding.fileID]; + final fileKey = getFileKey(file!); final input = EmbeddingsDecoderInput(embedding, fileKey); inputs.add(input); } diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/semantic_search/semantic_search_service.dart index fed266aef..b233559cf 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/semantic_search/semantic_search_service.dart @@ -13,6 +13,7 @@ import 'package:photos/events/embedding_updated_event.dart'; import "package:photos/events/file_uploaded_event.dart"; import "package:photos/models/embedding.dart"; import "package:photos/models/file/file.dart"; +import "package:photos/services/collections_service.dart"; import "package:photos/services/semantic_search/embedding_store.dart"; import "package:photos/services/semantic_search/frameworks/ggml.dart"; import "package:photos/services/semantic_search/frameworks/ml_framework.dart"; @@ -215,8 +216,12 @@ class SemanticSearchService { final filesMap = await FilesDB.instance .getFilesFromIDs(queryResults.map((e) => e.id).toList()); final results = []; + + final ignoredCollections = + CollectionsService.instance.getHiddenCollectionIds(); for (final result in queryResults) { - if (filesMap.containsKey(result.id)) { + final file = filesMap[result.id]; + if (file != null && !ignoredCollections.contains(file.collectionID)) { results.add(filesMap[result.id]!); } } diff --git a/lib/ui/viewer/location/dynamic_location_gallery_widget.dart b/lib/ui/viewer/location/dynamic_location_gallery_widget.dart index 3103fa143..04dbcd6a6 100644 --- a/lib/ui/viewer/location/dynamic_location_gallery_widget.dart +++ b/lib/ui/viewer/location/dynamic_location_gallery_widget.dart @@ -62,7 +62,7 @@ class _DynamicLocationGalleryWidgetState final stopWatch = Stopwatch()..start(); final copyOfFiles = List.from(result.files); copyOfFiles.removeWhere((f) { - return !LocationService.instance.isFileInsideLocationTag( + return !isFileInsideLocationTag( InheritedLocationTagData.of(context).centerPoint, f.location!, selectedRadius, diff --git a/lib/ui/viewer/location/location_screen.dart b/lib/ui/viewer/location/location_screen.dart index cdfa7f92b..374d70cb9 100644 --- a/lib/ui/viewer/location/location_screen.dart +++ b/lib/ui/viewer/location/location_screen.dart @@ -205,7 +205,7 @@ class _LocationGalleryWidgetState extends State { final stopWatch = Stopwatch()..start(); final filesInLocation = allFilesWithLocation; filesInLocation.removeWhere((f) { - return !LocationService.instance.isFileInsideLocationTag( + return !isFileInsideLocationTag( centerPoint, f.location!, selectedRadius, diff --git a/pubspec.yaml b/pubspec.yaml index 219fb7c85..d38ab111e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.8.32+552 +version: 0.8.33+553 environment: sdk: ">=3.0.0 <4.0.0"