Fixes to search (#1639)
This commit is contained in:
commit
5bbf227344
7 changed files with 111 additions and 80 deletions
|
@ -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<City> _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<City> _cities = [];
|
||||
|
||||
void init(SharedPreferences preferences) {
|
||||
prefs = preferences;
|
||||
if (FeatureFlagService.instance.isInternalUserOrDebugBuild()) {
|
||||
|
@ -39,8 +44,19 @@ class LocationService {
|
|||
);
|
||||
}
|
||||
|
||||
List<City> getAllCities() {
|
||||
return _cities;
|
||||
Future<Map<City, List<EnteFile>>> getFilesInCity(
|
||||
List<EnteFile> allFiles,
|
||||
String query,
|
||||
) async {
|
||||
final result = await _computer.compute(
|
||||
getCityResults,
|
||||
param: {
|
||||
"query": query,
|
||||
"cities": _cities,
|
||||
"files": allFiles,
|
||||
},
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<Iterable<LocalEntity<LocationTag>>> 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<List<LocalEntity<LocationTag>>> 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<String>? convertLocationToDMS(Location centerPoint) {
|
||||
if (centerPoint.latitude == null || centerPoint.longitude == null) {
|
||||
|
@ -218,14 +210,15 @@ class LocationService {
|
|||
|
||||
Future<void> _loadCities() async {
|
||||
try {
|
||||
final data =
|
||||
final file =
|
||||
await RemoteAssetsService.instance.getAsset(kCitiesRemotePath);
|
||||
final citiesJson = json.decode(await data.readAsString());
|
||||
final List<dynamic> jsonData = citiesJson['data'];
|
||||
final cities =
|
||||
jsonData.map<City>((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<List<City>> parseCities(Map args) async {
|
||||
final file = File(args["filePath"]);
|
||||
final citiesJson = json.decode(await file.readAsString());
|
||||
|
||||
final List<dynamic> jsonData = citiesJson['data'];
|
||||
final cities =
|
||||
jsonData.map<City>((jsonItem) => City.fromMap(jsonItem)).toList();
|
||||
return cities;
|
||||
}
|
||||
|
||||
Map<City, List<EnteFile>> getCityResults(Map args) {
|
||||
final query = (args["query"] as String).toLowerCase();
|
||||
final cities = args["cities"] as List<City>;
|
||||
final files = args["files"] as List<EnteFile>;
|
||||
|
||||
final matchingCities = cities.where(
|
||||
(city) => city.city.toLowerCase().contains(query),
|
||||
);
|
||||
|
||||
final Map<City, List<EnteFile>> results = {};
|
||||
for (final city in matchingCities) {
|
||||
final List<EnteFile> 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;
|
||||
|
|
|
@ -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<LocationTag> 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<LocationTag> tag in locationTagEntities) {
|
||||
if (LocationService.instance.isFileInsideLocationTag(
|
||||
if (isFileInsideLocationTag(
|
||||
tag.item.centerPoint,
|
||||
file.location!,
|
||||
tag.item.radius,
|
||||
|
@ -684,36 +682,9 @@ class SearchService {
|
|||
}
|
||||
|
||||
Future<List<GenericSearchResult>> getCityResults(String query) async {
|
||||
final startTime = DateTime.now().microsecondsSinceEpoch;
|
||||
final List<GenericSearchResult> searchResults = [];
|
||||
final cities = LocationService.instance.getAllCities();
|
||||
final matchingCities = <City>[];
|
||||
final queryLower = query.toLowerCase();
|
||||
for (City city in cities) {
|
||||
if (city.city.toLowerCase().startsWith(queryLower)) {
|
||||
matchingCities.add(city);
|
||||
}
|
||||
}
|
||||
final files = await getAllFiles();
|
||||
final Map<City, List<EnteFile>> results = {};
|
||||
for (final city in matchingCities) {
|
||||
final List<EnteFile> 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<GenericSearchResult> 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<LocationTag> tag in tagToItemsMap.keys) {
|
||||
if (LocationService.instance.isFileInsideLocationTag(
|
||||
if (isFileInsideLocationTag(
|
||||
tag.item.centerPoint,
|
||||
file.location!,
|
||||
tag.item.radius,
|
||||
|
|
|
@ -133,12 +133,12 @@ class EmbeddingStore {
|
|||
return;
|
||||
}
|
||||
final inputs = <EmbeddingsDecoderInput>[];
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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 = <EnteFile>[];
|
||||
|
||||
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]!);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class _DynamicLocationGalleryWidgetState
|
|||
final stopWatch = Stopwatch()..start();
|
||||
final copyOfFiles = List<EnteFile>.from(result.files);
|
||||
copyOfFiles.removeWhere((f) {
|
||||
return !LocationService.instance.isFileInsideLocationTag(
|
||||
return !isFileInsideLocationTag(
|
||||
InheritedLocationTagData.of(context).centerPoint,
|
||||
f.location!,
|
||||
selectedRadius,
|
||||
|
|
|
@ -205,7 +205,7 @@ class _LocationGalleryWidgetState extends State<LocationGalleryWidget> {
|
|||
final stopWatch = Stopwatch()..start();
|
||||
final filesInLocation = allFilesWithLocation;
|
||||
filesInLocation.removeWhere((f) {
|
||||
return !LocationService.instance.isFileInsideLocationTag(
|
||||
return !isFileInsideLocationTag(
|
||||
centerPoint,
|
||||
f.location!,
|
||||
selectedRadius,
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue