import "dart:collection"; import "dart:convert"; import "dart:math"; import "package:photos/core/constants.dart"; import "package:shared_preferences/shared_preferences.dart"; class LocationService { SharedPreferences? prefs; LocationService._privateConstructor(); static final LocationService instance = LocationService._privateConstructor(); Future init() async { prefs ??= await SharedPreferences.getInstance(); } List getAllLocationTags() { var list = prefs!.getStringList('locations'); list ??= []; return list; } Future addLocation( String location, double lat, double long, int radius, ) async { final list = getAllLocationTags(); //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]; final data = { "name": location, "radius": radius, "aSquare": a * a, "bSquare": b * b, "center": center, }; final encodedMap = json.encode(data); list.add(encodedMap); await prefs!.setStringList('locations', list); } ///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)); } List enclosingLocationTags(List coordinates) { final result = List.of([]); final allLocationTags = getAllLocationTags(); for (var locationTag in allLocationTags) { final locationJson = json.decode(locationTag); final aSquare = locationJson["aSquare"]; final bSquare = locationJson["bSquare"]; final center = locationJson["center"]; final x = coordinates[0] - center[0]; final y = coordinates[1] - center[1]; if ((x * x) / (aSquare) + (y * y) / (bSquare) <= 1) { result.add(locationJson["name"]); } } return result; } Future addFileToLocation(int locationId, int fileId) async { final list = getFilesByLocation(locationId.toString()); list.add(fileId.toString()); await prefs!.setStringList("location_$locationId", list); } List getFilesByLocation(String locationId) { var fileList = prefs!.getStringList("location_$locationId"); fileList ??= []; return fileList; } List getLocationsByFileID(int fileId) { final locationList = getAllLocationTags(); final locations = List.of([]); for (String locationString in locationList) { final locationJson = json.decode(locationString); locations.add(locationJson); } final res = List.of([]); for (dynamic location in locations) { final list = getFilesByLocation(location["id"].toString()); if (list.contains(fileId.toString())) { res.add(location["name"]); } } return res; } Map> clusterFilesByLocation() { final map = HashMap>(); 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; } } class GPSData { String latRef; List lat; String longRef; List long; GPSData(this.latRef, this.lat, this.longRef, this.long); List 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 ]; } }