aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOskari Timperi <oskari.timperi@iki.fi>2018-11-29 21:54:09 -0800
committerOskari Timperi <oskari.timperi@iki.fi>2018-11-29 21:54:09 -0800
commit984219aee20204d05a2ffa65cc912d1cda400d79 (patch)
tree83c2f83608e5c5a5bfabc1a357620ec5f538dc1f
downloadplag-984219aee20204d05a2ffa65cc912d1cda400d79.tar.gz
plag-984219aee20204d05a2ffa65cc912d1cda400d79.zip
Initial commit
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock196
-rw-r--r--Cargo.toml11
-rw-r--r--src/main.rs168
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();
+ }
+}