geotz 0.1.1

Look up timezones based on coordinates
//! Look up timezones from coordinates.

use flatgeobuf::{FallibleStreamingIterator as _, FeatureProperties as _};
use geo::Contains as _;
use geozero::ToGeo as _;

/// Errors.
#[derive(thiserror::Error, Debug)]
pub enum Error {
    /// Underlying error from [`flatgeobuf`] library.
    #[error("flatgeobuf: {0}")]
    Flatgeobuf(#[from] flatgeobuf::Error),

    /// Underlying error from [`geozero`] library.
    #[error("geozero: {0}")]
    Geozero(#[from] geozero::error::GeozeroError),
}

/// Looks up all timezones matching a given `long, lat` pair using the provided FlatGeobuf data.
pub fn lookup_with_fgb(fgb: &[u8], [x, y]: [f64; 2]) -> Result<Vec<String>, Error> {
    let point = geo::Point::new(x, y);

    let mut fgb =
        flatgeobuf::FgbReader::open(std::io::Cursor::new(fgb))?.select_bbox(x, y, x, y)?;

    let mut tzs = Vec::with_capacity(fgb.size_hint().0);

    while let Some(feature) = fgb.next().unwrap() {
        let Some(tzid) = match feature.property::<String>("tzid") {
            Ok(tzid) => Ok(Some(tzid)),
            Err(geozero::error::GeozeroError::ColumnNotFound) => Ok(None),
            Err(e) => Err(e),
        }?
        else {
            continue;
        };

        if !feature.to_geo().unwrap().contains(&point) {
            continue;
        }

        tzs.push(tzid);
    }
    Ok(tzs)
}

/// Looks up all timezones matching a given `long, lat` pair using the built-in FlatGeobuf data.
///
/// Generally, this will return either 0 or 1 elements, with the exception of points inside Xinjiang that may return both `Asia/Urumqi` and `Asia/Shanghai`.
#[cfg(feature = "with-data")]
pub fn lookup([x, y]: [f64; 2]) -> Result<Vec<String>, Error> {
    lookup_with_fgb(geotz_data::DATA, [x, y])
}