|
@@ -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;
|