calzone 1.1.6__tar.gz → 1.1.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {calzone-1.1.6 → calzone-1.1.8}/Cargo.toml +3 -1
- {calzone-1.1.6/src/python/calzone.egg-info → calzone-1.1.8}/PKG-INFO +1 -1
- {calzone-1.1.6 → calzone-1.1.8}/pyproject.toml +1 -1
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/map.rs +398 -10
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/volume.rs +1 -1
- {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone/__init__.py +1 -1
- {calzone-1.1.6 → calzone-1.1.8/src/python/calzone.egg-info}/PKG-INFO +1 -1
- {calzone-1.1.6 → calzone-1.1.8}/tests/test_geometry.py +19 -0
- {calzone-1.1.6 → calzone-1.1.8}/COPYING.LESSER +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/LICENSE +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/MANIFEST.in +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/README.md +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/setup.cfg +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/calzone.h +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/cxx.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/bytes.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/goupil.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/materials/gate.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/materials/hash.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/materials.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/materials.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/mesh.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/mesh.h +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/mesh.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/mulder.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/solids.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry/solids.h +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/geometry.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/lib.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone/__main__.py +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone.egg-info/SOURCES.txt +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone.egg-info/dependency_links.txt +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone.egg-info/entry_points.txt +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone.egg-info/requires.txt +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone.egg-info/top_level.txt +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/geometry.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/geometry.h +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/physics.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/physics.h +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/physics.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/random.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/random.h +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/random.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/sampler.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/sampler.h +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/sampler.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/source.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/source.h +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/source.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/tracker.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/tracker.h +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation/tracker.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/simulation.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/convert.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/data.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/error.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/error.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/export.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/extract.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/float.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/io.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/namespace.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/numpy.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/os.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/units.cc +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils/units.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/src/utils.rs +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/tests/test_examples.py +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/tests/test_materials.py +0 -0
- {calzone-1.1.6 → calzone-1.1.8}/tests/test_simulation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "calzone"
|
|
3
|
-
version = "1.1.
|
|
3
|
+
version = "1.1.8"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
|
|
6
6
|
[lib]
|
|
@@ -13,6 +13,8 @@ cxx = "1.0"
|
|
|
13
13
|
derive_more = "0.99"
|
|
14
14
|
enum-variants-strings = "0.3"
|
|
15
15
|
flate2.workspace = true
|
|
16
|
+
geotiff = "0.1"
|
|
17
|
+
geo-types = "0.7"
|
|
16
18
|
getrandom = "0.2"
|
|
17
19
|
indexmap = "2.2"
|
|
18
20
|
indicatif = "0.17"
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
use crate::utils::error::Error;
|
|
2
|
-
use crate::utils::error::ErrorKind::{NotImplementedError, ValueError};
|
|
2
|
+
use crate::utils::error::ErrorKind::{IOError, NotImplementedError, TypeError, ValueError};
|
|
3
3
|
use crate::utils::extract::{Extractor, Property, Tag};
|
|
4
4
|
use crate::utils::float::f64x3;
|
|
5
5
|
use crate::utils::io::{dump_stl, PathString};
|
|
6
6
|
use crate::utils::numpy::{PyArray, PyArrayMethods, PyUntypedArray};
|
|
7
|
+
use geotiff::{GeoTiff, RasterType};
|
|
8
|
+
use geo_types::geometry::Coord;
|
|
7
9
|
use pyo3::prelude::*;
|
|
8
10
|
use pyo3::types::PyDict;
|
|
9
11
|
use std::ffi::OsStr;
|
|
12
|
+
use std::fs::File;
|
|
13
|
+
use std::io::{BufRead, BufReader, BufWriter, Write};
|
|
10
14
|
use std::path::Path;
|
|
11
15
|
|
|
12
16
|
|
|
@@ -62,7 +66,7 @@ impl Map {
|
|
|
62
66
|
let geotiff = py.import_bound("geotiff")
|
|
63
67
|
.and_then(|module| module.getattr("GeoTiff"))?;
|
|
64
68
|
if any.is_instance(&geotiff)? {
|
|
65
|
-
Self::
|
|
69
|
+
Self::from_geotiff_object(&any)
|
|
66
70
|
} else {
|
|
67
71
|
let why = format!(
|
|
68
72
|
"unimplemented conversion from '{}'",
|
|
@@ -129,6 +133,23 @@ impl Map {
|
|
|
129
133
|
let filename = filename.to_string();
|
|
130
134
|
let path = Path::new(&filename);
|
|
131
135
|
match path.extension().and_then(OsStr::to_str) {
|
|
136
|
+
Some("asc") | Some("ASC") => {
|
|
137
|
+
let mut nodata: Option<f32> = None;
|
|
138
|
+
let mut precision: Option<usize> = None;
|
|
139
|
+
if let Some(kwargs) = kwargs {
|
|
140
|
+
const EXTRACTOR: Extractor<2> = Extractor::new([
|
|
141
|
+
Property::optional_f64("nodata"),
|
|
142
|
+
Property::optional_u32("precision"),
|
|
143
|
+
]);
|
|
144
|
+
let tag = Tag::new("dump", "", None);
|
|
145
|
+
let [nodata_value, prec] = EXTRACTOR.extract_any(&tag, kwargs, None)?;
|
|
146
|
+
let nodata_value: Option<f64> = nodata_value.into();
|
|
147
|
+
nodata = nodata_value.map(|value| value as f32);
|
|
148
|
+
let prec: Option<u32> = prec.into();
|
|
149
|
+
precision = prec.map(|value| value as usize);
|
|
150
|
+
}
|
|
151
|
+
self.to_ascii(py, &filename, nodata, precision)
|
|
152
|
+
},
|
|
132
153
|
Some("png") | Some("PNG") => {
|
|
133
154
|
if let Some(kwargs) = kwargs {
|
|
134
155
|
const EXTRACTOR: Extractor<0> = Extractor::new([]); // No arguments.
|
|
@@ -194,6 +215,7 @@ impl Map {
|
|
|
194
215
|
pub fn from_file(py: Python, path: &Path) -> PyResult<Self> {
|
|
195
216
|
let filename = path.to_str().unwrap();
|
|
196
217
|
match path.extension().and_then(OsStr::to_str) {
|
|
218
|
+
Some("asc") | Some("ASC") => Self::from_ascii(py, filename),
|
|
197
219
|
Some("png") | Some("PNG") => Self::from_png(py, filename),
|
|
198
220
|
Some("tif") | Some("TIF") => Self::from_geotiff_file(py, filename),
|
|
199
221
|
Some(other) => {
|
|
@@ -445,7 +467,73 @@ impl Map {
|
|
|
445
467
|
// ===============================================================================================
|
|
446
468
|
|
|
447
469
|
impl Map {
|
|
448
|
-
fn
|
|
470
|
+
fn from_geotiff_file<'py>(py: Python, path: &str) -> PyResult<Self> {
|
|
471
|
+
let geotiff = GeoTiff::read(File::open(path)?)
|
|
472
|
+
.map_err(|err| Error::new(IOError)
|
|
473
|
+
.what("GeoTIFF file")
|
|
474
|
+
.why(&err.to_string())
|
|
475
|
+
.to_err()
|
|
476
|
+
)?;
|
|
477
|
+
let crs = geotiff.geo_key_directory.projected_type.map(|crs| crs as usize);
|
|
478
|
+
let nx = geotiff.raster_width;
|
|
479
|
+
let ny = geotiff.raster_height;
|
|
480
|
+
let ([x0, x1], [y0, y1]) = {
|
|
481
|
+
let extent = geotiff.model_extent();
|
|
482
|
+
let min = extent.min();
|
|
483
|
+
let max = extent.max();
|
|
484
|
+
let x = [ min.x, max.x ];
|
|
485
|
+
let y = [ min.y, max.y ];
|
|
486
|
+
(x, y)
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
let raster_type = match geotiff.geo_key_directory.raster_type
|
|
490
|
+
.unwrap_or(RasterType::Undefined) {
|
|
491
|
+
RasterType::RasterPixelIsArea => RasterType::RasterPixelIsArea,
|
|
492
|
+
RasterType::RasterPixelIsPoint => RasterType::RasterPixelIsPoint,
|
|
493
|
+
_ => if geotiff
|
|
494
|
+
.get_value_at::<f64>(&Coord { x: x0, y: y0 }, 0)
|
|
495
|
+
.or_else(|| geotiff.get_value_at::<f64>(&Coord { x: x1, y: y0 }, 0))
|
|
496
|
+
.or_else(|| geotiff.get_value_at::<f64>(&Coord { x: x0, y: y1 }, 0))
|
|
497
|
+
.or_else(|| geotiff.get_value_at::<f64>(&Coord { x: x1, y: y1 }, 0))
|
|
498
|
+
.is_none() {
|
|
499
|
+
RasterType::RasterPixelIsPoint
|
|
500
|
+
} else {
|
|
501
|
+
RasterType::RasterPixelIsArea
|
|
502
|
+
},
|
|
503
|
+
};
|
|
504
|
+
let (x0, x1, dx, y0, y1, dy) = match raster_type {
|
|
505
|
+
RasterType::RasterPixelIsArea => {
|
|
506
|
+
let dx = (x1 - x0) / (nx as f64);
|
|
507
|
+
let dy = (y1 - y0) / (ny as f64);
|
|
508
|
+
(x0 + 0.5 * dx, x1 - 0.5 * dx, dx, y0 + 0.5 * dy, y1 - 0.5 * dy, dy)
|
|
509
|
+
},
|
|
510
|
+
RasterType::RasterPixelIsPoint => {
|
|
511
|
+
let dx = (x1 - x0) / ((nx - 1) as f64);
|
|
512
|
+
let dy = (y1 - y0) / ((ny - 1) as f64);
|
|
513
|
+
(x0, x1, dx, y0, y1, dy)
|
|
514
|
+
},
|
|
515
|
+
_ => unreachable!(),
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
let array = PyArray::<f32>::empty(py, &[ny, nx])?;
|
|
519
|
+
let size = nx * ny;
|
|
520
|
+
if size > 0 {
|
|
521
|
+
let z = unsafe { array.slice_mut()? };
|
|
522
|
+
for iy in 0..ny {
|
|
523
|
+
let y = if iy == ny - 1 { y1 } else { y0 + iy as f64 * dy };
|
|
524
|
+
for ix in 0..nx {
|
|
525
|
+
let x = if ix == nx - 1 { x1 } else { x0 + ix as f64 * dx };
|
|
526
|
+
z[iy * nx + ix] = geotiff.get_value_at(&Coord { x, y }, 0)
|
|
527
|
+
.unwrap_or_else(|| 0.0);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
let z = array.into_any().unbind();
|
|
532
|
+
|
|
533
|
+
Ok(Self { crs, nx, ny, x0, x1, y0, y1, z })
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
fn from_geotiff_object<'py>(geotiff: &Bound<'py, PyAny>) -> PyResult<Self> {
|
|
449
537
|
// Get metadata.
|
|
450
538
|
let crs = geotiff.getattr("crs_code")
|
|
451
539
|
.and_then(|crs| {
|
|
@@ -495,13 +583,6 @@ impl Map {
|
|
|
495
583
|
};
|
|
496
584
|
Ok(map)
|
|
497
585
|
}
|
|
498
|
-
|
|
499
|
-
fn from_geotiff_file<'py>(py: Python, path: &str) -> PyResult<Self> {
|
|
500
|
-
py.import_bound("geotiff")
|
|
501
|
-
.and_then(|module| module.getattr("GeoTiff"))
|
|
502
|
-
.and_then(|constr| constr.call1((path,)))
|
|
503
|
-
.and_then(|object| Self::from_geotiff(&object))
|
|
504
|
-
}
|
|
505
586
|
}
|
|
506
587
|
|
|
507
588
|
|
|
@@ -660,3 +741,310 @@ impl Map {
|
|
|
660
741
|
Ok(())
|
|
661
742
|
}
|
|
662
743
|
}
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
// ===============================================================================================
|
|
747
|
+
//
|
|
748
|
+
// ASCII Grid serialisation.
|
|
749
|
+
//
|
|
750
|
+
// ===============================================================================================
|
|
751
|
+
|
|
752
|
+
impl Map {
|
|
753
|
+
fn from_ascii<'py>(py: Python, path: &str) -> PyResult<Self> {
|
|
754
|
+
#[derive(Clone, Copy)]
|
|
755
|
+
enum MetaType {
|
|
756
|
+
Nx,
|
|
757
|
+
Ny,
|
|
758
|
+
X0,
|
|
759
|
+
Y0,
|
|
760
|
+
Delta,
|
|
761
|
+
NoData,
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
enum MetaValue {
|
|
765
|
+
NRows(usize),
|
|
766
|
+
NCols(usize),
|
|
767
|
+
XLLCenter(f64),
|
|
768
|
+
XLLCorner(f64),
|
|
769
|
+
YLLCenter(f64),
|
|
770
|
+
YLLCorner(f64),
|
|
771
|
+
CellSize(f64),
|
|
772
|
+
NoData(Result<f32, String>),
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
impl MetaValue {
|
|
776
|
+
fn into_f64(self) -> f64 {
|
|
777
|
+
match self {
|
|
778
|
+
Self::CellSize(value) => value,
|
|
779
|
+
_ => unreachable!(),
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
fn into_usize(self) -> usize {
|
|
784
|
+
match self {
|
|
785
|
+
Self::NRows(value) => value,
|
|
786
|
+
Self::NCols(value) => value,
|
|
787
|
+
_ => unreachable!(),
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const WHAT: &str = "ASCII Grid file";
|
|
793
|
+
|
|
794
|
+
impl MetaType {
|
|
795
|
+
const fn lineno(&self) -> usize {
|
|
796
|
+
match self {
|
|
797
|
+
Self::Nx => 1,
|
|
798
|
+
Self::Ny => 2,
|
|
799
|
+
Self::X0 => 3,
|
|
800
|
+
Self::Y0 => 4,
|
|
801
|
+
Self::Delta => 5,
|
|
802
|
+
Self::NoData => 6,
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
fn parse(
|
|
807
|
+
&self,
|
|
808
|
+
lines: &mut impl Iterator<Item=Result<String, std::io::Error>>,
|
|
809
|
+
path: &str,
|
|
810
|
+
) -> PyResult<MetaValue> {
|
|
811
|
+
let badname = |expected: &str, found: &str| -> PyErr {
|
|
812
|
+
let why = format!(
|
|
813
|
+
"{}:{}: expected {}, found {}", path, self.lineno(), expected, found
|
|
814
|
+
);
|
|
815
|
+
Error::new(TypeError).what(WHAT).why(&why).to_err()
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
let badvalue = |err: String, value: &str| -> PyErr {
|
|
819
|
+
let why = format!("{}:{}: {}: {}", path, self.lineno(), err, value);
|
|
820
|
+
Error::new(TypeError).what(WHAT).why(&why).to_err()
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
let eol = || -> PyErr {
|
|
824
|
+
let why = format!("{}:{}: unexpected end-of-line", path, self.lineno());
|
|
825
|
+
Error::new(TypeError).what(WHAT).why(&why).to_err()
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
let Some(line) = lines.next() else { return Err(eol()) };
|
|
829
|
+
let Ok(line) = line else { return Err(eol()) };
|
|
830
|
+
let mut tokens = line.split_ascii_whitespace();
|
|
831
|
+
let Some(name) = tokens.next() else { return Err(eol()) };
|
|
832
|
+
let name = name.to_uppercase();
|
|
833
|
+
let Some(value) = tokens.next() else { return Err(eol()) };
|
|
834
|
+
if let Some(token) = tokens.next() {
|
|
835
|
+
let why = format!("{}:{}: unexpected token: {}", path, self.lineno(), token);
|
|
836
|
+
return Err(Error::new(TypeError).what(WHAT).why(&why).to_err())
|
|
837
|
+
}
|
|
838
|
+
let value = match self {
|
|
839
|
+
Self::Nx => {
|
|
840
|
+
if name.as_str() != "NCOLS" { return Err(badname("ncols", name.as_str())) }
|
|
841
|
+
match value.parse::<usize>() {
|
|
842
|
+
Ok(value) => MetaValue::NCols(value),
|
|
843
|
+
Err(err) => { return Err(badvalue(err.to_string(), value)) },
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
Self::Ny => {
|
|
847
|
+
if name.as_str() != "NROWS" { return Err(badname("nrows", name.as_str())) }
|
|
848
|
+
match value.parse::<usize>() {
|
|
849
|
+
Ok(value) => MetaValue::NRows(value),
|
|
850
|
+
Err(err) => { return Err(badvalue(err.to_string(), value)) },
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
Self::X0 => {
|
|
854
|
+
if name.as_str() == "XLLCENTER" {
|
|
855
|
+
match value.parse::<f64>() {
|
|
856
|
+
Ok(value) => MetaValue::XLLCenter(value),
|
|
857
|
+
Err(err) => { return Err(badvalue(err.to_string(), value)) },
|
|
858
|
+
}
|
|
859
|
+
} else if name.as_str() == "XLLCORNER" {
|
|
860
|
+
match value.parse::<f64>() {
|
|
861
|
+
Ok(value) => MetaValue::XLLCorner(value),
|
|
862
|
+
Err(err) => { return Err(badvalue(err.to_string(), value)) },
|
|
863
|
+
}
|
|
864
|
+
} else {
|
|
865
|
+
return Err(badname("xllcenter or xllcorner", name.as_str()))
|
|
866
|
+
}
|
|
867
|
+
},
|
|
868
|
+
Self::Y0 => {
|
|
869
|
+
if name.as_str() == "YLLCENTER" {
|
|
870
|
+
match value.parse::<f64>() {
|
|
871
|
+
Ok(value) => MetaValue::YLLCenter(value),
|
|
872
|
+
Err(err) => { return Err(badvalue(err.to_string(), value)) },
|
|
873
|
+
}
|
|
874
|
+
} else if name.as_str() == "YLLCORNER" {
|
|
875
|
+
match value.parse::<f64>() {
|
|
876
|
+
Ok(value) => MetaValue::YLLCorner(value),
|
|
877
|
+
Err(err) => { return Err(badvalue(err.to_string(), value)) },
|
|
878
|
+
}
|
|
879
|
+
} else {
|
|
880
|
+
return Err(badname("yllcenter or yllcorner", name.as_str()))
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
Self::Delta => {
|
|
884
|
+
if name.as_str() != "CELLSIZE" {
|
|
885
|
+
return Err(badname("cellsize", name.as_str()))
|
|
886
|
+
}
|
|
887
|
+
match value.parse::<f64>() {
|
|
888
|
+
Ok(value) => MetaValue::CellSize(value),
|
|
889
|
+
Err(err) => { return Err(badvalue(err.to_string(), value)) },
|
|
890
|
+
}
|
|
891
|
+
},
|
|
892
|
+
Self::NoData => {
|
|
893
|
+
if name.as_str() == "NODATA_VALUE" {
|
|
894
|
+
match value.parse::<f32>() {
|
|
895
|
+
Ok(value) => MetaValue::NoData(Ok(value)),
|
|
896
|
+
Err(err) => { return Err(badvalue(err.to_string(), value)) },
|
|
897
|
+
}
|
|
898
|
+
} else {
|
|
899
|
+
MetaValue::NoData(Err(line))
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
};
|
|
903
|
+
Ok(value)
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
let file = File::open(path)?;
|
|
908
|
+
let mut lines = BufReader::new(file).lines();
|
|
909
|
+
|
|
910
|
+
let nx = MetaType::Nx.parse(&mut lines, path)?.into_usize();
|
|
911
|
+
let ny = MetaType::Ny.parse(&mut lines, path)?.into_usize();
|
|
912
|
+
let x0 = MetaType::X0.parse(&mut lines, path)?;
|
|
913
|
+
let y0 = MetaType::Y0.parse(&mut lines, path)?;
|
|
914
|
+
let dx = MetaType::Delta.parse(&mut lines, path)?.into_f64();
|
|
915
|
+
let dy = dx;
|
|
916
|
+
let x0 = match x0 {
|
|
917
|
+
MetaValue::XLLCorner(x0) => x0 + 0.5 * dx,
|
|
918
|
+
MetaValue::XLLCenter(x0) => x0,
|
|
919
|
+
_ => unreachable!(),
|
|
920
|
+
};
|
|
921
|
+
let y0 = match y0 {
|
|
922
|
+
MetaValue::YLLCorner(y0) => y0 + 0.5 * dy,
|
|
923
|
+
MetaValue::YLLCenter(y0) => y0,
|
|
924
|
+
_ => unreachable!(),
|
|
925
|
+
};
|
|
926
|
+
let nodata = MetaType::NoData.parse(&mut lines, path)?;
|
|
927
|
+
|
|
928
|
+
let (mut lineno, mut line, nodata) = match nodata {
|
|
929
|
+
MetaValue::NoData(value) => match value {
|
|
930
|
+
Ok(value) => (MetaType::NoData.lineno() + 1, lines.next(), value),
|
|
931
|
+
Err(line) => (MetaType::NoData.lineno(), Some(Ok(line)), -f32::INFINITY),
|
|
932
|
+
},
|
|
933
|
+
_ => unreachable!()
|
|
934
|
+
};
|
|
935
|
+
let array = PyArray::<f32>::empty(py, &[ny, nx])?;
|
|
936
|
+
let size = nx * ny;
|
|
937
|
+
if size > 0 {
|
|
938
|
+
let z = unsafe { array.slice_mut()? };
|
|
939
|
+
let mut index: usize = 0;
|
|
940
|
+
'outer: loop {
|
|
941
|
+
let Some(content) = line else { break };
|
|
942
|
+
let Ok(content) = content else {
|
|
943
|
+
let why = format!("{}:{}: unexpected end-of-line", path, lineno);
|
|
944
|
+
return Err(Error::new(TypeError).what(WHAT).why(&why).to_err())
|
|
945
|
+
};
|
|
946
|
+
for token in content.split_ascii_whitespace() {
|
|
947
|
+
if index >= size { break 'outer }
|
|
948
|
+
let row = ny - 1 - (index / nx);
|
|
949
|
+
let col = index % nx;
|
|
950
|
+
z[row * nx + col] = token.parse::<f32>()
|
|
951
|
+
.map(|zi| if zi == nodata { f32::NAN } else { zi })
|
|
952
|
+
.map_err(|err| {
|
|
953
|
+
let why = format!("{}:{}: {}: {}", path, lineno, err, token);
|
|
954
|
+
Error::new(TypeError).what(WHAT).why(&why).to_err()
|
|
955
|
+
})?;
|
|
956
|
+
index += 1;
|
|
957
|
+
}
|
|
958
|
+
lineno += 1;
|
|
959
|
+
line = lines.next();
|
|
960
|
+
}
|
|
961
|
+
if index < size {
|
|
962
|
+
let why = format!("{}: expected {} z-values, found {}", path, size, index);
|
|
963
|
+
return Err(Error::new(TypeError).what(WHAT).why(&why).to_err())
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
let map = Self {
|
|
968
|
+
crs: None,
|
|
969
|
+
nx,
|
|
970
|
+
x0,
|
|
971
|
+
x1: x0 + dx * ((nx - 1) as f64),
|
|
972
|
+
ny,
|
|
973
|
+
y0,
|
|
974
|
+
y1: y0 + dy * ((ny - 1) as f64),
|
|
975
|
+
z: array.into_any().unbind(),
|
|
976
|
+
};
|
|
977
|
+
Ok(map)
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
fn to_ascii(
|
|
981
|
+
&self,
|
|
982
|
+
py: Python,
|
|
983
|
+
path: &str,
|
|
984
|
+
nodata: Option<f32>,
|
|
985
|
+
precision: Option<usize>,
|
|
986
|
+
) -> PyResult<()> {
|
|
987
|
+
let dx = if self.nx > 1 { (self.x1 - self.x0) / ((self.nx - 1) as f64) } else { 0.0 };
|
|
988
|
+
let dy = if self.ny > 1 { (self.y1 - self.y0) / ((self.ny - 1) as f64) } else { 0.0 };
|
|
989
|
+
if (dx.abs() - dy.abs()).abs() > f64::EPSILON {
|
|
990
|
+
return Err(Error::new(TypeError).what("cells").why("not squared").to_err())
|
|
991
|
+
}
|
|
992
|
+
enum Order {
|
|
993
|
+
Decreasing,
|
|
994
|
+
Increasing,
|
|
995
|
+
}
|
|
996
|
+
let xorder = if dx < 0.0 { Order::Decreasing } else { Order::Increasing };
|
|
997
|
+
let yorder = if dy < 0.0 { Order::Decreasing } else { Order::Increasing };
|
|
998
|
+
|
|
999
|
+
let file = File::create(path)?;
|
|
1000
|
+
let mut stream = BufWriter::new(file);
|
|
1001
|
+
write!(stream,
|
|
1002
|
+
"\
|
|
1003
|
+
ncols {}\n\
|
|
1004
|
+
nrows {}\n\
|
|
1005
|
+
xllcenter {}\n\
|
|
1006
|
+
yllcenter {}\n\
|
|
1007
|
+
cellsize {}\n",
|
|
1008
|
+
self.nx,
|
|
1009
|
+
self.ny,
|
|
1010
|
+
if dx > 0.0 { self.x0 } else { self.x1 },
|
|
1011
|
+
if dy > 0.0 { self.y0 } else { self.y1 },
|
|
1012
|
+
dx.abs(),
|
|
1013
|
+
)?;
|
|
1014
|
+
if let Some(nodata) = nodata {
|
|
1015
|
+
match precision {
|
|
1016
|
+
Some(precision) => write!(
|
|
1017
|
+
stream, "nodata_value {:.prec$}\n", nodata, prec=precision
|
|
1018
|
+
)?,
|
|
1019
|
+
None => write!(stream, "nodata_value {}\n", nodata)?,
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
let z: &PyArray<f32> = self.z.extract(py)?;
|
|
1024
|
+
let z = unsafe { z.slice()? };
|
|
1025
|
+
for i in 0..self.ny {
|
|
1026
|
+
let i = match yorder {
|
|
1027
|
+
Order::Increasing => self.ny - 1 - i,
|
|
1028
|
+
Order::Decreasing => i,
|
|
1029
|
+
};
|
|
1030
|
+
for j in 0..self.nx {
|
|
1031
|
+
let jj = match xorder {
|
|
1032
|
+
Order::Increasing => j,
|
|
1033
|
+
Order::Decreasing => self.nx - 1 - j,
|
|
1034
|
+
};
|
|
1035
|
+
let mut zij = z[i * self.nx + jj];
|
|
1036
|
+
if let Some(nodata) = nodata {
|
|
1037
|
+
if zij.is_nan() { zij = nodata }
|
|
1038
|
+
}
|
|
1039
|
+
match precision {
|
|
1040
|
+
Some(precision) => write!(stream, "{:.prec$}", zij, prec=precision)?,
|
|
1041
|
+
None => write!(stream, "{}", zij)?,
|
|
1042
|
+
}
|
|
1043
|
+
let sep = if j < self.nx - 1 { ' ' } else { '\n' };
|
|
1044
|
+
write!(stream, "{}", sep)?
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
Ok(())
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
@@ -745,7 +745,7 @@ impl TryFromBound for MeshShape {
|
|
|
745
745
|
None
|
|
746
746
|
}
|
|
747
747
|
},
|
|
748
|
-
Some("png") | Some("tif") => {
|
|
748
|
+
Some("asc") | Some("ASC") | Some("png") | Some("PNG") | Some("tif") | Some("TIF") => {
|
|
749
749
|
let regular = regular.unwrap_or(false);
|
|
750
750
|
let map = MapParameters::new(padding, origin, regular);
|
|
751
751
|
Some(map)
|
|
@@ -204,6 +204,25 @@ def test_Map():
|
|
|
204
204
|
A = calzone.Geometry(data)["A"]
|
|
205
205
|
assert_allclose(A.surface_area, 6 * 4.0)
|
|
206
206
|
|
|
207
|
+
dem1 = calzone.Map(PREFIX / "assets/map.asc")
|
|
208
|
+
path = Path(TMPDIR.name) / "map.asc"
|
|
209
|
+
dem1.dump(path, nodata=-9999)
|
|
210
|
+
dem2 = calzone.Map(path)
|
|
211
|
+
|
|
212
|
+
for dem in (dem1, dem2):
|
|
213
|
+
assert dem.nx == 4
|
|
214
|
+
assert dem.ny == 3
|
|
215
|
+
assert dem.x0 == -1.5
|
|
216
|
+
assert dem.x1 == 1.5
|
|
217
|
+
assert dem.y0 == -1.0
|
|
218
|
+
assert dem.y1 == 1.0
|
|
219
|
+
expected = (
|
|
220
|
+
(0, 1, 2, 3),
|
|
221
|
+
(4, 5, 6, 7),
|
|
222
|
+
(8, 9, 10, 11),
|
|
223
|
+
)
|
|
224
|
+
assert_allclose(dem.z, expected)
|
|
225
|
+
|
|
207
226
|
|
|
208
227
|
def test_Mesh():
|
|
209
228
|
"""Test the mesh shape."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|