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.
Files changed (72) hide show
  1. {calzone-1.1.6 → calzone-1.1.8}/Cargo.toml +3 -1
  2. {calzone-1.1.6/src/python/calzone.egg-info → calzone-1.1.8}/PKG-INFO +1 -1
  3. {calzone-1.1.6 → calzone-1.1.8}/pyproject.toml +1 -1
  4. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/map.rs +398 -10
  5. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/volume.rs +1 -1
  6. {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone/__init__.py +1 -1
  7. {calzone-1.1.6 → calzone-1.1.8/src/python/calzone.egg-info}/PKG-INFO +1 -1
  8. {calzone-1.1.6 → calzone-1.1.8}/tests/test_geometry.py +19 -0
  9. {calzone-1.1.6 → calzone-1.1.8}/COPYING.LESSER +0 -0
  10. {calzone-1.1.6 → calzone-1.1.8}/LICENSE +0 -0
  11. {calzone-1.1.6 → calzone-1.1.8}/MANIFEST.in +0 -0
  12. {calzone-1.1.6 → calzone-1.1.8}/README.md +0 -0
  13. {calzone-1.1.6 → calzone-1.1.8}/setup.cfg +0 -0
  14. {calzone-1.1.6 → calzone-1.1.8}/src/calzone.h +0 -0
  15. {calzone-1.1.6 → calzone-1.1.8}/src/cxx.rs +0 -0
  16. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/bytes.rs +0 -0
  17. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/goupil.rs +0 -0
  18. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/materials/gate.rs +0 -0
  19. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/materials/hash.rs +0 -0
  20. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/materials.cc +0 -0
  21. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/materials.rs +0 -0
  22. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/mesh.cc +0 -0
  23. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/mesh.h +0 -0
  24. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/mesh.rs +0 -0
  25. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/mulder.rs +0 -0
  26. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/solids.cc +0 -0
  27. {calzone-1.1.6 → calzone-1.1.8}/src/geometry/solids.h +0 -0
  28. {calzone-1.1.6 → calzone-1.1.8}/src/geometry.cc +0 -0
  29. {calzone-1.1.6 → calzone-1.1.8}/src/geometry.rs +0 -0
  30. {calzone-1.1.6 → calzone-1.1.8}/src/lib.rs +0 -0
  31. {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone/__main__.py +0 -0
  32. {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone.egg-info/SOURCES.txt +0 -0
  33. {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone.egg-info/dependency_links.txt +0 -0
  34. {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone.egg-info/entry_points.txt +0 -0
  35. {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone.egg-info/requires.txt +0 -0
  36. {calzone-1.1.6 → calzone-1.1.8}/src/python/calzone.egg-info/top_level.txt +0 -0
  37. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/geometry.cc +0 -0
  38. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/geometry.h +0 -0
  39. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/physics.cc +0 -0
  40. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/physics.h +0 -0
  41. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/physics.rs +0 -0
  42. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/random.cc +0 -0
  43. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/random.h +0 -0
  44. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/random.rs +0 -0
  45. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/sampler.cc +0 -0
  46. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/sampler.h +0 -0
  47. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/sampler.rs +0 -0
  48. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/source.cc +0 -0
  49. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/source.h +0 -0
  50. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/source.rs +0 -0
  51. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/tracker.cc +0 -0
  52. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/tracker.h +0 -0
  53. {calzone-1.1.6 → calzone-1.1.8}/src/simulation/tracker.rs +0 -0
  54. {calzone-1.1.6 → calzone-1.1.8}/src/simulation.cc +0 -0
  55. {calzone-1.1.6 → calzone-1.1.8}/src/simulation.rs +0 -0
  56. {calzone-1.1.6 → calzone-1.1.8}/src/utils/convert.cc +0 -0
  57. {calzone-1.1.6 → calzone-1.1.8}/src/utils/data.rs +0 -0
  58. {calzone-1.1.6 → calzone-1.1.8}/src/utils/error.cc +0 -0
  59. {calzone-1.1.6 → calzone-1.1.8}/src/utils/error.rs +0 -0
  60. {calzone-1.1.6 → calzone-1.1.8}/src/utils/export.rs +0 -0
  61. {calzone-1.1.6 → calzone-1.1.8}/src/utils/extract.rs +0 -0
  62. {calzone-1.1.6 → calzone-1.1.8}/src/utils/float.rs +0 -0
  63. {calzone-1.1.6 → calzone-1.1.8}/src/utils/io.rs +0 -0
  64. {calzone-1.1.6 → calzone-1.1.8}/src/utils/namespace.rs +0 -0
  65. {calzone-1.1.6 → calzone-1.1.8}/src/utils/numpy.rs +0 -0
  66. {calzone-1.1.6 → calzone-1.1.8}/src/utils/os.cc +0 -0
  67. {calzone-1.1.6 → calzone-1.1.8}/src/utils/units.cc +0 -0
  68. {calzone-1.1.6 → calzone-1.1.8}/src/utils/units.rs +0 -0
  69. {calzone-1.1.6 → calzone-1.1.8}/src/utils.rs +0 -0
  70. {calzone-1.1.6 → calzone-1.1.8}/tests/test_examples.py +0 -0
  71. {calzone-1.1.6 → calzone-1.1.8}/tests/test_materials.py +0 -0
  72. {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.6"
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calzone
3
- Version: 1.1.6
3
+ Version: 1.1.8
4
4
  Summary: A Geant4 Python wrapper.
5
5
  Author-email: Valentin Niess <valentin.niess@gmail.com>
6
6
  License: LGPLv3
@@ -15,7 +15,7 @@ classifiers = [
15
15
  "Intended Audience :: Science/Research",
16
16
  "Topic :: Scientific/Engineering :: Physics"
17
17
  ]
18
- version = "1.1.6"
18
+ version = "1.1.8"
19
19
  requires-python = ">=3.7.0"
20
20
  dependencies = [
21
21
  "numpy >= 1.6.0",
@@ -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::from_geotiff(&any)
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 from_geotiff<'py>(geotiff: &Bound<'py, PyAny>) -> PyResult<Self> {
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)
@@ -11,4 +11,4 @@ def init():
11
11
  init()
12
12
  del init
13
13
 
14
- VERSION = "1.1.6"
14
+ VERSION = "1.1.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calzone
3
- Version: 1.1.6
3
+ Version: 1.1.8
4
4
  Summary: A Geant4 Python wrapper.
5
5
  Author-email: Valentin Niess <valentin.niess@gmail.com>
6
6
  License: LGPLv3
@@ -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