sn0int-std 0.26.0

sn0int - stdlib
Documentation
use crate::errors::*;
use serde::{Deserialize, Serialize};
use std::io;

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Location {
    latitude: f64,
    longitude: f64,
}

impl Location {
    fn try_from_iter<'a, I: IntoIterator<Item = &'a exif::Field>>(iter: I) -> Result<Self> {
        let mut builder = LocationBuilder::default();
        for f in iter {
            debug!("Exif field: {:?}", f.display_value().to_string());
            builder.add_one(f)?;
        }
        builder.build()
    }
}

#[derive(Debug, Default)]
struct LocationBuilder {
    latitude: Option<f64>,
    latitude_ref: Option<f64>,
    longitude: Option<f64>,
    longitude_ref: Option<f64>,
}

impl LocationBuilder {
    fn add_one(&mut self, f: &exif::Field) -> Result<()> {
        debug!("Exif tag: {:?}, {}", f.tag, f.value.display_as(f.tag));
        match f.tag {
            exif::Tag::GPSLatitudeRef => {
                self.latitude_ref = Some(cardinal_direction_modifier(&f.value)?)
            }
            exif::Tag::GPSLongitudeRef => {
                self.longitude_ref = Some(cardinal_direction_modifier(&f.value)?)
            }
            exif::Tag::GPSLatitude => self.latitude = Some(dms_to_float(&f.value)?),
            exif::Tag::GPSLongitude => self.longitude = Some(dms_to_float(&f.value)?),
            _ => (),
        };
        Ok(())
    }

    fn build(self) -> Result<Location> {
        let latitude = self
            .latitude
            .ok_or_else(|| format_err!("Missing latitude field"))?;
        let latitude_ref = self
            .latitude_ref
            .ok_or_else(|| format_err!("Missing latitude field"))?;

        let longitude = self
            .longitude
            .ok_or_else(|| format_err!("Missing latitude field"))?;
        let longitude_ref = self
            .longitude_ref
            .ok_or_else(|| format_err!("Missing latitude field"))?;

        Ok(Location {
            latitude: latitude * latitude_ref,
            longitude: longitude * longitude_ref,
        })
    }
}

pub fn gps(img: &[u8]) -> Result<Option<Location>> {
    let mut buf = io::Cursor::new(img);
    let reader = exif::Reader::new().read_from_container(&mut buf)?;
    let fields = reader.fields();

    let location = Location::try_from_iter(fields).ok();
    Ok(location)
}

pub fn dms_to_float(dms: &exif::Value) -> Result<f64> {
    let dms = match dms {
        exif::Value::Rational(dms) => dms,
        _ => panic!("Unexpected exif value for dms"),
    };

    if dms.len() != 3 {
        bail!("Incorrect numbers for floats for dms");
    }

    let degrees = dms[0].to_f64();
    let minutes = dms[1].to_f64();
    let seconds = dms[2].to_f64();

    let float = degrees + minutes / 60.0 + seconds / 3600.0;
    let float = (float * 1000000.0).round() / 1000000.0;
    Ok(float)
}

pub fn cardinal_direction_modifier(value: &exif::Value) -> Result<f64> {
    match value {
        exif::Value::Ascii(s) => {
            let s = s
                .get(0)
                .ok_or_else(|| format_err!("Cardinal direction value is empty"))?;

            match s.first() {
                Some(b'N') => Ok(1.0),
                Some(b'S') => Ok(-1.0),
                Some(b'E') => Ok(1.0),
                Some(b'W') => Ok(-1.0),
                _ => bail!("Unexpected cardinal direction"),
            }
        }
        _ => bail!("Unexpected exif value"),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::test_init;

    #[test]
    fn verify_exif_location() {
        test_init();

        let location = Location::try_from_iter(&[
            exif::Field {
                tag: exif::Tag::GPSLatitudeRef,
                ifd_num: exif::In::PRIMARY,
                value: exif::Value::Ascii(vec![vec![b'N']]),
            },
            exif::Field {
                tag: exif::Tag::GPSLongitudeRef,
                ifd_num: exif::In::PRIMARY,
                value: exif::Value::Ascii(vec![vec![b'E']]),
            },
            exif::Field {
                tag: exif::Tag::GPSLatitude,
                ifd_num: exif::In::PRIMARY,
                value: exif::Value::Rational(vec![
                    exif::Rational { num: 43, denom: 1 },
                    exif::Rational { num: 28, denom: 1 },
                    exif::Rational {
                        num: 176399999,
                        denom: 100000000,
                    },
                ]),
            },
            exif::Field {
                tag: exif::Tag::GPSLongitude,
                ifd_num: exif::In::PRIMARY,
                value: exif::Value::Rational(vec![
                    exif::Rational { num: 11, denom: 1 },
                    exif::Rational { num: 53, denom: 1 },
                    exif::Rational {
                        num: 742199999,
                        denom: 100000000,
                    },
                ]),
            },
        ])
        .unwrap();
        println!("{:?}", location);

        assert_eq!(
            location,
            Location {
                latitude: 43.467157,
                longitude: 11.885395
            }
        );
    }

    #[test]
    fn verify_dms() {
        test_init();

        let latitude = dms_to_float(&exif::Value::Rational(vec![
            exif::Rational { num: 43, denom: 1 },
            exif::Rational { num: 28, denom: 1 },
            exif::Rational {
                num: 176399999,
                denom: 100000000,
            },
        ]))
        .unwrap();

        assert_eq!(latitude, 43.467157);
    }
}