mirror of https://github.com/sharkdp/fd.git
Filter by EXIF GPS location
This commit is contained in:
parent
d62bbbbcd1
commit
f9f4e89caa
|
@ -331,6 +331,7 @@ dependencies = [
|
|||
"humantime",
|
||||
"ignore",
|
||||
"jemallocator",
|
||||
"kamadak-exif",
|
||||
"libc",
|
||||
"lscolors",
|
||||
"nix 0.27.1",
|
||||
|
@ -474,6 +475,15 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kamadak-exif"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077"
|
||||
dependencies = [
|
||||
"mutate_once",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
|
@ -522,6 +532,12 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mutate_once"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.24.3"
|
||||
|
|
|
@ -49,6 +49,7 @@ normpath = "1.1.1"
|
|||
crossbeam-channel = "0.5.8"
|
||||
clap_complete = {version = "4.4.4", optional = true}
|
||||
faccess = "0.2.4"
|
||||
kamadak-exif = "0.5.5"
|
||||
|
||||
[patch.crates-io]
|
||||
ignore = { git = "https://github.com/tavianator/ripgrep", branch = "fd" }
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
use std::{
|
||||
f32::consts::PI,
|
||||
fmt::{Display, Formatter},
|
||||
fs::File,
|
||||
io::BufReader,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use exif::{Exif, In, Reader, Tag};
|
||||
use regex::Regex;
|
||||
|
||||
/// Struct representing a geo location
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct GeoLocation {
|
||||
pub latitude: f32,
|
||||
pub longitude: f32,
|
||||
}
|
||||
|
||||
/// Display trait implementation for GeoLocation
|
||||
impl Display for GeoLocation {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[lat={} lon={}]", self.latitude, self.longitude,)
|
||||
}
|
||||
}
|
||||
|
||||
impl GeoLocation {
|
||||
///Computes the distance in meters using the Haversine formula
|
||||
pub fn distance_to(&self, other: &GeoLocation) -> f32 {
|
||||
let r = 6378137.; // radius of earth in meters
|
||||
let d_lat = other.latitude * PI / 180. - self.latitude * PI / 180.;
|
||||
let d_lon = other.longitude * PI / 180. - self.longitude * PI / 180.;
|
||||
let a = f32::sin(d_lat / 2.) * f32::sin(d_lat / 2.)
|
||||
+ f32::cos(self.latitude * PI / 180.)
|
||||
* f32::cos(other.latitude * PI / 180.)
|
||||
* f32::sin(d_lon / 2.)
|
||||
* f32::sin(d_lon / 2.);
|
||||
let c = 2. * f32::atan2(f32::sqrt(a), f32::sqrt(1. - a));
|
||||
return r * c;
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts Degrees Minutes Seconds To Decimal Degrees
|
||||
fn dms_to_dd(dms_string: &str, dms_ref: &str) -> Option<f32> {
|
||||
// Depending on the dms ref the value has to be multiplied by -1
|
||||
let dms_ref_multiplier = match dms_ref {
|
||||
"S" | "W" => -1.0,
|
||||
_ => 1.0,
|
||||
};
|
||||
|
||||
let dms_parse_pattern: Regex = Regex::new(
|
||||
// e.g.: 7 deg 33 min 55.5155 sec or 7 deg 33 min 55 sec
|
||||
r"(?P<deg>\d+\.?\d*) deg (?P<min>\d+) min (?P<sec>\d+\.?\d*) sec",
|
||||
)
|
||||
.unwrap();
|
||||
let Some(pattern_match) = dms_parse_pattern.captures(dms_string) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Some(deg) = pattern_match
|
||||
.name("deg")
|
||||
.map(|cap| cap.as_str().parse::<f32>().unwrap())
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let Some(min) = pattern_match
|
||||
.name("min")
|
||||
.map(|cap| cap.as_str().parse::<f32>().unwrap())
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let Some(sec) = pattern_match
|
||||
.name("sec")
|
||||
.map(|cap| cap.as_str().parse::<f32>().unwrap())
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(dms_ref_multiplier * (deg + (min / 60.0) + (sec / 3600.0)))
|
||||
}
|
||||
|
||||
impl GeoLocation {
|
||||
/// Detects the location from the exif data
|
||||
/// If the location is not found, the location is set to None
|
||||
fn from_exif(exif_data: &Exif) -> Option<GeoLocation> {
|
||||
let Some(latitude) = exif_data.get_field(Tag::GPSLatitude, In::PRIMARY) else {
|
||||
return None;
|
||||
};
|
||||
let Some(latitude_ref) = exif_data.get_field(Tag::GPSLatitudeRef, In::PRIMARY) else {
|
||||
return None;
|
||||
};
|
||||
let Some(longitude) = exif_data.get_field(Tag::GPSLongitude, In::PRIMARY) else {
|
||||
return None;
|
||||
};
|
||||
let Some(longitude_ref) = exif_data.get_field(Tag::GPSLongitudeRef, In::PRIMARY) else {
|
||||
return None;
|
||||
};
|
||||
let Some(dd_lat) = dms_to_dd(
|
||||
&latitude.display_value().to_string(),
|
||||
&latitude_ref.display_value().to_string(),
|
||||
) else {
|
||||
return None;
|
||||
};
|
||||
let Some(dd_lon) = dms_to_dd(
|
||||
&longitude.display_value().to_string(),
|
||||
&longitude_ref.display_value().to_string(),
|
||||
) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(GeoLocation {
|
||||
latitude: dd_lat,
|
||||
longitude: dd_lon,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exif_geo_distance(path: &Path, reference: &GeoLocation) -> Option<f32> {
|
||||
let Ok(file) = File::open(path) else {
|
||||
return None;
|
||||
};
|
||||
let Ok(exif) = Reader::new().read_from_container(&mut BufReader::new(&file)) else {
|
||||
return None;
|
||||
};
|
||||
let Some(location) = GeoLocation::from_exif(&exif) else {
|
||||
return None;
|
||||
};
|
||||
let distance_meter = location.distance_to(&reference);
|
||||
return Some(distance_meter);
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
pub use self::geo_location::{exif_geo_distance, GeoLocation};
|
||||
pub use self::size::SizeFilter;
|
||||
pub use self::time::TimeFilter;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub use self::owner::OwnerFilter;
|
||||
|
||||
mod geo_location;
|
||||
mod size;
|
||||
mod time;
|
||||
|
||||
|
|
14
src/walk.rs
14
src/walk.rs
|
@ -21,6 +21,7 @@ use crate::error::print_error;
|
|||
use crate::exec;
|
||||
use crate::exit_codes::{merge_exitcodes, ExitCode};
|
||||
use crate::filesystem;
|
||||
use crate::filter::{exif_geo_distance, GeoLocation};
|
||||
use crate::output;
|
||||
|
||||
/// The receiver thread can either be buffering results or directly streaming to the console.
|
||||
|
@ -502,6 +503,19 @@ impl WorkerState {
|
|||
}
|
||||
}
|
||||
|
||||
let reference = GeoLocation {
|
||||
latitude: 47.3,
|
||||
longitude: 11.3,
|
||||
};
|
||||
let max_distance = 500.;
|
||||
if let Some(distance) = exif_geo_distance(entry.path(), &reference) {
|
||||
if distance > max_distance {
|
||||
return WalkState::Continue;
|
||||
}
|
||||
} else {
|
||||
return WalkState::Continue;
|
||||
}
|
||||
|
||||
if config.is_printing() {
|
||||
if let Some(ls_colors) = &config.ls_colors {
|
||||
// Compute colors in parallel
|
||||
|
|
Loading…
Reference in New Issue