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",
|
"humantime",
|
||||||
"ignore",
|
"ignore",
|
||||||
"jemallocator",
|
"jemallocator",
|
||||||
|
"kamadak-exif",
|
||||||
"libc",
|
"libc",
|
||||||
"lscolors",
|
"lscolors",
|
||||||
"nix 0.27.1",
|
"nix 0.27.1",
|
||||||
|
@ -474,6 +475,15 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"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]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -522,6 +532,12 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mutate_once"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.24.3"
|
version = "0.24.3"
|
||||||
|
|
|
@ -49,6 +49,7 @@ normpath = "1.1.1"
|
||||||
crossbeam-channel = "0.5.8"
|
crossbeam-channel = "0.5.8"
|
||||||
clap_complete = {version = "4.4.4", optional = true}
|
clap_complete = {version = "4.4.4", optional = true}
|
||||||
faccess = "0.2.4"
|
faccess = "0.2.4"
|
||||||
|
kamadak-exif = "0.5.5"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
ignore = { git = "https://github.com/tavianator/ripgrep", branch = "fd" }
|
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::size::SizeFilter;
|
||||||
pub use self::time::TimeFilter;
|
pub use self::time::TimeFilter;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub use self::owner::OwnerFilter;
|
pub use self::owner::OwnerFilter;
|
||||||
|
|
||||||
|
mod geo_location;
|
||||||
mod size;
|
mod size;
|
||||||
mod time;
|
mod time;
|
||||||
|
|
||||||
|
|
14
src/walk.rs
14
src/walk.rs
|
@ -21,6 +21,7 @@ use crate::error::print_error;
|
||||||
use crate::exec;
|
use crate::exec;
|
||||||
use crate::exit_codes::{merge_exitcodes, ExitCode};
|
use crate::exit_codes::{merge_exitcodes, ExitCode};
|
||||||
use crate::filesystem;
|
use crate::filesystem;
|
||||||
|
use crate::filter::{exif_geo_distance, GeoLocation};
|
||||||
use crate::output;
|
use crate::output;
|
||||||
|
|
||||||
/// The receiver thread can either be buffering results or directly streaming to the console.
|
/// 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 config.is_printing() {
|
||||||
if let Some(ls_colors) = &config.ls_colors {
|
if let Some(ls_colors) = &config.ls_colors {
|
||||||
// Compute colors in parallel
|
// Compute colors in parallel
|
||||||
|
|
Loading…
Reference in New Issue