trailgen-core 0.3.0

Typed trail graph, route scoring, loop search, and geospatial route I/O for adequate-trailgen.
Documentation
use crate::geo::LineString;
use crate::io::kml;
use crate::io::route_file::RouteFile;
use crate::{Result, TrailgenError};
use std::io::{Cursor, Read};

const MAX_KML_BYTES: u64 = 32 * 1024 * 1024;

pub fn route_line_from_bytes(bytes: &[u8]) -> Result<LineString> {
    route_file_from_bytes(bytes).map(|route| route.line)
}

pub fn route_file_from_bytes(bytes: &[u8]) -> Result<RouteFile> {
    let cursor = Cursor::new(bytes);
    let mut archive = zip::ZipArchive::new(cursor)
        .map_err(|e| TrailgenError::InvalidData(format!("invalid KMZ archive: {e}")))?;
    for index in 0..archive.len() {
        let file = archive
            .by_index(index)
            .map_err(|e| TrailgenError::InvalidData(format!("invalid KMZ member: {e}")))?;
        if !file.name().to_ascii_lowercase().ends_with(".kml") {
            continue;
        }
        if file.size() > MAX_KML_BYTES {
            return Err(TrailgenError::InvalidData(format!(
                "KMZ KML member is {} bytes; limit is {MAX_KML_BYTES}",
                file.size()
            )));
        }
        let mut xml = String::new();
        file.take(MAX_KML_BYTES + 1)
            .read_to_string(&mut xml)
            .map_err(|e| TrailgenError::InvalidData(format!("invalid KMZ KML text: {e}")))?;
        if u64::try_from(xml.len()).unwrap_or(u64::MAX) > MAX_KML_BYTES {
            return Err(TrailgenError::InvalidData(format!(
                "KMZ KML member exceeds {MAX_KML_BYTES} byte limit"
            )));
        }
        return kml::route_file_from_str(&xml);
    }
    Err(TrailgenError::InvalidData(
        "KMZ archive contains no KML member".to_owned(),
    ))
}