No content changes: git diff --ignore-all-space over these files is empty. The churn came from editing on Windows against a repo checked out with LF.
86 lines
2.7 KiB
Dart
86 lines
2.7 KiB
Dart
import 'dart:io';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:exif/exif.dart';
|
|
|
|
class ExifHelper {
|
|
/// Extracts GPS coordinates (latitude and longitude) from an image file's EXIF metadata.
|
|
/// Returns a map with 'latitude' and 'longitude', or null if not found or invalid.
|
|
static Future<Map<String, double>?> extractGps(String imagePath) async {
|
|
try {
|
|
final file = File(imagePath);
|
|
if (!await file.exists()) {
|
|
debugPrint('EXIF extract: File does not exist at $imagePath');
|
|
return null;
|
|
}
|
|
|
|
final bytes = await file.readAsBytes();
|
|
final tags = await readExifFromBytes(bytes);
|
|
|
|
if (tags.isEmpty) {
|
|
debugPrint('EXIF extract: No EXIF tags found in image');
|
|
return null;
|
|
}
|
|
|
|
final latTag = tags['GPS GPSLatitude'];
|
|
final latRefTag = tags['GPS GPSLatitudeRef'];
|
|
final lngTag = tags['GPS GPSLongitude'];
|
|
final lngRefTag = tags['GPS GPSLongitudeRef'];
|
|
|
|
if (latTag == null || latRefTag == null || lngTag == null || lngRefTag == null) {
|
|
debugPrint('EXIF extract: GPS tags missing in EXIF metadata');
|
|
return null;
|
|
}
|
|
|
|
final double lat = _convertRatioListToDouble(latTag.values.toList());
|
|
final double lng = _convertRatioListToDouble(lngTag.values.toList());
|
|
|
|
final latRef = latRefTag.toString().trim().toUpperCase();
|
|
final lngRef = lngRefTag.toString().trim().toUpperCase();
|
|
|
|
double finalLat = lat;
|
|
if (latRef == 'S') finalLat = -finalLat;
|
|
|
|
double finalLng = lng;
|
|
if (lngRef == 'W') finalLng = -finalLng;
|
|
|
|
debugPrint('EXIF extract success: parsed coordinates Lat: $finalLat, Lng: $finalLng');
|
|
return {
|
|
'latitude': finalLat,
|
|
'longitude': finalLng,
|
|
};
|
|
} catch (e) {
|
|
debugPrint('Error parsing EXIF metadata: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static double _convertRatioListToDouble(dynamic values) {
|
|
if (values == null || values.length < 3) return 0.0;
|
|
|
|
double degrees = _ratioToDouble(values[0]);
|
|
double minutes = _ratioToDouble(values[1]);
|
|
double seconds = _ratioToDouble(values[2]);
|
|
|
|
return degrees + (minutes / 60.0) + (seconds / 3600.0);
|
|
}
|
|
|
|
static double _ratioToDouble(dynamic val) {
|
|
if (val is Ratio) {
|
|
return val.numerator / val.denominator;
|
|
}
|
|
if (val is num) {
|
|
return val.toDouble();
|
|
}
|
|
final str = val.toString().trim();
|
|
if (str.contains('/')) {
|
|
final parts = str.split('/');
|
|
if (parts.length == 2) {
|
|
final numVal = double.tryParse(parts[0]) ?? 0.0;
|
|
final denVal = double.tryParse(parts[1]) ?? 1.0;
|
|
return numVal / denVal;
|
|
}
|
|
}
|
|
return double.tryParse(str) ?? 0.0;
|
|
}
|
|
}
|