1#![cfg_attr(feature = "nightly", feature(test))]
59#[cfg(feature = "nightly")]
60extern crate test;
61
62#[macro_use]
63extern crate lazy_static;
64
65use numpy::{PyArray, PyReadonlyArrayDyn};
70use pyo3::prelude::*;
71use std::io;
72
73pub mod mask;
74pub mod providers;
75pub mod shapes;
76
77pub use mask::RoaringMask;
78pub use providers::LandmaskProvider;
79pub use shapes::Shapes;
80
81include!(concat!(env!("OUT_DIR"), "/source_data.rs"));
82
83#[pymodule]
84fn roaring_landmask(_py: Python, m: &PyModule) -> PyResult<()> {
85 m.add_class::<mask::Affine>()?;
86 m.add_class::<RoaringMask>()?;
87 m.add_class::<Shapes>()?;
88 m.add_class::<RoaringLandmask>()?;
89 m.add_class::<LandmaskProvider>()?;
90
91 Ok(())
92}
93
94#[pyclass]
95pub struct RoaringLandmask {
96 #[pyo3(get)]
97 pub mask: RoaringMask,
98 #[pyo3(get)]
99 pub shapes: Shapes,
100}
101
102#[pymethods]
103impl RoaringLandmask {
104 #[staticmethod]
105 pub fn new(py: Python) -> io::Result<RoaringLandmask> {
106 Self::new_with_provider(py, LandmaskProvider::Gshhg)
107 }
108
109 #[staticmethod]
110 pub fn new_with_provider(
111 py: Python,
112 landmask_provider: LandmaskProvider,
113 ) -> io::Result<RoaringLandmask> {
114 let mask = RoaringMask::new(landmask_provider)?;
115 let shapes = Shapes::new(py, landmask_provider)?;
116
117 Ok(RoaringLandmask { mask, shapes })
118 }
119
120 #[getter]
121 pub fn dx(&self) -> f64 {
122 self.mask.dx()
123 }
124
125 #[getter]
126 pub fn dy(&self) -> f64 {
127 self.mask.dy()
128 }
129
130 pub fn contains(&self, x: f64, y: f64) -> bool {
138 assert!(y >= -90. && y <= 90.);
139
140 let x = modulate_longitude(x);
141
142 self.mask.contains_unchecked(x, y) && self.shapes.contains_unchecked(x, y)
143 }
144
145 fn contains_many(
146 &self,
147 py: Python,
148 x: PyReadonlyArrayDyn<f64>,
149 y: PyReadonlyArrayDyn<f64>,
150 ) -> Py<PyArray<bool, numpy::Ix1>> {
151 let x = x.as_array();
152 let y = y.as_array();
153
154 PyArray::from_iter(
155 py,
156 x.iter().zip(y.iter()).map(|(x, y)| self.contains(*x, *y)),
157 )
158 .to_owned()
159 }
160
161 pub fn contains_many_par(
162 &self,
163 py: Python,
164 x: PyReadonlyArrayDyn<f64>,
165 y: PyReadonlyArrayDyn<f64>,
166 ) -> Py<PyArray<bool, numpy::IxDyn>> {
167 let x = x.as_array();
168 let y = y.as_array();
169
170 use ndarray::Zip;
171 let contains = Zip::from(&x)
172 .and(&y)
173 .par_map_collect(|x, y| self.contains(*x, *y));
174 PyArray::from_owned_array(py, contains).to_owned()
175 }
176}
177
178fn modulate_longitude(lon: f64) -> f64 {
180 ((lon + 180.) % 360.) - 180.
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn load_ms() {
189 pyo3::prepare_freethreaded_python();
190 pyo3::Python::with_gil(|py| {
191 let _ms = RoaringLandmask::new_with_provider(py, LandmaskProvider::Gshhg).unwrap();
192 let _ms = RoaringLandmask::new_with_provider(py, LandmaskProvider::Osm).unwrap();
193 })
194 }
195
196 #[test]
197 fn test_np() {
198 pyo3::prepare_freethreaded_python();
199 pyo3::Python::with_gil(|py| {
200 let mask = RoaringLandmask::new_with_provider(py, LandmaskProvider::Gshhg).unwrap();
201 assert!(!mask.contains(5., 90.));
202
203 let mask = RoaringLandmask::new_with_provider(py, LandmaskProvider::Osm).unwrap();
204 assert!(!mask.contains(5., 90.));
205 })
206 }
207
208 #[test]
209 fn test_sp() {
210 pyo3::prepare_freethreaded_python();
211 pyo3::Python::with_gil(|py| {
212 let mask = RoaringLandmask::new_with_provider(py, LandmaskProvider::Gshhg).unwrap();
213 assert!(mask.contains(5., -89.99));
214
215 let mask = RoaringLandmask::new_with_provider(py, LandmaskProvider::Osm).unwrap();
216 assert!(mask.contains(5., -89.99));
217 })
218 }
219
220 #[test]
221 #[should_panic]
222 fn test_sp_oob() {
223 pyo3::prepare_freethreaded_python();
224 pyo3::Python::with_gil(|py| {
225 let mask = RoaringLandmask::new_with_provider(py, LandmaskProvider::Gshhg).unwrap();
226 assert!(mask.contains(5., -90.));
227
228 let mask = RoaringLandmask::new_with_provider(py, LandmaskProvider::Osm).unwrap();
229 assert!(mask.contains(5., -90.));
230 })
231 }
232
233 #[test]
234 fn test_dateline_wrap() {
235 pyo3::prepare_freethreaded_python();
236 pyo3::Python::with_gil(|py| {
237 for provider in [LandmaskProvider::Gshhg, LandmaskProvider::Osm] {
238 let mask = RoaringLandmask::new_with_provider(py, provider).unwrap();
239 assert!(!mask.contains(5., 89.));
241 assert!(mask.contains(5., -89.));
243 let x = (-180..180).map(f64::from).collect::<Vec<_>>();
245 let m = x.iter().map(|x| mask.contains(*x, 65.)).collect::<Vec<_>>();
246 let x = (180..540).map(f64::from).collect::<Vec<_>>();
248 let mm = x.iter().map(|x| mask.contains(*x, 65.)).collect::<Vec<_>>();
249 assert_eq!(m, mm);
250 }
251 })
252 }
253
254 #[test]
255 #[should_panic]
256 fn test_not_on_earth_north() {
257 pyo3::prepare_freethreaded_python();
258 pyo3::Python::with_gil(|py| {
259 let mask = RoaringLandmask::new_with_provider(py, LandmaskProvider::Gshhg).unwrap();
260 assert!(!mask.contains(5., 95.));
261
262 let mask = RoaringLandmask::new_with_provider(py, LandmaskProvider::Osm).unwrap();
263 assert!(!mask.contains(5., 95.));
264 })
265 }
266
267 #[test]
268 #[should_panic]
269 fn test_not_on_earth_south() {
270 pyo3::prepare_freethreaded_python();
271 pyo3::Python::with_gil(|py| {
272 let mask = RoaringLandmask::new_with_provider(py, LandmaskProvider::Gshhg).unwrap();
273 assert!(!mask.contains(5., -95.));
274
275 let mask = RoaringLandmask::new_with_provider(py, LandmaskProvider::Osm).unwrap();
276 assert!(!mask.contains(5., -95.));
277 })
278 }
279
280 #[cfg(feature = "nightly")]
281 mod benches {
282 use super::*;
283 use test::Bencher;
284
285 #[bench]
286 fn test_contains_on_land(b: &mut Bencher) {
287 pyo3::prepare_freethreaded_python();
288 pyo3::Python::with_gil(|py| {
289 for provider in [LandmaskProvider::Gshhg, LandmaskProvider::Osm] {
290 let mask = RoaringLandmask::new_with_provider(py, provider).unwrap();
291 assert!(mask.contains(15., 65.6));
292 assert!(mask.contains(10., 60.0));
293 b.iter(|| mask.contains(15., 65.6));
294 }
295 })
296 }
297
298 #[bench]
299 fn test_contains_in_ocean(b: &mut Bencher) {
300 pyo3::prepare_freethreaded_python();
301 pyo3::Python::with_gil(|py| {
302 for provider in [LandmaskProvider::Gshhg, LandmaskProvider::Osm] {
303 let mask = RoaringLandmask::new_with_provider(py, provider).unwrap();
304 assert!(!mask.contains(5., 65.6));
305 b.iter(|| mask.contains(5., 65.6));
306 }
307 });
308 }
309
310 #[bench]
311 fn test_contains_many(b: &mut Bencher) {
312 pyo3::prepare_freethreaded_python();
313 pyo3::Python::with_gil(|py| {
314 for provider in [LandmaskProvider::Gshhg, LandmaskProvider::Osm] {
315 let mask = RoaringLandmask::new_with_provider(py, provider).unwrap();
316 let (x, y): (Vec<f64>, Vec<f64>) = (0..360 * 2)
317 .map(|v| v as f64 * 0.5 - 180.)
318 .map(|x| {
319 (0..180 * 2)
320 .map(|y| y as f64 * 0.5 - 90.)
321 .map(move |y| (x, y))
322 })
323 .flatten()
324 .unzip();
325 let (x, y): (Vec<f64>, Vec<f64>) = (0..360 * 2)
326 .map(|v| v as f64 * 0.5 - 180.)
327 .map(|x| {
328 (0..180 * 2)
329 .map(|y| y as f64 * 0.5 - 90.)
330 .map(move |y| (x, y))
331 })
332 .flatten()
333 .unzip();
334 let x = PyArray::from_vec(py, x);
335 let y = PyArray::from_vec(py, y);
336 println!("testing {} points..", x.len());
337 b.iter(|| {
338 let len = x.len();
339
340 let x = x.to_dyn().readonly();
341 let y = y.to_dyn().readonly();
342
343 let onland = mask.contains_many(py, x, y);
344 assert!(onland.as_ref(py).len() == len);
345 });
346 }
347 })
348 }
349
350 #[bench]
351 #[ignore]
352 fn test_contains_many_par(b: &mut Bencher) {
353 pyo3::prepare_freethreaded_python();
354 pyo3::Python::with_gil(|py| {
355 for provider in [LandmaskProvider::Gshhg, LandmaskProvider::Osm] {
356 let mask = RoaringLandmask::new_with_provider(py, provider).unwrap();
357 let (x, y): (Vec<f64>, Vec<f64>) = (0..360 * 2)
358 .map(|v| v as f64 * 0.5 - 180.)
359 .map(|x| {
360 (0..180 * 2)
361 .map(|y| y as f64 * 0.5 - 90.)
362 .map(move |y| (x, y))
363 })
364 .flatten()
365 .unzip();
366 let (x, y): (Vec<f64>, Vec<f64>) = (0..360 * 2)
367 .map(|v| v as f64 * 0.5 - 180.)
368 .map(|x| {
369 (0..180 * 2)
370 .map(|y| y as f64 * 0.5 - 90.)
371 .map(move |y| (x, y))
372 })
373 .flatten()
374 .unzip();
375 let x = PyArray::from_vec(py, x);
376 let y = PyArray::from_vec(py, y);
377 println!("testing {} points..", x.len());
378 b.iter(|| {
379 let len = x.len();
380
381 let x = x.to_dyn().readonly();
382 let y = y.to_dyn().readonly();
383
384 let onland = mask.contains_many_par(py, x, y);
385 assert!(onland.as_ref(py).len() == len);
386 });
387 }
388 })
389 }
390 }
391}