<commit_before>#![doc(html_root_url = "https://urschrei.github.io/lonlat_bng/")]
//! The `lonlat_bng` crate provides functions that convert decimal longitude
//! and latitude coordinates into [British National Grid](https://en.wikipedia.org/wiki/Ordnance_Survey_National_Grid) coordinates, and vice versa.
//! This library makes use of the [OSTN02](https://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/surveying.html) transformations
//! for the following functions:
//!
//! - [`convert_osgb36`](fn.convert_osgb36.html)
//! - [`convert_etrs89_to_osgb36`](fn.convert_etrs89_to_osgb36.html)
//! - [`convert_to_osgb36_threaded`](fn.convert_to_osgb36_threaded.html)
//! - [`convert_to_osgb36_threaded_vec`](fn.convert_to_osgb_threaded_vec.html)
//! - [`convert_etrs89_to_osgb36_threaded`](fn.convert_etrs89_to_osgb36_threaded.html)
//! - [`convert_etrs89_to_osgb36_threaded_vec`](fn.convert_etrs89_to_osgb36_threaded_vec.html)
//! - [`convert_osgb36_to_ll_threaded`](fn.convert_osgb36_to_ll_threaded.html)
//! - [`convert_osgb36_to_ll_threaded_vec`](fn.convert_osgb36_to_ll_threaded_vec.html)
//! - [`convert_osgb36_to_etrs89_threaded`](fn.convert_osgb36_to_etrs89_threaded.html)
//! - [`convert_osgb36_to_etrs89_threaded_vec`](fn.convert_osgb36_to_etrs89_threaded_vec.html)
//!
//! These functions transform input longitude and latitude coordinates to OSGB36 Eastings and Northings with high accuracy, and are suitable for use in surveying and construction. Please run your own tests, though.
//! **Note that `lon`, `lat` coordinates outside the UK bounding box will be transformed to `(9999, 9999)`, which cannot be mapped.**
//!
//! # Examples
//!
//! ```
//! // Convert single Longitude, Latitude values to OSGB36 Eastings and Northings
//! assert_eq!((651409.792, 313177.448), lonlat_bng::ostn02::convert_osgb36(&1.716073973, &52.658007833).unwrap());
//! ```
//! ```
//! // Convert single Longitude, Latitude values to BNG Eastings and Northings (Fast, but only accurate within ~7m)
//! assert_eq!((516276, 173141), lonlat_bng::convert_bng(&-0.32824866, &51.44533267).unwrap());
//! ```
//! ```
//! // Convert single BNG values to Longitude, Latitude (Fast, but only accurate within ~7m)
//! assert_eq!((-0.328248, 51.44534), lonlat_bng::convert_lonlat(&516276, &173141));
//! ```
//! ```
//! // Convert vectors using threaded functions
//! lonlat_bng::convert_to_bng_threaded_vec(vec![&-0.32824866], vec![&51.44533267]);
//! ```
//! ```
//! lonlat_bng::convert_to_lonlat_threaded_vec(vec![&516276], vec![&173141]);
//! ```
//! The crate also provides C-compatible wrapper functions, which are intended for use with FFI.
//! An example implementation using Python can be found at [Convertbng](https://github.com/urschrei/convertbng).
use std::marker::Send;

extern crate libc;

extern crate rand;

extern crate crossbeam;
use crossbeam::scope;

extern crate num_cpus;
extern crate ostn02_phf;

mod conversions;
mod utils;
mod ffi;

pub use ffi::Array;
pub use ffi::drop_float_array;
pub use ffi::convert_to_bng_threaded;
pub use ffi::convert_to_lonlat_threaded;
pub use ffi::convert_to_osgb36_threaded;
pub use ffi::convert_to_etrs89_threaded;
pub use ffi::convert_etrs89_to_osgb36_threaded;
pub use ffi::convert_etrs89_to_ll_threaded;
pub use ffi::convert_osgb36_to_ll_threaded;
pub use ffi::convert_osgb36_to_etrs89_threaded;

pub use conversions::convert_bng;
pub use conversions::convert_lonlat;
pub use conversions::convert_etrs89;
pub use conversions::convert_osgb36;
pub use conversions::convert_etrs89_to_osgb36;
pub use conversions::convert_osgb36_to_etrs89;
pub use conversions::convert_osgb36_to_ll;
pub use conversions::convert_etrs89_to_ll;

// fn helmert(lon_vec: Vec<&f32>, lat_vec: Vec<&f32>) -> (Vec<f32>, Vec<f32>) {
//     let t_array = Vec3::new(TX, TY, TZ);
//     let params = Mat3::new(1. + s, RZS, -RYS, -RZS, 1. + s, RXS, RYS, -RXS, 1. + s);
//     // let zeros: Vec<&f32> = vec![&1.; lon_vec.len()];
//     let test = vec![1, 2, 3, 4, 5, 6];
//     // let mut combined: Vec<&f32> = lon_vec.extend(lat_vec.iter()).collect();
//     // combined.extend(zeros.iter().cloned());
//     // let tmat = DMat::from_row_vec(3, zeros.len(), &combined);
//     let tmat = DMat::from_row_vec(3, 2, &test);
//     // build input vector of x, y, and z columns
//     // let inp = DMat::from_row_vec(3, 1, vec![lon_vec, lat_vec, h_vec]);
//     (vec![1.], vec![2.])
// }

/// A threaded wrapper for [`lonlat_bng::convert_bng`](fn.convert_bng.html)
pub fn convert_to_bng_threaded_vec<'a, 'b>(longitudes: &'a mut [f64],
                                           latitudes: &'b mut [f64])
                                           -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(longitudes, latitudes, convert_bng)
}

/// A threaded wrapper for [`lonlat_bng::convert_lonlat`](fn.convert_lonlat.html)
pub fn convert_to_lonlat_threaded_vec<'a, 'b>(eastings: &'a mut [f64],
                                              northings: &'b mut [f64])
                                              -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(eastings, northings, convert_lonlat)
}

/// A threaded wrapper for [`lonlat_bng::convert_etrs89`](fn.convert_etrs89.html)
pub fn convert_to_etrs89_threaded_vec<'a, 'b>(longitudes: &'a mut [f64],
                                              latitudes: &'b mut [f64])
                                              -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(longitudes, latitudes, convert_etrs89)
}

/// A threaded wrapper for [`lonlat_bng::convert_osgb36`](fn.convert_osgb36.html)
pub fn convert_to_osgb36_threaded_vec<'a, 'b>(longitudes: &'a mut [f64],
                                              latitudes: &'b mut [f64])
                                              -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(longitudes, latitudes, convert_osgb36)
}

/// A threaded wrapper for [`lonlat_bng::convert_etrs89_to_osgb36`](fn.convert_etrs89_to_osgb36.html)
pub fn convert_etrs89_to_osgb36_threaded_vec<'a, 'b>(eastings: &'a mut [f64],
                                                     northings: &'b mut [f64])
                                                     -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(eastings, northings, convert_etrs89_to_osgb36)
}

/// A threaded wrapper for [`lonlat_bng::convert_etrs89_to_ll`](fn.convert_etrs8989_to_ll.html)
pub fn convert_etrs89_to_ll_threaded_vec<'a, 'b>(eastings: &'a mut [f64],
                                                 northings: &'b mut [f64])
                                                 -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(eastings, northings, convert_etrs89_to_ll)
}

/// A threaded wrapper for [`lonlat_bng::convert_osgb36_to_etrs89`](fn.convert_osgb36_to_etrs89.html)
pub fn convert_osgb36_to_etrs89_threaded_vec<'a, 'b>(eastings: &'a mut [f64],
                                                     northings: &'b mut [f64])
                                                     -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(eastings, northings, convert_osgb36_to_etrs89)
}

/// A threaded wrapper for [`lonlat_bng::convert_osgb36_to_ll`](fn.convert_osgb36_to_ll.html)
pub fn convert_osgb36_to_ll_threaded_vec<'a, 'b>(eastings: &'a mut [f64],
                                                 northings: &'b mut [f64])
                                                 -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(eastings, northings, convert_osgb36_to_ll)
}

// Generic function that avoids unnecessary zipping and unzipping
// As opposed to convert_vec, we're directly modifying and returning the
// input vectors here, at the cost of having to use lifetime annotations
fn convert_vec_direct<'a, 'b, F>(ex: &'a mut [f64],
                                 ny: &'b mut [f64],
                                 func: F)
                                 -> (&'a mut [f64], &'b mut [f64])
    where F: Fn(&f64, &f64) -> Result<(f64, f64), ()> + Send + Copy
{
    let numthreads = num_cpus::get() * 8 as usize;
    let mut size = ex.len() / numthreads;
    if ex.len() % numthreads > 0 {
        size += 1;
    }
    size = std::cmp::max(1, size);
    crossbeam::scope(|scope| {
        for (ex_chunk, ny_chunk) in ex.chunks_mut(size).zip(ny.chunks_mut(size)) {
            scope.spawn(move || {
                for (ex_elem, ny_elem) in ex_chunk.iter_mut().zip(ny_chunk.iter_mut()) {
                    match func(ex_elem, ny_elem) {
                        Ok(res) => {
                            *ex_elem = res.0;
                            *ny_elem = res.1;
                        }
                        // we don't care about the return value as such
                        Err(_) => {
                            *ex_elem = 9999.000;
                            *ny_elem = 9999.000;
                        }
                    };
                }
            });
        }
    });
    (ex, ny)
}

#[cfg(test)]
mod tests {
    use conversions::convert_bng;
    use ffi::drop_float_array;
    use ffi::Array;
    use super::convert_vec_direct;
    use super::convert_to_bng_threaded;
    use super::convert_to_lonlat_threaded;
    use super::convert_to_osgb36_threaded;
    use super::convert_to_etrs89_threaded;
    use super::convert_etrs89_to_osgb36_threaded;<commit_msg>Add some comments to generic threaded conversion function<commit_after>#![doc(html_root_url = "https://urschrei.github.io/lonlat_bng/")]
//! The `lonlat_bng` crate provides functions that convert decimal longitude
//! and latitude coordinates into [British National Grid](https://en.wikipedia.org/wiki/Ordnance_Survey_National_Grid) coordinates, and vice versa.
//! This library makes use of the [OSTN02](https://www.ordnancesurvey.co.uk/business-and-government/help-and-support/navigation-technology/os-net/surveying.html) transformations
//! for the following functions:
//!
//! - [`convert_osgb36`](fn.convert_osgb36.html)
//! - [`convert_etrs89_to_osgb36`](fn.convert_etrs89_to_osgb36.html)
//! - [`convert_to_osgb36_threaded`](fn.convert_to_osgb36_threaded.html)
//! - [`convert_to_osgb36_threaded_vec`](fn.convert_to_osgb_threaded_vec.html)
//! - [`convert_etrs89_to_osgb36_threaded`](fn.convert_etrs89_to_osgb36_threaded.html)
//! - [`convert_etrs89_to_osgb36_threaded_vec`](fn.convert_etrs89_to_osgb36_threaded_vec.html)
//! - [`convert_osgb36_to_ll_threaded`](fn.convert_osgb36_to_ll_threaded.html)
//! - [`convert_osgb36_to_ll_threaded_vec`](fn.convert_osgb36_to_ll_threaded_vec.html)
//! - [`convert_osgb36_to_etrs89_threaded`](fn.convert_osgb36_to_etrs89_threaded.html)
//! - [`convert_osgb36_to_etrs89_threaded_vec`](fn.convert_osgb36_to_etrs89_threaded_vec.html)
//!
//! These functions transform input longitude and latitude coordinates to OSGB36 Eastings and Northings with high accuracy, and are suitable for use in surveying and construction. Please run your own tests, though.
//! **Note that `lon`, `lat` coordinates outside the UK bounding box will be transformed to `(9999, 9999)`, which cannot be mapped.**
//!
//! # Examples
//!
//! ```
//! // Convert single Longitude, Latitude values to OSGB36 Eastings and Northings
//! assert_eq!((651409.792, 313177.448), lonlat_bng::ostn02::convert_osgb36(&1.716073973, &52.658007833).unwrap());
//! ```
//! ```
//! // Convert single Longitude, Latitude values to BNG Eastings and Northings (Fast, but only accurate within ~7m)
//! assert_eq!((516276, 173141), lonlat_bng::convert_bng(&-0.32824866, &51.44533267).unwrap());
//! ```
//! ```
//! // Convert single BNG values to Longitude, Latitude (Fast, but only accurate within ~7m)
//! assert_eq!((-0.328248, 51.44534), lonlat_bng::convert_lonlat(&516276, &173141));
//! ```
//! ```
//! // Convert vectors using threaded functions
//! lonlat_bng::convert_to_bng_threaded_vec(vec![&-0.32824866], vec![&51.44533267]);
//! ```
//! ```
//! lonlat_bng::convert_to_lonlat_threaded_vec(vec![&516276], vec![&173141]);
//! ```
//! The crate also provides C-compatible wrapper functions, which are intended for use with FFI.
//! An example implementation using Python can be found at [Convertbng](https://github.com/urschrei/convertbng).
use std::marker::Send;

extern crate libc;

extern crate rand;

extern crate crossbeam;
use crossbeam::scope;

extern crate num_cpus;
extern crate ostn02_phf;

mod conversions;
mod utils;
mod ffi;

pub use ffi::Array;
pub use ffi::drop_float_array;
pub use ffi::convert_to_bng_threaded;
pub use ffi::convert_to_lonlat_threaded;
pub use ffi::convert_to_osgb36_threaded;
pub use ffi::convert_to_etrs89_threaded;
pub use ffi::convert_etrs89_to_osgb36_threaded;
pub use ffi::convert_etrs89_to_ll_threaded;
pub use ffi::convert_osgb36_to_ll_threaded;
pub use ffi::convert_osgb36_to_etrs89_threaded;

pub use conversions::convert_bng;
pub use conversions::convert_lonlat;
pub use conversions::convert_etrs89;
pub use conversions::convert_osgb36;
pub use conversions::convert_etrs89_to_osgb36;
pub use conversions::convert_osgb36_to_etrs89;
pub use conversions::convert_osgb36_to_ll;
pub use conversions::convert_etrs89_to_ll;

// fn helmert(lon_vec: Vec<&f32>, lat_vec: Vec<&f32>) -> (Vec<f32>, Vec<f32>) {
//     let t_array = Vec3::new(TX, TY, TZ);
//     let params = Mat3::new(1. + s, RZS, -RYS, -RZS, 1. + s, RXS, RYS, -RXS, 1. + s);
//     // let zeros: Vec<&f32> = vec![&1.; lon_vec.len()];
//     let test = vec![1, 2, 3, 4, 5, 6];
//     // let mut combined: Vec<&f32> = lon_vec.extend(lat_vec.iter()).collect();
//     // combined.extend(zeros.iter().cloned());
//     // let tmat = DMat::from_row_vec(3, zeros.len(), &combined);
//     let tmat = DMat::from_row_vec(3, 2, &test);
//     // build input vector of x, y, and z columns
//     // let inp = DMat::from_row_vec(3, 1, vec![lon_vec, lat_vec, h_vec]);
//     (vec![1.], vec![2.])
// }

/// A threaded wrapper for [`lonlat_bng::convert_bng`](fn.convert_bng.html)
pub fn convert_to_bng_threaded_vec<'a, 'b>(longitudes: &'a mut [f64],
                                           latitudes: &'b mut [f64])
                                           -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(longitudes, latitudes, convert_bng)
}

/// A threaded wrapper for [`lonlat_bng::convert_lonlat`](fn.convert_lonlat.html)
pub fn convert_to_lonlat_threaded_vec<'a, 'b>(eastings: &'a mut [f64],
                                              northings: &'b mut [f64])
                                              -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(eastings, northings, convert_lonlat)
}

/// A threaded wrapper for [`lonlat_bng::convert_etrs89`](fn.convert_etrs89.html)
pub fn convert_to_etrs89_threaded_vec<'a, 'b>(longitudes: &'a mut [f64],
                                              latitudes: &'b mut [f64])
                                              -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(longitudes, latitudes, convert_etrs89)
}

/// A threaded wrapper for [`lonlat_bng::convert_osgb36`](fn.convert_osgb36.html)
pub fn convert_to_osgb36_threaded_vec<'a, 'b>(longitudes: &'a mut [f64],
                                              latitudes: &'b mut [f64])
                                              -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(longitudes, latitudes, convert_osgb36)
}

/// A threaded wrapper for [`lonlat_bng::convert_etrs89_to_osgb36`](fn.convert_etrs89_to_osgb36.html)
pub fn convert_etrs89_to_osgb36_threaded_vec<'a, 'b>(eastings: &'a mut [f64],
                                                     northings: &'b mut [f64])
                                                     -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(eastings, northings, convert_etrs89_to_osgb36)
}

/// A threaded wrapper for [`lonlat_bng::convert_etrs89_to_ll`](fn.convert_etrs8989_to_ll.html)
pub fn convert_etrs89_to_ll_threaded_vec<'a, 'b>(eastings: &'a mut [f64],
                                                 northings: &'b mut [f64])
                                                 -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(eastings, northings, convert_etrs89_to_ll)
}

/// A threaded wrapper for [`lonlat_bng::convert_osgb36_to_etrs89`](fn.convert_osgb36_to_etrs89.html)
pub fn convert_osgb36_to_etrs89_threaded_vec<'a, 'b>(eastings: &'a mut [f64],
                                                     northings: &'b mut [f64])
                                                     -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(eastings, northings, convert_osgb36_to_etrs89)
}

/// A threaded wrapper for [`lonlat_bng::convert_osgb36_to_ll`](fn.convert_osgb36_to_ll.html)
pub fn convert_osgb36_to_ll_threaded_vec<'a, 'b>(eastings: &'a mut [f64],
                                                 northings: &'b mut [f64])
                                                 -> (&'a mut [f64], &'b mut [f64]) {
    convert_vec_direct(eastings, northings, convert_osgb36_to_ll)
}

// Generic function that avoids unnecessary zipping and unzipping
// As opposed to convert_vec, we're directly modifying and returning the
// input vectors here, at the cost of having to use lifetime annotations
fn convert_vec_direct<'a, 'b, F>(ex: &'a mut [f64],
                                 ny: &'b mut [f64],
                                 func: F)
                                 -> (&'a mut [f64], &'b mut [f64])
    where F: Fn(&f64, &f64) -> Result<(f64, f64), ()> + Send + Copy
{
    // TODO measure number of threads v perf more effectively
    let numthreads = num_cpus::get() * 8 as usize;
    let mut size = ex.len() / numthreads;
    if ex.len() % numthreads > 0 {
        size += 1;
    }
    size = std::cmp::max(1, size);
    crossbeam::scope(|scope| {
        // chunks_mut returns chunks of "size"
        // e.g. 20 items / 4 (numthreads) = 5, resulting in 4 chunks, 1 per thread
        for (ex_chunk, ny_chunk) in ex.chunks_mut(size).zip(ny.chunks_mut(size)) {
            scope.spawn(move || {
                for (ex_elem, ny_elem) in ex_chunk.iter_mut().zip(ny_chunk.iter_mut()) {
                    match func(ex_elem, ny_elem) {
                        // mutate values, or assign default error values
                        Ok(res) => {
                            *ex_elem = res.0;
                            *ny_elem = res.1;
                        }
                        Err(_) => {
                            *ex_elem = 9999.000;
                            *ny_elem = 9999.000;
                        }
                    };
                }
            });
        }
    });
    (ex, ny)
}

#[cfg(test)]
mod tests {
    use conversions::convert_bng;
    use ffi::drop_float_array;
    use ffi::Array;
    use super::convert_vec_direct;
    use super::convert_to_bng_threaded;
    use super::convert_to_lonlat_threaded;
    use super::convert_to_osgb36_threaded;
    use super::convert_to_etrs89_threaded;
    use super::convert_etrs89_to_osgb36_threaded;
{'fraction_non_alphanumeric': 0.14016967908520842, 'fraction_numerical': 0.04710965906096854, 'mean_word_length': 2.695813047711782, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 63, 'https://': 8, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 72, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '555988', 'n_tokens_mistral': 7075, 'n_tokens_neox': 6437, 'n_words': 1365}