pycontrails 0.40.1__cp39-cp39-macosx_11_0_arm64.whl → 0.42.0__cp39-cp39-macosx_11_0_arm64.whl

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.

Potentially problematic release.


This version of pycontrails might be problematic. Click here for more details.

pycontrails/core/met.py CHANGED
@@ -1683,12 +1683,12 @@ class MetDataArray(MetBase):
1683
1683
  fill_value: float = 0.0,
1684
1684
  iso_value: float | None = None,
1685
1685
  min_area: float = 0.0,
1686
- min_area_to_iterate: float = 0.0,
1687
1686
  epsilon: float = 0.0,
1688
1687
  precision: int | None = None,
1689
- depth: int = 2,
1688
+ interiors: bool = True,
1690
1689
  convex_hull: bool = False,
1691
1690
  include_altitude: bool = False,
1691
+ properties: dict[str, Any] | None = None,
1692
1692
  ) -> dict[str, Any]:
1693
1693
  """Create GeoJSON Feature artifact from spatial array on a single level and time slice.
1694
1694
 
@@ -1696,7 +1696,7 @@ class MetDataArray(MetBase):
1696
1696
  `GeoJSON Polygon specification <https://www.rfc-editor.org/rfc/rfc7946.html#section-3.1.6>`.
1697
1697
  Polygons may also contain interior linear rings (holes). This method does not support
1698
1698
  nesting beyond the GeoJSON specification. See the :mod:`pycontrails.core.polygon`
1699
- for additional polygon support, including arbitrary nesting depth.
1699
+ for additional polygon support.
1700
1700
 
1701
1701
  .. versionchanged:: 0.25.12
1702
1702
 
@@ -1717,6 +1717,12 @@ class MetDataArray(MetBase):
1717
1717
 
1718
1718
  Change default value of ``epsilon`` from 0.15 to 0.
1719
1719
 
1720
+ .. versionchanged:: 0.41.0
1721
+
1722
+ Convert continuous fields to binary fields before computing polygons.
1723
+ The parameters ``max_area`` and ``epsilon`` are now expressed in terms of
1724
+ longitude/latitude units instead of pixels.
1725
+
1720
1726
  Parameters
1721
1727
  ----------
1722
1728
  level : float, optional
@@ -1733,28 +1739,20 @@ class MetDataArray(MetBase):
1733
1739
  iso_value : float, optional
1734
1740
  Value in field to create iso-surface.
1735
1741
  Defaults to the average of the min and max value of the array. (This is the
1736
- same convention as used by ``skimage``.) Used for the ``level`` parameter
1737
- in :func:`skimage.measure.find_contours`.
1742
+ same convention as used by ``skimage``.)
1738
1743
  min_area : float, optional
1739
1744
  Minimum area of each polygon. Polygons with area less than ``min_area`` are
1740
- not included in the output. The unit of this parameter is intrinsic to the
1741
- underlying array: an individual "grid cell" has unit 1. Set to 0 to omit any
1742
- polygon filtering based on a minimal area conditional. By default, 0.0.
1743
- min_area_to_iterate : float, optional
1744
- Minimum area of exterior polygon to search for interior rings. Polygons with
1745
- area less than ``min_area_to_iterate`` are not searched for interior rings.
1746
- The same unit convention as ``min_area`` applies. Set to 0 to omit any polygon
1747
- filtering based on a minimal area conditional. By default, 0.0.
1748
- A reasonable value for this parameter is 10x the ``min_area`` parameter.
1745
+ not included in the output. The unit of this parameter is in longitude/latitude
1746
+ degrees squared. Set to 0 to omit any polygon filtering based on a minimal area
1747
+ conditional. By default, 0.0.
1749
1748
  epsilon : float, optional
1750
1749
  Control the extent to which the polygon is simplified. A value of 0 does not alter
1751
- the geometry of the polygon. Values in the interval [0, 0.2] are reasonable.
1750
+ the geometry of the polygon. The unit of this parameter is in longitude/latitude
1751
+ degrees. By default, 0.0.
1752
1752
  precision : int, optional
1753
1753
  Number of decimal places to round coordinates to. If None, no rounding is performed.
1754
- depth : int, optional
1755
- Number of levels of nesting to include in the output. By default, 2, meaning that
1756
- the exterior and interior rings are both included. Set to 1 to include only the
1757
- exterior ring. Must be 1 or 2.
1754
+ interiors : bool, optional
1755
+ If True, include interior linear rings (holes) in the output. True by default.
1758
1756
  convex_hull : bool, optional
1759
1757
  EXPERIMENTAL. If True, compute the convex hull of each polygon. Only implemented
1760
1758
  for depth=1. False by default. A warning is issued if the underlying algorithm
@@ -1763,6 +1761,8 @@ class MetDataArray(MetBase):
1763
1761
  If True, include the array altitude [:math:`m`] as a z-coordinate in the
1764
1762
  `GeoJSON output <https://www.rfc-editor.org/rfc/rfc7946#section-3.1.1>`.
1765
1763
  False by default.
1764
+ properties : dict, optional
1765
+ Additional properties to include in the GeoJSON output. By default, None.
1766
1766
 
1767
1767
  Returns
1768
1768
  -------
@@ -1772,8 +1772,7 @@ class MetDataArray(MetBase):
1772
1772
  See Also
1773
1773
  --------
1774
1774
  :meth:`to_polyhedra`
1775
- :func:`skimage.measure.find_contours`
1776
- :func:`pycontrails.core.polygons.find_contours_to_depth`
1775
+ :func:`pycontrails.core.polygons.find_multipolygons`
1777
1776
 
1778
1777
  Examples
1779
1778
  --------
@@ -1784,34 +1783,24 @@ class MetDataArray(MetBase):
1784
1783
  >>> mda.shape
1785
1784
  (1440, 721, 1, 1)
1786
1785
 
1787
- >>> pprint(mda.to_polygon_feature(iso_value=239.5, precision=2, epsilon=0.2))
1788
- {'geometry': {'coordinates': [[[[43.66, -33.5],
1789
- [43.5, -33.29],
1790
- [43.44, -33.75],
1791
- [43.5, -34.1],
1792
- [43.67, -33.75],
1793
- [43.66, -33.5]]],
1794
- [[[167.83, -22.5],
1795
- [167.75, -22.31],
1796
- [167.66, -22.5],
1797
- [167.83, -22.5]]]],
1786
+ >>> pprint(mda.to_polygon_feature(iso_value=239.5, precision=2, epsilon=0.1))
1787
+ {'geometry': {'coordinates': [[[[167.88, -22.5],
1788
+ [167.75, -22.38],
1789
+ [167.62, -22.5],
1790
+ [167.75, -22.62],
1791
+ [167.88, -22.5]]],
1792
+ [[[43.38, -33.5],
1793
+ [43.5, -34.12],
1794
+ [43.62, -33.5],
1795
+ [43.5, -33.38],
1796
+ [43.38, -33.5]]]],
1798
1797
  'type': 'MultiPolygon'},
1799
1798
  'properties': {},
1800
1799
  'type': 'Feature'}
1801
1800
 
1802
1801
  """
1803
- # Not completely sure if this is necessary ...
1804
- if iso_value is not None and fill_value > iso_value:
1805
- warnings.warn(
1806
- f"The fill_value {fill_value} expected to be less than the iso_value {iso_value}"
1807
- )
1808
-
1809
- if depth not in (1, 2):
1810
- raise ValueError(
1811
- f"Invalid depth value {depth}. Must be 1 or 2. See docstring for details."
1812
- )
1813
- if convex_hull and depth != 1:
1814
- raise ValueError(f"Set depth=1 to use the 'convex_hull' parameter. Found depth={depth}")
1802
+ if convex_hull and interiors:
1803
+ raise ValueError("Set 'interiors=False' to use the 'convex_hull' parameter.")
1815
1804
 
1816
1805
  arr, altitude = _extract_2d_arr_and_altitude(self, level, time)
1817
1806
  if not include_altitude:
@@ -1825,52 +1814,48 @@ class MetDataArray(MetBase):
1825
1814
 
1826
1815
  np.nan_to_num(arr, copy=False, nan=fill_value)
1827
1816
 
1828
- # Pad along axes to ensure polygons are closed
1829
- arr = np.pad(arr, pad_width=1, constant_values=fill_value)
1830
-
1831
1817
  # default iso_value
1832
1818
  if iso_value is None:
1833
- iso_value = (np.nanmax(arr) + np.nanmin(arr)) / 2
1819
+ iso_value = (np.max(arr) + np.min(arr)) / 2
1834
1820
  warnings.warn(f"The 'iso_value' parameter was not specified. Using value: {iso_value}")
1835
1821
 
1836
1822
  # We'll get a nice error message if dependencies are not installed
1837
1823
  import pycontrails.core.polygon as polygon
1838
1824
 
1839
- nc = polygon.find_contours_to_depth(
1825
+ # Convert to nested lists of coordinates for GeoJSON representation
1826
+ longitude: npt.NDArray[np.float_] = self.variables["longitude"].values
1827
+ latitude: npt.NDArray[np.float_] = self.variables["latitude"].values
1828
+
1829
+ mp = polygon.find_multipolygon(
1840
1830
  arr,
1841
1831
  threshold=iso_value,
1842
1832
  min_area=min_area,
1843
- min_area_to_iterate=min_area_to_iterate,
1844
1833
  epsilon=epsilon,
1845
- depth=depth,
1834
+ interiors=interiors,
1846
1835
  convex_hull=convex_hull,
1836
+ longitude=longitude,
1837
+ latitude=latitude,
1838
+ precision=precision,
1847
1839
  )
1848
1840
 
1849
- # Convert to nested lists of coordinates for GeoJSON representation
1850
- longitude = self.variables["longitude"].values
1851
- latitude = self.variables["latitude"].values
1852
-
1853
- multipolygons: list[list[list[list[float]]]] = []
1854
- for exterior_poly in nc:
1855
- poly = [
1856
- polygon.contour_to_lon_lat(p.contour, longitude, latitude, altitude, precision)
1857
- for p in [exterior_poly, *exterior_poly]
1858
- ]
1859
- multipolygons.append(poly)
1860
-
1861
- return {
1862
- "type": "Feature",
1863
- "properties": {},
1864
- "geometry": {"type": "MultiPolygon", "coordinates": multipolygons},
1865
- }
1841
+ return polygon.multipolygon_to_geojson(mp, altitude, properties)
1866
1842
 
1867
- def to_polygon_feature_collection(self, **kwargs: Any) -> dict[str, Any]:
1843
+ def to_polygon_feature_collection(
1844
+ self,
1845
+ time: np.datetime64 | datetime | None = None,
1846
+ fill_value: float = 0.0,
1847
+ iso_value: float | None = None,
1848
+ min_area: float = 0.0,
1849
+ epsilon: float = 0.0,
1850
+ precision: int | None = None,
1851
+ interiors: bool = True,
1852
+ convex_hull: bool = False,
1853
+ include_altitude: bool = False,
1854
+ properties: dict[str, Any] | None = None,
1855
+ ) -> dict[str, Any]:
1868
1856
  """Create GeoJSON FeatureCollection artifact from spatial array at time slice.
1869
1857
 
1870
- Parameters
1871
- ----------
1872
- **kwargs : Any
1873
- Passed into :meth:`to_polygon_feature`.
1858
+ See the :meth:`to_polygon_feature` method for a description of the parameters.
1874
1859
 
1875
1860
  Returns
1876
1861
  -------
@@ -1878,13 +1863,26 @@ class MetDataArray(MetBase):
1878
1863
  Python representation of GeoJSON FeatureCollection. This dictionary is
1879
1864
  comprised of individual GeoJON Features, one per :attr:`self.data["level"]`.
1880
1865
  """
1866
+ base_properties = properties or {}
1881
1867
  features = []
1882
1868
  for level in self.data["level"]:
1883
- feature = self.to_polygon_feature(level=level, **kwargs)
1884
- # Add some level metadata
1885
- feature["properties"]["level"] = level.item()
1886
- to_update = {f"level_{k}": v for k, v in self.data["level"].attrs.items()}
1887
- feature["properties"].update(to_update)
1869
+ properties = base_properties.copy()
1870
+ properties.update(level=level.item())
1871
+ properties.update({f"level_{k}": v for k, v in self.data["level"].attrs.items()})
1872
+
1873
+ feature = self.to_polygon_feature(
1874
+ level=level,
1875
+ time=time,
1876
+ fill_value=fill_value,
1877
+ iso_value=iso_value,
1878
+ min_area=min_area,
1879
+ epsilon=epsilon,
1880
+ precision=precision,
1881
+ interiors=interiors,
1882
+ convex_hull=convex_hull,
1883
+ include_altitude=include_altitude,
1884
+ properties=properties,
1885
+ )
1888
1886
  features.append(feature)
1889
1887
 
1890
1888
  return {
@@ -2317,6 +2315,8 @@ def _extract_2d_arr_and_altitude(
2317
2315
  altitude = da["altitude"].values.item() # item not implemented on dask arrays
2318
2316
  except KeyError:
2319
2317
  altitude = None
2318
+ else:
2319
+ altitude = round(altitude)
2320
2320
 
2321
2321
  return arr, altitude
2322
2322