1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#![doc(html_root_url = "https://urschrei.github.io/ostn02_phf/")]
//! Look up OSTN02 adjustments for transforming ETRS89 Eastings and Northings
//! to OSGB36 Eastings and Northings

const MIN_X_SHIFT: f64 = 86.275;
const MIN_Y_SHIFT: f64 = -81.603;
const MIN_Z_SHIFT: f64 = 43.982;

use std::f64;
const NAN: f64 = f64::NAN;

extern crate phf;
include!("ostn02.rs");

extern crate libc;
use libc::{c_double, int32_t};

/// Return a 3-tuple of adjustments which convert ETRS89 Eastings and Northings
/// to OSGB36 Eastings, Northings, and Orthometric height
fn get_shifts(tup: (i32, i32)) -> (f64, f64, f64) {
    // look up the shifts, or return NAN
    let key = format!("{:03x}{:03x}", tup.1, tup.0);
    match ostn02_lookup(&*key) {
        Some(res) => {
            (res.0 as f64 / 1000. + MIN_X_SHIFT,
             res.1 as f64 / 1000. + MIN_Y_SHIFT,
             res.2 as f64 / 1000. + MIN_Z_SHIFT)
        }
        None => (NAN, NAN, NAN),
    }
}

#[repr(C)]
/// Incoming ETRS89 kilometer-grid references
pub struct GridRefs {
    pub easting: int32_t,
    pub northing: int32_t,
}

#[repr(C)]
/// Outgoing OSTN02 Easting, Northing, and height adjustments
pub struct Adjustment {
    pub x_shift: c_double,
    pub y_shift: c_double,
    pub z_shift: c_double,
}

// From and Into traits for GridRefs
impl From<(i32, i32)> for GridRefs {
    fn from(gr: (i32, i32)) -> GridRefs {
        GridRefs {
            easting: gr.0,
            northing: gr.1,
        }
    }
}

impl From<GridRefs> for (i32, i32) {
    fn from(gr: GridRefs) -> (i32, i32) {
        (gr.easting, gr.northing)
    }
}

// From and Into traits for Adjustment
impl From<(f64, f64, f64)> for Adjustment {
    fn from(adj: (f64, f64, f64)) -> Adjustment {
        Adjustment {
            x_shift: adj.0,
            y_shift: adj.1,
            z_shift: adj.2,
        }
    }
}

impl From<Adjustment> for (f64, f64, f64) {
    fn from(adj: Adjustment) -> (f64, f64, f64) {
        (adj.x_shift, adj.y_shift, adj.z_shift)
    }
}

/// FFI function returning a 3-tuple of Easting, Northing, and height adjustments, for use in transforming
/// ETRS89 Eastings and Northings to OSGB36 Eastings, Northings.  
/// The argument is a Struct containing kilometer-grid references of the ETRS89 Northings and Eastings you wish to convert
/// 
/// # Examples
/// 
/// ```python
/// # Python example using ctypes
/// import sys, ctypes
/// from ctypes import c_int32, c_double, Structure
/// 
/// 
/// class GridRefs(Structure):
///     _fields_ = [("eastings", c_int32),
///                 ("northings", c_int32)]
/// 
///     def __str__(self):
///         return "({},{})".format(self.eastings, self.northings)
/// 
/// 
/// class Shifts(Structure):
///     _fields_ = [("x_shift", c_double),
///                 ("y_shift", c_double),
///                 ("z_shift", c_double)]
/// 
///     def __str__(self):
///         return "({}, {}, {})".format(self.x_shift, self.y_shift, self.z_shift)
/// 
/// 
/// prefix = {'win32': ''}.get(sys.platform, 'lib')
/// extension = {'darwin': '.dylib', 'win32': '.dll'}.get(sys.platform, '.so')
/// lib = ctypes.cdll.LoadLibrary(prefix + "ostn02_phf" + extension)
/// 
/// lib.get_shifts_ffi.argtypes = (GridRefs,)
/// lib.get_shifts_ffi.restype = Shifts
/// 
/// tup = GridRefs(651, 313)
/// 
/// # Should return (102.775, -78.244, 44.252)
/// print lib.get_shifts_ffi(tup)
/// ```
#[no_mangle]
pub extern "C" fn get_shifts_ffi(gr: GridRefs) -> Adjustment {
    get_shifts(gr.into()).into()
}

/// Return a 3-tuple of Easting, Northing, and height adjustments, for use in transforming
/// ETRS89 Eastings and Northings to OSGB36 Eastings, Northings.  
/// The key is the combined hex-transformed (03x) kilometer-grid reference of the Northings and Eastings:
/// 
/// # Examples
/// 
/// ```
/// use ostn02_phf::ostn02_lookup;
/// 
/// // Caister Tower Eastings and Northings: 651307.003, 313255.686
/// let e_grid = (651307.003 / 1000.) as i32;
/// let n_grid = (313255.686 / 1000.) as i32;
/// let key = format!("{:03x}{:03x}", n_grid, e_grid);
/// // key is 13928b
/// let result = ostn02_lookup(&*key).unwrap();
/// // result should be (16500, 3359, 270)
/// assert_eq!(result, (16500, 3359, 270));
/// // remember that the actual adjustment for a coordinate is a bilinear transform, using a square
/// // see ostn02_shifts in https://github.com/urschrei/lonlat_bng/blob/master/src/ostn02/mod.rs
/// ```
pub fn ostn02_lookup(key: &str) -> Option<(i32, i32, i32)> {
    if key.is_empty() {
        return None;
    }
    OSTN02.get(&*key).cloned()
}

#[test]
fn test_internal_ffi() {
    assert_eq!((102.775, -78.244, 44.252), get_shifts((651, 313)));
}

#[test]
fn test_ffi() {
    let gr = GridRefs {easting: 651, northing: 313};
    assert_eq!((102.775, -78.244, 44.252), get_shifts_ffi(gr).into());
}