2023-03-07 02:12:54 +00:00
|
|
|
import "dart:collection";
|
|
|
|
import "dart:convert";
|
2023-03-17 04:42:47 +00:00
|
|
|
import "dart:math";
|
2023-03-07 02:12:54 +00:00
|
|
|
|
2023-03-17 04:42:47 +00:00
|
|
|
import "package:photos/core/constants.dart";
|
2023-03-07 02:12:54 +00:00
|
|
|
import "package:shared_preferences/shared_preferences.dart";
|
|
|
|
|
|
|
|
class LocationService {
|
|
|
|
SharedPreferences? prefs;
|
|
|
|
LocationService._privateConstructor();
|
|
|
|
|
|
|
|
static final LocationService instance = LocationService._privateConstructor();
|
|
|
|
|
|
|
|
Future<void> init() async {
|
|
|
|
prefs ??= await SharedPreferences.getInstance();
|
|
|
|
}
|
|
|
|
|
2023-03-17 13:51:53 +00:00
|
|
|
List<String> getAllLocationTags() {
|
2023-03-07 02:12:54 +00:00
|
|
|
var list = prefs!.getStringList('locations');
|
|
|
|
list ??= [];
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> addLocation(
|
|
|
|
String location,
|
|
|
|
double lat,
|
2023-03-17 04:42:47 +00:00
|
|
|
double long,
|
2023-03-07 02:12:54 +00:00
|
|
|
int radius,
|
|
|
|
) async {
|
2023-03-17 13:51:53 +00:00
|
|
|
final list = getAllLocationTags();
|
2023-03-17 04:42:47 +00:00
|
|
|
//The area enclosed by the location tag will be a circle on a 3D spherical
|
|
|
|
//globe and an ellipse on a 2D Mercator projection (2D map)
|
|
|
|
//a & b are the semi-major and semi-minor axes of the ellipse
|
|
|
|
//Converting the unit from kilometers to degrees for a and b as that is
|
|
|
|
//the unit on the caritesian plane
|
|
|
|
final a = (radius * _scaleFactor(lat)) / kilometersPerDegree;
|
|
|
|
final b = radius / kilometersPerDegree;
|
|
|
|
final center = [lat, long];
|
2023-03-07 02:12:54 +00:00
|
|
|
final data = {
|
|
|
|
"name": location,
|
|
|
|
"radius": radius,
|
2023-03-17 04:42:47 +00:00
|
|
|
"a": a,
|
|
|
|
"b": b,
|
|
|
|
"center": center,
|
2023-03-07 02:12:54 +00:00
|
|
|
};
|
|
|
|
final encodedMap = json.encode(data);
|
|
|
|
list.add(encodedMap);
|
|
|
|
await prefs!.setStringList('locations', list);
|
|
|
|
}
|
|
|
|
|
2023-03-17 04:42:47 +00:00
|
|
|
///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));
|
|
|
|
}
|
|
|
|
|
2023-03-17 13:51:53 +00:00
|
|
|
List<String> enclosingLocationTags(List<double> coordinates) {
|
|
|
|
final result = List<String>.of([]);
|
|
|
|
final allLocationTags = getAllLocationTags();
|
|
|
|
for (var locationTag in allLocationTags) {
|
|
|
|
final locationJson = json.decode(locationTag);
|
|
|
|
final a = locationJson["a"];
|
|
|
|
final b = locationJson["b"];
|
|
|
|
final center = locationJson["center"];
|
|
|
|
final x = coordinates[0] - center[0];
|
|
|
|
final y = coordinates[1] - center[1];
|
|
|
|
if ((x * x) / (a * a) + (y * y) / (b * b) <= 1) {
|
|
|
|
result.add(locationJson["name"]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-03-07 02:12:54 +00:00
|
|
|
Future<void> addFileToLocation(int locationId, int fileId) async {
|
|
|
|
final list = getFilesByLocation(locationId.toString());
|
|
|
|
list.add(fileId.toString());
|
|
|
|
await prefs!.setStringList("location_$locationId", list);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<String> getFilesByLocation(String locationId) {
|
|
|
|
var fileList = prefs!.getStringList("location_$locationId");
|
|
|
|
fileList ??= [];
|
|
|
|
return fileList;
|
|
|
|
}
|
|
|
|
|
|
|
|
List<String> getLocationsByFileID(int fileId) {
|
2023-03-17 13:51:53 +00:00
|
|
|
final locationList = getAllLocationTags();
|
2023-03-07 02:12:54 +00:00
|
|
|
final locations = List<dynamic>.of([]);
|
|
|
|
for (String locationString in locationList) {
|
|
|
|
final locationJson = json.decode(locationString);
|
|
|
|
locations.add(locationJson);
|
|
|
|
}
|
|
|
|
final res = List<String>.of([]);
|
|
|
|
for (dynamic location in locations) {
|
|
|
|
final list = getFilesByLocation(location["id"].toString());
|
|
|
|
if (list.contains(fileId.toString())) {
|
|
|
|
res.add(location["name"]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
Map<String, List<String>> clusterFilesByLocation() {
|
|
|
|
final map = HashMap<String, List<String>>();
|
|
|
|
var locations = prefs!.getStringList('locations');
|
|
|
|
locations ??= [];
|
|
|
|
for (String locationData in locations) {
|
|
|
|
final locationJson = json.decode(locationData);
|
|
|
|
map.putIfAbsent(
|
|
|
|
locationData,
|
|
|
|
() => getFilesByLocation(locationJson['id'].toString()),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
}
|
2023-03-17 10:53:22 +00:00
|
|
|
|
|
|
|
class GPSData {
|
|
|
|
String latRef;
|
|
|
|
List<double> lat;
|
|
|
|
String longRef;
|
|
|
|
List<double> long;
|
|
|
|
|
|
|
|
GPSData(this.latRef, this.lat, this.longRef, this.long);
|
2023-03-17 12:00:39 +00:00
|
|
|
|
|
|
|
List<double> toSignedDecimalDegreeCoordinates() {
|
|
|
|
final latSign = latRef == "N" ? 1 : -1;
|
|
|
|
final longSign = longRef == "E" ? 1 : -1;
|
|
|
|
return [
|
|
|
|
latSign * lat[0] + lat[1] / 60 + lat[2] / 3600,
|
|
|
|
longSign * long[0] + long[1] / 60 + long[2] / 3600
|
|
|
|
];
|
|
|
|
}
|
2023-03-17 10:53:22 +00:00
|
|
|
}
|