diff options
| author | Oskari Timperi <oskari.timperi@iki.fi> | 2018-11-29 21:54:09 -0800 |
|---|---|---|
| committer | Oskari Timperi <oskari.timperi@iki.fi> | 2018-11-29 21:54:09 -0800 |
| commit | 984219aee20204d05a2ffa65cc912d1cda400d79 (patch) | |
| tree | 83c2f83608e5c5a5bfabc1a357620ec5f538dc1f | |
| download | plag-984219aee20204d05a2ffa65cc912d1cda400d79.tar.gz plag-984219aee20204d05a2ffa65cc912d1cda400d79.zip | |
Initial commit
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Cargo.lock | 196 | ||||
| -rw-r--r-- | Cargo.toml | 11 | ||||
| -rw-r--r-- | src/main.rs | 168 |
4 files changed, 377 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ac9b70d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,196 @@ +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "geo-types" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "geojson" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "geo-types 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "kamadak-exif" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.44" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num-traits" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "plag" +version = "0.1.0" +dependencies = [ + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "geo-types 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "geojson 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kamadak-exif 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ryu" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde_json" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum geo-types 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cf32d4e214224ef12bb688d0e35a74a7ce24653678cbee180df6c0a214a84512" +"checksum geojson 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba15ed82ca7885866c6257f6d91f57aeab25ca3dcb7d93204a6d4aa02ecf83ae" +"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" +"checksum kamadak-exif 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c316dd4d5adad2fa96d1db9a5983ff2bb618de926d6f9b20e117901541f97ad" +"checksum libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)" = "10923947f84a519a45c8fefb7dd1b3e8c08747993381adee176d7a82b4195311" +"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" +"checksum redox_syscall 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "679da7508e9a6390aeaf7fbd02a800fdc64b73fe2204dd2c8ae66d22d9d5ad5d" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" +"checksum serde 1.0.80 (registry+https://github.com/rust-lang/crates.io-index)" = "15c141fc7027dd265a47c090bf864cf62b42c4d228bbcf4e51a0c9e2b0d3f7ef" +"checksum serde_json 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)" = "c37ccd6be3ed1fdf419ee848f7c758eb31b054d7cd3ae3600e3bae0adf569811" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..55b6c92 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "plag" +version = "0.1.0" +authors = ["Oskari Timperi <oskari.timperi@iki.fi>"] + +[dependencies] +geojson = "0.13.0" +geo-types = "0.2.2" +kamadak-exif = "0.3.1" +serde_json = "1.0.33" +clap = "2.32.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..032182f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,168 @@ +extern crate geojson; +extern crate geo_types; +extern crate exif; +extern crate serde_json; +extern crate clap; + +use std::path::Path; + +use geojson::{Feature, GeoJson, Geometry, Value, FeatureCollection}; + +#[derive(Debug)] +enum Error { + IoError(std::io::Error), + Utf8Error(std::str::Utf8Error), + FieldMissing(exif::Tag), + InvalidField(&'static str), + ExifError(exif::Error), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Error::IoError(error) => write!(f, "{}", error), + Error::Utf8Error(error) => write!(f, "{}", error), + Error::FieldMissing(tag) => write!(f, "{}", tag), + Error::InvalidField(msg) => write!(f, "invalid field: {}", msg), + Error::ExifError(error) => write!(f, "{}", error), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::IoError(e) => Some(e), + Error::Utf8Error(e) => Some(e), + Error::ExifError(e) => Some(e), + _ => None, + } + } +} + +impl From<std::str::Utf8Error> for Error { + fn from(value: std::str::Utf8Error) -> Error { + Error::Utf8Error(value) + } +} + +impl From<std::io::Error> for Error { + fn from(value: std::io::Error) -> Error { + Error::IoError(value) + } +} + +impl From<exif::Error> for Error { + fn from(value: exif::Error) -> Error { + Error::ExifError(value) + } +} + +type Result<T> = std::result::Result<T, Error>; + +fn get_degrees(reader: &exif::Reader, tag: exif::Tag) -> Result<f64> { + let field = reader.get_field(tag, false).ok_or(Error::FieldMissing(tag))?; + + match field.value { + exif::Value::Rational(ref dms) => { + if dms.len() != 3 { + return Err(Error::InvalidField("expected 3 rationals")) + } + let degrees = dms[0].to_f64(); + let min = dms[1].to_f64(); + let sec = dms[2].to_f64(); + Ok(degrees + min/60.0 + sec/3600.0) + }, + _ => Err(Error::InvalidField("invalid field type")) + } +} + +fn get_string(reader: &exif::Reader, tag: exif::Tag) -> Result<&str> { + let field = reader.get_field(tag, false).ok_or(Error::FieldMissing(tag))?; + if let exif::Value::Ascii(ref s) = field.value { + let s = s[0]; + std::str::from_utf8(s).map_err(|err| err.into()) + } else { + Err(Error::InvalidField("field is not a string")) + } +} + +fn get_latitude(reader: &exif::Reader) -> Result<f64> { + let mut latitude = get_degrees(reader, exif::Tag::GPSLatitude)?; + let ref_ = get_string(reader, exif::Tag::GPSLatitudeRef)?; + if ref_.ends_with("S") { + latitude = -latitude; + } + Ok(latitude) +} + +fn get_longitude(reader: &exif::Reader) -> Result<f64> { + let mut longitude = get_degrees(reader, exif::Tag::GPSLongitude)?; + let ref_ = get_string(reader, exif::Tag::GPSLongitudeRef)?; + if ref_.ends_with("W") { + longitude = -longitude; + } + Ok(longitude) +} + +fn get_coordinates<P: AsRef<Path> + ?Sized>(filename: &P) -> Result<geo_types::Point<f64>> { + let file = std::fs::File::open(filename)?; + let reader = exif::Reader::new(&mut std::io::BufReader::new(&file))?; + let latitude = get_latitude(&reader)?; + let longitude = get_longitude(&reader)?; + Ok((longitude, latitude).into()) +} + +fn main() { + let matches = clap::App::new("plag") + .version("0.1") + .author("Oskari Timperi <oskari.timperi@iki.fi>") + .about("Photo Location As GeoJSON - Extract GPS location from photos to GeoJSON") + .arg(clap::Arg::with_name("pretty") + .long("pretty") + .help("Output human-readable GeoJSON")) + .arg(clap::Arg::with_name("files") + .required(true) + .multiple(true) + .help("A list of photos")) + .get_matches(); + + // "files" is a required argument. Should be quite safe to unwrap. + let files = matches.values_of_os("files").unwrap(); + + let features: Vec<_> = files.into_iter() + .map(|path| (path, get_coordinates(&path))) + .filter_map(|x| { + match x { + (_, Ok(coord)) => Some(coord), + (path, Err(err)) => { + eprintln!("{}: {}", path.to_string_lossy(), err); + None + } + } + }) + .map(|point| { + Feature { + bbox: None, + geometry: Some(Geometry::new(Value::from(&point))), + id: None, + properties: None, + foreign_members: None, + } + }) + .collect(); + + let collection = FeatureCollection { + bbox: None, + features: features, + foreign_members: None, + }; + + let geojson = GeoJson::from(collection); + + if matches.is_present("pretty") { + serde_json::to_writer_pretty(std::io::stdout(), &geojson).unwrap(); + } else { + serde_json::to_writer(std::io::stdout(), &geojson).unwrap(); + } +} |
