Skip to content

Commit d803d0f

Browse files
authored
feat(python): implement Mapping protocol for IFD and GeoKeyDirectory (#148)
* feat(python): implement Mapping protocol for IFD and GeoKeyDirectory * Add PartialEq derives * remove sample_format
1 parent b53ffbd commit d803d0f

File tree

9 files changed

+447
-5
lines changed

9 files changed

+447
-5
lines changed

python/python/async_tiff/_geo.pyi

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1+
from collections.abc import Iterable
2+
from typing import Any
3+
14
class GeoKeyDirectory:
5+
def keys(self) -> list[str]:
6+
"""A list of string keys representing the GeoKey fields."""
7+
def __eq__(self, value: object) -> bool: ...
8+
def __iter__(self) -> Iterable[str]:
9+
"""An iterable of string keys representing the GeoKey fields."""
10+
def __getitem__(self, key: str) -> Any:
11+
"""Access GeoKey fields by string key."""
12+
213
@property
314
def model_type(self) -> int | None: ...
415
@property

python/python/async_tiff/_ifd.pyi

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from collections.abc import Iterable
2+
from typing import Any
13
from .enums import (
24
CompressionMethod,
35
PhotometricInterpretation,
@@ -11,6 +13,14 @@ from ._geo import GeoKeyDirectory
1113
Value = int | float | str | tuple[int, int] | list[Value]
1214

1315
class ImageFileDirectory:
16+
def keys(self) -> list[str]:
17+
"""A list of string keys representing the IFD fields."""
18+
def __eq__(self, value: object) -> bool: ...
19+
def __iter__(self) -> Iterable[str]:
20+
"""An iterable of string keys representing the IFD fields."""
21+
def __getitem__(self, key: str) -> Any:
22+
"""Access IFD fields by string key."""
23+
1424
@property
1525
def new_subfile_type(self) -> int | None: ...
1626
@property

python/src/geo.rs

Lines changed: 211 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use async_tiff::geo::GeoKeyDirectory;
22
use pyo3::prelude::*;
3+
use pyo3::IntoPyObjectExt;
34

4-
#[pyclass(name = "GeoKeyDirectory")]
5+
#[pyclass(name = "GeoKeyDirectory", frozen, eq)]
6+
#[derive(PartialEq)]
57
pub(crate) struct PyGeoKeyDirectory {
68
#[pyo3(get)]
79
model_type: Option<u16>,
@@ -97,6 +99,214 @@ pub(crate) struct PyGeoKeyDirectory {
9799
vertical_units: Option<u16>,
98100
}
99101

102+
#[pymethods]
103+
impl PyGeoKeyDirectory {
104+
/// This exists to implement the Mapping protocol, so we support `dict(gkd)`.`
105+
fn keys(&self) -> Vec<&'static str> {
106+
let mut keys = vec![];
107+
if self.model_type.is_some() {
108+
keys.push("model_type");
109+
}
110+
if self.raster_type.is_some() {
111+
keys.push("raster_type");
112+
}
113+
if self.citation.is_some() {
114+
keys.push("citation");
115+
}
116+
if self.geographic_type.is_some() {
117+
keys.push("geographic_type");
118+
}
119+
if self.geog_citation.is_some() {
120+
keys.push("geog_citation");
121+
}
122+
if self.geog_geodetic_datum.is_some() {
123+
keys.push("geog_geodetic_datum");
124+
}
125+
if self.geog_prime_meridian.is_some() {
126+
keys.push("geog_prime_meridian");
127+
}
128+
if self.geog_linear_units.is_some() {
129+
keys.push("geog_linear_units");
130+
}
131+
if self.geog_linear_unit_size.is_some() {
132+
keys.push("geog_linear_unit_size");
133+
}
134+
if self.geog_angular_units.is_some() {
135+
keys.push("geog_angular_units");
136+
}
137+
if self.geog_angular_unit_size.is_some() {
138+
keys.push("geog_angular_unit_size");
139+
}
140+
if self.geog_ellipsoid.is_some() {
141+
keys.push("geog_ellipsoid");
142+
}
143+
if self.geog_semi_major_axis.is_some() {
144+
keys.push("geog_semi_major_axis");
145+
}
146+
if self.geog_semi_minor_axis.is_some() {
147+
keys.push("geog_semi_minor_axis");
148+
}
149+
if self.geog_inv_flattening.is_some() {
150+
keys.push("geog_inv_flattening");
151+
}
152+
if self.geog_azimuth_units.is_some() {
153+
keys.push("geog_azimuth_units");
154+
}
155+
if self.geog_prime_meridian_long.is_some() {
156+
keys.push("geog_prime_meridian_long");
157+
}
158+
if self.projected_type.is_some() {
159+
keys.push("projected_type");
160+
}
161+
if self.proj_citation.is_some() {
162+
keys.push("proj_citation");
163+
}
164+
if self.projection.is_some() {
165+
keys.push("projection");
166+
}
167+
if self.proj_coord_trans.is_some() {
168+
keys.push("proj_coord_trans");
169+
}
170+
if self.proj_linear_units.is_some() {
171+
keys.push("proj_linear_units");
172+
}
173+
if self.proj_linear_unit_size.is_some() {
174+
keys.push("proj_linear_unit_size");
175+
}
176+
if self.proj_std_parallel1.is_some() {
177+
keys.push("proj_std_parallel1");
178+
}
179+
if self.proj_std_parallel2.is_some() {
180+
keys.push("proj_std_parallel2");
181+
}
182+
if self.proj_nat_origin_long.is_some() {
183+
keys.push("proj_nat_origin_long");
184+
}
185+
if self.proj_nat_origin_lat.is_some() {
186+
keys.push("proj_nat_origin_lat");
187+
}
188+
if self.proj_false_easting.is_some() {
189+
keys.push("proj_false_easting");
190+
}
191+
if self.proj_false_northing.is_some() {
192+
keys.push("proj_false_northing");
193+
}
194+
if self.proj_false_origin_long.is_some() {
195+
keys.push("proj_false_origin_long");
196+
}
197+
if self.proj_false_origin_lat.is_some() {
198+
keys.push("proj_false_origin_lat");
199+
}
200+
if self.proj_false_origin_easting.is_some() {
201+
keys.push("proj_false_origin_easting");
202+
}
203+
if self.proj_false_origin_northing.is_some() {
204+
keys.push("proj_false_origin_northing");
205+
}
206+
if self.proj_center_long.is_some() {
207+
keys.push("proj_center_long");
208+
}
209+
if self.proj_center_lat.is_some() {
210+
keys.push("proj_center_lat");
211+
}
212+
if self.proj_center_easting.is_some() {
213+
keys.push("proj_center_easting");
214+
}
215+
if self.proj_center_northing.is_some() {
216+
keys.push("proj_center_northing");
217+
}
218+
if self.proj_scale_at_nat_origin.is_some() {
219+
keys.push("proj_scale_at_nat_origin");
220+
}
221+
if self.proj_scale_at_center.is_some() {
222+
keys.push("proj_scale_at_center");
223+
}
224+
if self.proj_azimuth_angle.is_some() {
225+
keys.push("proj_azimuth_angle");
226+
}
227+
if self.proj_straight_vert_pole_long.is_some() {
228+
keys.push("proj_straight_vert_pole_long");
229+
}
230+
if self.vertical.is_some() {
231+
keys.push("vertical");
232+
}
233+
if self.vertical_citation.is_some() {
234+
keys.push("vertical_citation");
235+
}
236+
if self.vertical_datum.is_some() {
237+
keys.push("vertical_datum");
238+
}
239+
if self.vertical_units.is_some() {
240+
keys.push("vertical_units");
241+
}
242+
243+
keys
244+
}
245+
246+
/// This exists to implement the Mapping protocol, so we support `dict(gkd)`.`
247+
fn __iter__<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
248+
self.keys().into_pyobject(py)?.call_method0("__iter__")
249+
}
250+
251+
/// Get a GeoKeyDirectory property by name
252+
/// This exists to implement the Mapping protocol, so we support `dict(gkd)`.`
253+
fn __getitem__<'py>(&self, py: Python<'py>, key: &str) -> PyResult<Bound<'py, PyAny>> {
254+
match key {
255+
"model_type" => self.model_type.into_bound_py_any(py),
256+
"raster_type" => self.raster_type.into_bound_py_any(py),
257+
"citation" => self.citation.as_ref().into_bound_py_any(py),
258+
"geographic_type" => self.geographic_type.into_bound_py_any(py),
259+
"geog_citation" => self.geog_citation.as_ref().into_bound_py_any(py),
260+
"geog_geodetic_datum" => self.geog_geodetic_datum.into_bound_py_any(py),
261+
"geog_prime_meridian" => self.geog_prime_meridian.into_bound_py_any(py),
262+
"geog_linear_units" => self.geog_linear_units.into_bound_py_any(py),
263+
"geog_linear_unit_size" => self.geog_linear_unit_size.into_bound_py_any(py),
264+
"geog_angular_units" => self.geog_angular_units.into_bound_py_any(py),
265+
"geog_angular_unit_size" => self.geog_angular_unit_size.into_bound_py_any(py),
266+
"geog_ellipsoid" => self.geog_ellipsoid.into_bound_py_any(py),
267+
"geog_semi_major_axis" => self.geog_semi_major_axis.into_bound_py_any(py),
268+
"geog_semi_minor_axis" => self.geog_semi_minor_axis.into_bound_py_any(py),
269+
"geog_inv_flattening" => self.geog_inv_flattening.into_bound_py_any(py),
270+
"geog_azimuth_units" => self.geog_azimuth_units.into_bound_py_any(py),
271+
"geog_prime_meridian_long" => self.geog_prime_meridian_long.into_bound_py_any(py),
272+
"projected_type" => self.projected_type.into_bound_py_any(py),
273+
"proj_citation" => self.proj_citation.as_ref().into_bound_py_any(py),
274+
"projection" => self.projection.into_bound_py_any(py),
275+
"proj_coord_trans" => self.proj_coord_trans.into_bound_py_any(py),
276+
"proj_linear_units" => self.proj_linear_units.into_bound_py_any(py),
277+
"proj_linear_unit_size" => self.proj_linear_unit_size.into_bound_py_any(py),
278+
"proj_std_parallel1" => self.proj_std_parallel1.into_bound_py_any(py),
279+
"proj_std_parallel2" => self.proj_std_parallel2.into_bound_py_any(py),
280+
"proj_nat_origin_long" => self.proj_nat_origin_long.into_bound_py_any(py),
281+
"proj_nat_origin_lat" => self.proj_nat_origin_lat.into_bound_py_any(py),
282+
"proj_false_easting" => self.proj_false_easting.into_bound_py_any(py),
283+
"proj_false_northing" => self.proj_false_northing.into_bound_py_any(py),
284+
"proj_false_origin_long" => self.proj_false_origin_long.into_bound_py_any(py),
285+
"proj_false_origin_lat" => self.proj_false_origin_lat.into_bound_py_any(py),
286+
"proj_false_origin_easting" => self.proj_false_origin_easting.into_bound_py_any(py),
287+
"proj_false_origin_northing" => self.proj_false_origin_northing.into_bound_py_any(py),
288+
"proj_center_long" => self.proj_center_long.into_bound_py_any(py),
289+
"proj_center_lat" => self.proj_center_lat.into_bound_py_any(py),
290+
"proj_center_easting" => self.proj_center_easting.into_bound_py_any(py),
291+
"proj_center_northing" => self.proj_center_northing.into_bound_py_any(py),
292+
"proj_scale_at_nat_origin" => self.proj_scale_at_nat_origin.into_bound_py_any(py),
293+
"proj_scale_at_center" => self.proj_scale_at_center.into_bound_py_any(py),
294+
"proj_azimuth_angle" => self.proj_azimuth_angle.into_bound_py_any(py),
295+
"proj_straight_vert_pole_long" => {
296+
self.proj_straight_vert_pole_long.into_bound_py_any(py)
297+
}
298+
"vertical" => self.vertical.into_bound_py_any(py),
299+
"vertical_citation" => self.vertical_citation.as_ref().into_bound_py_any(py),
300+
"vertical_datum" => self.vertical_datum.into_bound_py_any(py),
301+
"vertical_units" => self.vertical_units.into_bound_py_any(py),
302+
_ => Err(pyo3::exceptions::PyKeyError::new_err(format!(
303+
"Unknown IFD property: {}",
304+
key
305+
))),
306+
}
307+
}
308+
}
309+
100310
impl From<PyGeoKeyDirectory> for GeoKeyDirectory {
101311
fn from(value: PyGeoKeyDirectory) -> Self {
102312
Self {

0 commit comments

Comments
 (0)