// examples/00-transformations.rs

// Using Rust Geodesy to transform geodata.
// Run with:
// cargo run --example 00-transformations

use geodesy::prelude::*;

// Use Anyhow for convenient error handling
fn main() -> anyhow::Result<()> {
    // The context provider is the entry point to all transformation functionality:
    let mut ctx = Minimal::default();
    // The concept of a "context data structure" will be well known to
    // PROJ users, where the context plays a somewhat free-flowing role,
    // and only becomes truly visible in multithreaded cases.
    // In Rust Geodesy, the context plays a much more visible role, as
    // most transformation functionality is implemented directly as
    // methods of the context data structure.

    // We need some coordinates to test the code. The convenience methods
    // `gis` and `geo` produce 2D coordinate tuples and automatically handle
    // conversion of the angular parts to radians, and geographical coordinates
    // in latitude/longitude order, to the GIS convention of longitude/latitude.

    // Here using the GIS convention - longitude before latitude
    let cph = Coor2D::gis(12., 55.); // Copenhagen
    let osl = Coor2D::gis(10., 60.); // Oslo

    // And here using the geodesy/navigation convention - latitude before longitude
    let sth = Coor2D::geo(59., 18.); // Stockholm
    let hel = Coor2D::geo(60., 25.); // Helsinki

    // `gis` and `geo` have a sibling `raw` which generates coordinate tuples
    // from raw numbers, in case your point coordinates are already given in
    // radians.
    let cph_raw = Coor2D::raw(12_f64.to_radians(), 55_f64.to_radians());
    // But since a coordinate tuple is really just an array of double
    // precision numbers, you may also generate it directly using plain
    // Rust syntax. Note that Coor2D, like f64, provides the to_radians
    // method, but it operates in place, so we need two steps in this case.
    let cph_direct = Coor2D([12., 55.]).to_radians();
    // The three versions of Copenhagen coordinates should be identical.
    assert_eq!(cph, cph_raw);
    assert_eq!(cph, cph_direct);

    // The Rust Geodesy interface is based on transformation of *arrays* of
    // coordinate tuples, rather than single points. So let's make an array:
    let mut data = [osl, cph, sth, hel];
    // Since all operations are carried out in place, the array needs to
    // be mutable, hence `let mut`.

    // Note that on the inside, Rust Geodesy operates in 4 dimensions, but
    // externally you may use whatever fits the problem at hand. Here, we
    // use the two-dimensional Coor2D, since we're doing a plain projection
    // from 2D geographical coordinates (latitude, longitude) to 2D projected
    // coordinates (northing, easting)

    // Let's obtain a handle to a transformation element ("an operation"),
    // turning geographical coordinates into UTM zone 32 coordinates.
    // Since this may go wrong (e.g. due to syntax errors in the operator
    // definition), use the Rust `?`-operator to handle errors.
    let utm32 = ctx.op("utm zone=32")?;
    // Now, let's use the utm32-operator to transform some coordinate data.
    // The data are transformed in place, so we pass them by mutable reference.
    // The `Fwd` constant indicates that the operation runs in the forward
    // direction
    ctx.apply(utm32, Fwd, &mut data)?;
    println!("utm32:");
    println!("    {:#?}", data);
    for coord in data {
        println!("    {:?}", coord);
    }

    // Specifying `Inv` rather than `Fwd` takes us on the inverse trip back
    // to geographic coordinates
    ctx.apply(utm32, Inv, &mut data)?;
    println!("Roundtrip to geo:");
    // Note the use of `to_geo`, which transforms lon/lat in radians
    // to lat/lon in degrees. It is defined for Coor2D as well as for
    // arrays, vectors and slices of Coor2D
    for coord in data {
        println!("    {:?}", coord.to_geo());
    }

    // To geo again, but using slices - in two different ways
    println!("Sliced to_geo:");
    let mut data = [osl, cph, sth, hel];
    let slice = &mut data[..2];
    for coord in slice {
        println!("    {:?}", coord.to_geo());
    }
    for coord in (&mut data[2..]).iter_mut() {
        println!("    {:?}", coord.to_geo());
    }

    // ctx.apply(...) with slices (a step towards parallel operation)
    println!("Sliced utm32:");
    let mut data = [osl, cph, sth, hel];
    let slice = &mut data[..];
    let (mut first, mut last) = slice.split_at_mut(2);
    ctx.apply(utm32, Fwd, &mut first)?;
    ctx.apply(utm32, Fwd, &mut last)?;
    for coord in data {
        println!("    {:?}", coord);
    }

    // Now a slightly more complex case: Transforming the coordinates,
    // which we consider given in WGS84, back to the older ED50 datum.
    // The EPSG:1134 method handles that through a 3 parameter Helmert
    // transformation. But since the Helmert transformation works on
    // cartesian coordinates, rather than geographic, we need to add
    // pre- and post-processing steps, taking us from geographical
    // coordinates to cartesian, and back. Hence, we need a pipeline
    // of 3 steps:
    let pipeline = "cart ellps=intl | helmert x=-87 y=-96 z=-120 | cart inv=true ellps=GRS80";

    // Here we're doing a 3D operation, so we need a 3D coordinate representation
    let cph = Coor3D::geo(55., 12., 0.); // Copenhagen
    let osl = Coor3D::geo(60., 10., 0.); // Oslo
    let sth = Coor3D::geo(59., 18., 0.); // Stockholm
    let hel = Coor3D::geo(60., 25., 0.); // Helsinki

    let mut data = [osl, cph, sth, hel];
    let ed50_wgs84 = ctx.op(pipeline)?;

    // Since the forward transformation goes *from* ed50 to wgs84, we use
    // the inverse method to take us the other way, back in time to ED50
    ctx.apply(ed50_wgs84, Inv, &mut data)?;
    println!("ed50:");
    for coord in data {
        println!("    {:?}", coord.to_geo());
    }

    // Finally an example of handling bad syntax:
    println!("Bad syntax example:");
    let op = ctx.op("aargh zone=23");
    if op.is_err() {
        println!("Deliberate error - {:?}", op);
    }

    // ------------------------------------------------------------------------------
    // Don't do this!
    // ------------------------------------------------------------------------------
    // As PROJ-conoisseurs know, the `Context` type in PROJ plays a somewhat more
    // passive role than the `Context` type in RG. But actually, the RG Context API
    // is built on top of a much more PROJ-like interface, where the operators take
    // center stage. In general, the use of this interface is *not* recommended, but
    // for the rare cases where it is preferable, we demonstrate its use below, by
    // repeating the exercises above, while swapping the roles of the `Op`/`OpHandle`
    // and the `Context`.
    println!("------------------------------------");
    println!(" Doing the same in the wrong way... ");
    println!("------------------------------------");
    use geodesy::authoring::Op;
    // Create an `Op`, turning geographical coordinates into UTM zone 32 coordinates
    let utm32 = Op::new("utm zone=32", &ctx)?;
    // Now, let's use the utm32-operator to transform some data
    utm32.apply(&ctx, &mut data, Fwd);

    // println!("utm32:");
    // for coord in data {
    //     println!("    {:?}", coord);
    // }

    // Take the inverse road back to geographic coordinates
    utm32.apply(&ctx, &mut data, Inv);

    // println!("Roundtrip to geo:");
    // println!("    {:#?}", data);
    // for coord in data {
    //     println!("    {:?}", coord.to_geo());
    // }

    // EPSG:1134
    let ed50_wgs84 = Op::new(pipeline, &ctx)?;
    let mut data = [osl, cph, sth, hel];
    ed50_wgs84.apply(&ctx, &mut data, Inv);
    // println!("ed50:");
    // println!("    {:?}", data.to_geo());

    // Handling bad syntax:
    println!("Bad syntax example:");
    let op = Op::new("aargh zone=23", &ctx);
    //let op = ctx.define_operation("aargh zone: 23");
    if op.is_err() {
        println!("Deliberate error - {:?}", op);
    }

    Ok(())
}
