rle-python 0.2.0__py3-none-any.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.
rle/core/eoo.py ADDED
@@ -0,0 +1,243 @@
1
+ """Extent of Occurrence (EOO) computation for RLE assessments.
2
+
3
+ Provides the ``EOO`` class hierarchy and the ``make_eoo()`` helper for
4
+ computing EOO from a local ``Ecosystems`` instance. Earth Engine EOO support
5
+ lives in the optional ``rle-python-gee`` package.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+
10
+ from rle.core.ecosystems import (
11
+ Ecosystems,
12
+ EcosystemKind,
13
+ )
14
+
15
+
16
+ class EOONotComputedError(Exception):
17
+ """Raised when accessing EOO results before compute() has been called."""
18
+
19
+ def __init__(self):
20
+ super().__init__(
21
+ "EOO has not been computed yet. "
22
+ "Call .compute() to run the computation."
23
+ )
24
+
25
+
26
+ class EOO(ABC):
27
+ """Base class for Extent of Occurrence computations.
28
+
29
+ Subclasses implement ``_compute()`` to calculate the convex hull
30
+ geometry and area.
31
+ """
32
+
33
+ def __init__(self, ecosystems: Ecosystems):
34
+ self._ecosystems = ecosystems
35
+ self._computed = False
36
+ self._geometry = None
37
+ self._area_km2: float | None = None
38
+ self._crs = None
39
+
40
+ @abstractmethod
41
+ def _compute(self) -> None:
42
+ """Run the EOO computation.
43
+
44
+ Must store results so that ``_load_geometry()`` and
45
+ ``_load_area_km2()`` can retrieve them.
46
+ """
47
+ ...
48
+
49
+ @abstractmethod
50
+ def _load_geometry(self):
51
+ """Return the computed convex hull geometry."""
52
+ ...
53
+
54
+ @abstractmethod
55
+ def _load_area_km2(self) -> float:
56
+ """Return the EOO area in km²."""
57
+ ...
58
+
59
+ def compute(self) -> "EOO":
60
+ """Run the EOO computation. Returns self for method chaining."""
61
+ self._compute()
62
+ self._computed = True
63
+ self._geometry = None
64
+ self._area_km2 = None
65
+ return self
66
+
67
+ @property
68
+ def geometry(self):
69
+ """The convex hull geometry. Raises EOONotComputedError if not computed."""
70
+ if not self._computed:
71
+ raise EOONotComputedError()
72
+ if self._geometry is None:
73
+ self._geometry = self._load_geometry()
74
+ return self._geometry
75
+
76
+ @property
77
+ def area_km2(self) -> float:
78
+ """EOO area in km²."""
79
+ if not self._computed:
80
+ raise EOONotComputedError()
81
+ if self._area_km2 is None:
82
+ self._area_km2 = self._load_area_km2()
83
+ return self._area_km2
84
+
85
+ # -- export / visualization ----------------------------------------------
86
+
87
+ def to_geodataframe(self):
88
+ """Return the convex hull as a single-row GeoDataFrame."""
89
+ import geopandas as gpd
90
+
91
+ return gpd.GeoDataFrame(
92
+ {"area_km2": [self.area_km2]},
93
+ geometry=[self.geometry],
94
+ crs=self._crs,
95
+ )
96
+
97
+ def to_gdf_for_viz(self, *, get_fill_color=None, get_line_color=None, **_):
98
+ """Return (gdf, style_dict) for static-image fallback rendering."""
99
+ if get_fill_color is None:
100
+ get_fill_color = [255, 0, 0, 40]
101
+ if get_line_color is None:
102
+ get_line_color = [255, 0, 0, 255]
103
+ return self.to_geodataframe(), {"fill": get_fill_color, "edge": get_line_color}
104
+
105
+ def to_layer(self, *, get_fill_color=None, get_line_color=None):
106
+ """Return a lonboard PolygonLayer of the EOO convex hull."""
107
+ gdf = self.to_geodataframe()
108
+ if gdf.geometry.is_empty.all():
109
+ return []
110
+
111
+ try:
112
+ from lonboard import PolygonLayer
113
+ except ImportError:
114
+ raise ImportError(
115
+ "lonboard is required for visualization. "
116
+ "Install it with: pip install rle-python[viz]"
117
+ ) from None
118
+
119
+ if get_fill_color is None:
120
+ get_fill_color = [255, 0, 0, 40]
121
+ if get_line_color is None:
122
+ get_line_color = [255, 0, 0, 255]
123
+
124
+ return [PolygonLayer.from_geopandas(
125
+ gdf,
126
+ get_fill_color=get_fill_color,
127
+ get_line_color=get_line_color,
128
+ line_width_min_pixels=2,
129
+ )]
130
+
131
+ def to_map(self, **kwargs):
132
+ """Return a lonboard Map showing the EOO convex hull."""
133
+ try:
134
+ from lonboard import Map
135
+ except ImportError:
136
+ raise ImportError(
137
+ "lonboard is required for visualization. "
138
+ "Install it with: pip install rle-python[viz]"
139
+ ) from None
140
+
141
+ return Map(layers=self.to_layer(), **kwargs)
142
+
143
+ # -- display -------------------------------------------------------------
144
+
145
+ def __repr__(self) -> str:
146
+ if not self._computed:
147
+ return f"{type(self).__name__}(not computed)"
148
+ return (
149
+ f"{type(self).__name__}("
150
+ f"area_km2={self.area_km2:,.0f})"
151
+ )
152
+
153
+ def _repr_html_(self) -> str:
154
+ if not self._computed:
155
+ return (
156
+ f"<b>{type(self).__name__}</b><br>"
157
+ f"<i>Not computed — call .compute() to run</i>"
158
+ )
159
+ return (
160
+ f"<b>{type(self).__name__}</b><br>"
161
+ f"EOO: {self.area_km2:,.0f} km²"
162
+ )
163
+
164
+
165
+ class EOOVectorLocal(EOO):
166
+ """EOO from a local vector dataset (GeoDataFrame, GeoJSON, GeoParquet)."""
167
+
168
+ def _compute(self) -> None:
169
+ import geopandas as gpd
170
+
171
+ gdf = self._ecosystems.to_geodataframe()
172
+
173
+ self._crs = gdf.crs or "EPSG:4326"
174
+
175
+ # Union all geometries, then compute convex hull
176
+ union = gdf.geometry.union_all()
177
+ hull = union.convex_hull
178
+
179
+ # Degenerate inputs (single point, collinear points, empty) produce
180
+ # Point/LineString/GeometryCollection which lonboard cannot render.
181
+ geom_type = hull.geom_type if hull is not None and not hull.is_empty else None
182
+ if geom_type not in ("Polygon", "MultiPolygon"):
183
+ from shapely import Polygon
184
+ if hull is None or hull.is_empty:
185
+ hull = Polygon()
186
+ else:
187
+ hull = hull.buffer(0.0001)
188
+
189
+ self._computed_geometry = hull
190
+
191
+ # Compute area in km² using equal-area projection (ESRI:54034)
192
+ hull_gdf = gpd.GeoDataFrame(geometry=[hull], crs=self._crs)
193
+ hull_ea = hull_gdf.to_crs("ESRI:54034")
194
+ self._computed_area = hull_ea.geometry.iloc[0].area / 1e6
195
+
196
+ def _load_geometry(self):
197
+ return self._computed_geometry
198
+
199
+ def _load_area_km2(self) -> float:
200
+ return self._computed_area
201
+
202
+
203
+ # ---------------------------------------------------------------------------
204
+ # Factory
205
+ # ---------------------------------------------------------------------------
206
+
207
+
208
+ def make_eoo(data, **kwargs) -> EOO:
209
+ """Create an EOO from an ``Ecosystems`` instance.
210
+
211
+ Call ``.compute()`` to run the computation before accessing results.
212
+
213
+ Args:
214
+ data: An ``Ecosystems`` instance. For local vector data this returns
215
+ an :class:`EOOVectorLocal`. Earth Engine ecosystems require the
216
+ ``rle-python-gee`` package; construct the EE EOO backend
217
+ explicitly from ``rle.gee``.
218
+ **kwargs: Additional arguments passed to the backend constructor.
219
+
220
+ Returns:
221
+ An EOO instance. Call .compute() to run the computation.
222
+
223
+ Example:
224
+ >>> from rle.core import Ecosystems
225
+ >>> eco = Ecosystems.from_file("ecosystems.geojson", ecosystem_column="ECO_NAME")
226
+ >>> eoo = make_eoo(eco).compute()
227
+ >>> print(eoo.area_km2)
228
+ """
229
+ if isinstance(data, Ecosystems):
230
+ kind = data.kind
231
+ if kind == EcosystemKind.VECTOR_LOCAL:
232
+ return EOOVectorLocal(data, **kwargs)
233
+ if kind in (EcosystemKind.EE_FEATURE_COLLECTION, EcosystemKind.EE_IMAGE):
234
+ raise ValueError(
235
+ f"EOO for {kind.value} requires the 'rle-python-gee' package. "
236
+ f"Install it and construct the Earth Engine EOO backend from rle.gee."
237
+ )
238
+ raise ValueError(f"EOO not yet supported for {kind.value}")
239
+
240
+ raise TypeError(
241
+ "make_eoo expects an Ecosystems instance. Construct one first, e.g. "
242
+ "Ecosystems.from_file(path, ecosystem_column=...) or a backend from rle.gee."
243
+ )
rle/core/registry.py ADDED
@@ -0,0 +1,62 @@
1
+ """Backend discovery for the ``rle`` namespace.
2
+
3
+ Backend distributions (e.g. ``rle-python-gee``) advertise their data-access
4
+ backends through an ``rle.backends`` entry point. Each entry point is a
5
+ callable returning one or more :class:`BackendInfo` records.
6
+
7
+ This registry is for *discovery and introspection* only (e.g. the ``rle
8
+ backends`` CLI command). Backends are constructed explicitly by importing
9
+ their classes — there is no auto-dispatch here.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass
15
+ from importlib.metadata import entry_points
16
+ from typing import Callable, Optional
17
+
18
+ _GROUP = "rle.backends"
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class BackendInfo:
23
+ """Describes a single data-access backend.
24
+
25
+ Attributes:
26
+ name: Stable identifier, e.g. ``"gee-feature-collection"``.
27
+ cls: The backend class (an ``Ecosystems``/``AOOGrid``/``EOO`` subclass).
28
+ capability: One of ``"ecosystems"``, ``"aoo"``, ``"eoo"``.
29
+ distribution: The distribution that provides it, e.g. ``"rle-python-gee"``.
30
+ can_handle: Optional predicate for a future URI/data dispatcher.
31
+ """
32
+
33
+ name: str
34
+ cls: type
35
+ capability: str
36
+ distribution: str = ""
37
+ can_handle: Optional[Callable[[object], bool]] = None
38
+
39
+
40
+ def iter_backends() -> list[BackendInfo]:
41
+ """Return every backend advertised by installed ``rle.backends`` entry points.
42
+
43
+ Entry points whose callable fails to load or run are skipped silently so a
44
+ broken optional backend never breaks discovery of the others.
45
+ """
46
+ infos: list[BackendInfo] = []
47
+ for ep in entry_points(group=_GROUP):
48
+ try:
49
+ factory = ep.load()
50
+ result = factory()
51
+ except Exception:
52
+ continue
53
+ if isinstance(result, (list, tuple)):
54
+ infos.extend(r for r in result if isinstance(r, BackendInfo))
55
+ elif isinstance(result, BackendInfo):
56
+ infos.append(result)
57
+ return infos
58
+
59
+
60
+ def list_backends() -> list[str]:
61
+ """Return the names of all installed backends, sorted."""
62
+ return sorted(b.name for b in iter_backends())
rle/core/rle.py ADDED
@@ -0,0 +1,71 @@
1
+ # https://iucnrle.org/rle-categ-and-criteria
2
+ rle_criteria = {
3
+ "A": {
4
+ "description": "Reduction in geographic distribution",
5
+ "threshold": 0.5
6
+ },
7
+ "B": {
8
+ "description": "Restricted geographic distribution",
9
+ "threshold": 0.5
10
+ },
11
+ "C": {
12
+ "description": "Environmental degradation",
13
+ "threshold": 0.5
14
+ },
15
+ "D": {
16
+ "description": "Disruption of biotic processes and interactions",
17
+ "threshold": 0.5
18
+ },
19
+ "E": {
20
+ "description": "Quantitative analysis that estimates the probability of ecosystem collapse",
21
+ "threshold": 0.5
22
+ }
23
+ }
24
+
25
+ # Source: https://iucnrle.org/rle-categ-and-criteria
26
+ rle_categories = [
27
+ {
28
+ "name": "Collapsed",
29
+ "abbreviation": "CO",
30
+ "threatened": True,
31
+ "background_color": "black",
32
+ },
33
+ {
34
+ "name": "Critically Endangered",
35
+ "abbreviation": "CR",
36
+ "threatened": True,
37
+ "background_color": "red",
38
+ },
39
+ {
40
+ "name": "Endangered",
41
+ "abbreviation": "EN",
42
+ "threatened": True,
43
+ "background_color": "orange",
44
+ },
45
+ {
46
+ "name": "Vulnerable",
47
+ "abbreviation": "VU",
48
+ "threatened": True,
49
+ "background_color": "yellow",
50
+ },
51
+ {
52
+ "name": "Near Threatened",
53
+ "abbreviation": "NT",
54
+ "background_color": "green",
55
+ },
56
+ {
57
+ "name": "Least Concern",
58
+ "abbreviation": "LC",
59
+ "background_color": "darkgreen",
60
+ },
61
+ {
62
+ "name": "Data Deficient",
63
+ "abbreviation": "DD",
64
+ "background_color": "lightgray",
65
+ },
66
+ {
67
+ "name": "Not Evaluated",
68
+ "abbreviation": "NE",
69
+ "background_color": "white",
70
+ }
71
+ ]
rle/core/viz.py ADDED
@@ -0,0 +1,183 @@
1
+ """Visualization helpers that gracefully fall back to static images.
2
+
3
+ The main entry point is :func:`smart_map`, which attempts to build an
4
+ interactive :class:`lonboard.Map`. If any layer construction raises a
5
+ ``max_features`` / "too many" :class:`ValueError`, it falls back to rendering
6
+ the same data as a static matplotlib PNG so that documents (e.g. Quarto
7
+ notebooks) still render instead of failing.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from typing import Any, Iterable, Sequence
12
+
13
+ import numpy as np
14
+
15
+
16
+ _FALLBACK_MARKERS = ("max_features", "too many")
17
+
18
+
19
+ def _is_fallback_error(exc: BaseException) -> bool:
20
+ msg = str(exc)
21
+ return any(marker in msg for marker in _FALLBACK_MARKERS)
22
+
23
+
24
+ def _normalize_specs(specs: Iterable[Any]) -> list[tuple[str, Any, dict | None]]:
25
+ """Normalize heterogeneous spec entries to a uniform internal shape.
26
+
27
+ Each returned entry is one of:
28
+ - ("source", datasource, kwargs_dict) — has .to_layer() / .to_gdf_for_viz()
29
+ - ("layer", prebuilt_lonboard_layer, None)
30
+ """
31
+ normalized: list[tuple[str, Any, dict | None]] = []
32
+ for entry in specs:
33
+ if isinstance(entry, tuple) and len(entry) == 2 and isinstance(entry[1], dict):
34
+ normalized.append(("source", entry[0], entry[1]))
35
+ elif hasattr(entry, "to_layer"):
36
+ normalized.append(("source", entry, {}))
37
+ else:
38
+ normalized.append(("layer", entry, None))
39
+ return normalized
40
+
41
+
42
+ def smart_map(
43
+ specs: Sequence[Any],
44
+ *,
45
+ view_state: dict | None = None,
46
+ title: str | None = None,
47
+ figsize: tuple[float, float] = (8, 8),
48
+ ):
49
+ """Build an interactive ``lonboard.Map`` or fall back to a static PNG.
50
+
51
+ Args:
52
+ specs: Iterable of layer specs. Each entry may be:
53
+ - a datasource with ``.to_layer()`` (uses default styling)
54
+ - a ``(datasource, kwargs)`` tuple forwarded to ``.to_layer(**kwargs)``
55
+ - a pre-built lonboard layer (passed through as-is)
56
+ view_state: Optional initial view state forwarded to ``lonboard.Map``.
57
+ title: Optional title rendered on the static fallback image.
58
+ figsize: Matplotlib figsize for the static fallback.
59
+
60
+ Returns:
61
+ ``lonboard.Map`` on success, or ``IPython.display.Image`` (PNG) on
62
+ fallback. Both render inline in Jupyter / Quarto.
63
+ """
64
+ normalized = _normalize_specs(specs)
65
+
66
+ try:
67
+ layers: list = []
68
+ for kind, obj, kwargs in normalized:
69
+ if kind == "source":
70
+ layers.extend(obj.to_layer(**(kwargs or {})))
71
+ else:
72
+ layers.append(obj)
73
+ except ValueError as e:
74
+ if not _is_fallback_error(e):
75
+ raise
76
+ return _static_fallback(normalized, title=title, figsize=figsize)
77
+
78
+ from lonboard import Map
79
+
80
+ map_kwargs = {}
81
+ if view_state is not None:
82
+ map_kwargs["view_state"] = view_state
83
+ return Map(layers=layers, **map_kwargs)
84
+
85
+
86
+ def _static_fallback(
87
+ normalized: list[tuple[str, Any, dict | None]],
88
+ *,
89
+ title: str | None,
90
+ figsize: tuple[float, float],
91
+ ):
92
+ import io
93
+
94
+ import matplotlib.pyplot as plt
95
+ from IPython.display import Image
96
+
97
+ fig, ax = plt.subplots(figsize=figsize)
98
+
99
+ skipped = 0
100
+ for kind, obj, kwargs in normalized:
101
+ if kind != "source":
102
+ skipped += 1
103
+ continue
104
+ if not hasattr(obj, "to_gdf_for_viz"):
105
+ skipped += 1
106
+ continue
107
+ gdf, style = obj.to_gdf_for_viz(**(kwargs or {}))
108
+ if gdf is None or len(gdf) == 0:
109
+ continue
110
+ _plot_gdf(ax, gdf, style)
111
+
112
+ ax.set_aspect("equal")
113
+ ax.set_xlabel("Longitude")
114
+ ax.set_ylabel("Latitude")
115
+ if title:
116
+ ax.set_title(title)
117
+ if skipped:
118
+ ax.text(
119
+ 0.99, 0.01,
120
+ f"{skipped} pre-built layer(s) omitted in static view",
121
+ transform=ax.transAxes, ha="right", va="bottom",
122
+ fontsize=8, color="gray",
123
+ )
124
+
125
+ buf = io.BytesIO()
126
+ fig.tight_layout()
127
+ fig.savefig(buf, format="png", dpi=120, bbox_inches="tight")
128
+ plt.close(fig)
129
+ return Image(data=buf.getvalue(), format="png")
130
+
131
+
132
+ def _plot_gdf(ax, gdf, style):
133
+ """Plot a GeoDataFrame onto ``ax`` using lonboard-style color kwargs."""
134
+ from pyproj import CRS
135
+
136
+ if gdf.crs is not None and CRS.from_user_input(gdf.crs).to_epsg() != 4326:
137
+ gdf = gdf.to_crs("EPSG:4326")
138
+
139
+ fill = _lonboard_color_to_mpl(style.get("fill"), len(gdf))
140
+ edge = _lonboard_color_to_mpl(style.get("edge"), len(gdf))
141
+
142
+ plot_kwargs: dict[str, Any] = {"ax": ax, "linewidth": 0.5}
143
+ if fill is not None:
144
+ plot_kwargs["color"] = fill
145
+ if edge is not None:
146
+ plot_kwargs["edgecolor"] = edge
147
+ gdf.plot(**plot_kwargs)
148
+
149
+
150
+ def _lonboard_color_to_mpl(color, n_rows: int):
151
+ """Convert lonboard-style RGB(A) colors to matplotlib-friendly floats.
152
+
153
+ Accepts:
154
+ - None (returned as-is)
155
+ - list/tuple of 3 or 4 ints (0-255) → single matplotlib RGBA tuple
156
+ - ndarray shape (N, 3 or 4) uint8/int → array of RGBA tuples (0-1 floats)
157
+ """
158
+ if color is None:
159
+ return None
160
+ arr = np.asarray(color)
161
+ if arr.ndim == 1:
162
+ rgba = _rgba_from_bytes(arr)
163
+ return rgba
164
+ if arr.ndim == 2:
165
+ if arr.shape[0] != n_rows:
166
+ return None
167
+ if np.issubdtype(arr.dtype, np.integer) or arr.max() > 1.0:
168
+ arr = arr.astype(float) / 255.0
169
+ if arr.shape[1] == 3:
170
+ alpha = np.ones((arr.shape[0], 1))
171
+ arr = np.concatenate([arr, alpha], axis=1)
172
+ return arr
173
+ return None
174
+
175
+
176
+ def _rgba_from_bytes(arr: np.ndarray) -> tuple[float, float, float, float]:
177
+ vals = [float(v) for v in arr.tolist()]
178
+ if len(vals) == 3:
179
+ vals.append(255.0)
180
+ r, g, b, a = vals[:4]
181
+ if max(vals) > 1.0:
182
+ r, g, b, a = r / 255.0, g / 255.0, b / 255.0, a / 255.0
183
+ return (r, g, b, a)
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: rle-python
3
+ Version: 0.2.0
4
+ Summary: Core tools for IUCN Red List of Ecosystems analysis with local and cloud data access
5
+ Project-URL: Homepage, https://rle-assessment.github.io/rle-python/
6
+ Project-URL: Documentation, https://rle-assessment.github.io/rle-python/
7
+ Project-URL: Repository, https://github.com/RLE-Assessment/rle-python
8
+ Project-URL: Issues, https://github.com/RLE-Assessment/rle-python/issues
9
+ Author-email: Tyler Erickson <tyler@vorgeo.com>
10
+ License-Expression: Apache-2.0
11
+ License-File: LICENSE
12
+ Keywords: AOO,EOO,IUCN,RLE,Red List of Ecosystems,biodiversity,ecology,geospatial
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: GIS
20
+ Requires-Python: <3.13,>=3.11
21
+ Requires-Dist: fsspec
22
+ Requires-Dist: geopandas>=1.1.3
23
+ Requires-Dist: numpy>=1.24.0
24
+ Requires-Dist: pandas
25
+ Requires-Dist: pyarrow
26
+ Requires-Dist: pyproj
27
+ Requires-Dist: pyyaml>=6.0.3
28
+ Requires-Dist: rasterio>=1.3.0
29
+ Requires-Dist: rasterstats
30
+ Requires-Dist: rioxarray
31
+ Requires-Dist: shapely>=2
32
+ Requires-Dist: typer>=0.20.0
33
+ Provides-Extra: aws
34
+ Requires-Dist: s3fs; extra == 'aws'
35
+ Provides-Extra: gcs
36
+ Requires-Dist: gcsfs; extra == 'gcs'
37
+ Provides-Extra: viz
38
+ Requires-Dist: lonboard; extra == 'viz'
39
+ Requires-Dist: matplotlib>=3.9.0; extra == 'viz'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # rle-python
43
+
44
+ Core tools for **IUCN Red List of Ecosystems (RLE)** analysis.
45
+
46
+ `rle-python` provides the RLE data model and assessment business logic
47
+ (Extent of Occurrence, Area of Occupancy grids, ecosystem code assignment,
48
+ criteria/categories) together with **local and cloud-file data access** — it
49
+ has no Earth Engine dependency.
50
+
51
+ Everything imports under the shared `rle` namespace:
52
+
53
+ ```python
54
+ from rle.core import Ecosystems, make_aoo_grid, make_eoo
55
+
56
+ eco = Ecosystems.from_file("ecosystems.geojson", ecosystem_column="ECO_NAME")
57
+ aoo = make_aoo_grid(eco).compute()
58
+ print(aoo.cell_count, aoo.aoo_km2)
59
+
60
+ eoo = make_eoo(eco).compute()
61
+ print(eoo.area_km2)
62
+ ```
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ pip install rle-python # local files
68
+ pip install rle-python[gcs] # + gs:// GeoParquet (gcsfs)
69
+ pip install rle-python[aws] # + s3:// GeoParquet (s3fs)
70
+ pip install rle-python[viz] # + interactive maps (lonboard) / static fallback
71
+ ```
72
+
73
+ ## Optional backends
74
+
75
+ Additional data sources are provided by separate distributions that install
76
+ into the same `rle` namespace and register themselves for discovery:
77
+
78
+ ```bash
79
+ pip install rle-python-gee # Google Earth Engine backends -> rle.gee
80
+ ```
81
+
82
+ ```python
83
+ from rle.gee import GeeEcosystems
84
+ eco = GeeEcosystems("projects/my-project/assets/ecosystems")
85
+ ```
86
+
87
+ List the backends available in your environment:
88
+
89
+ ```bash
90
+ rle backends
91
+ ```
92
+
93
+ ## Layout
94
+
95
+ `rle-python` ships the `rle.core` package under a PEP 420 namespace (`rle/`),
96
+ so multiple `rle-*` distributions can coexist without conflict.
@@ -0,0 +1,16 @@
1
+ rle/core/__init__.py,sha256=UMRyfBaTAaZMLzlHE-k8WRS6gqPtRCPCLA6HAL3rmFY,1843
2
+ rle/core/_entrypoints.py,sha256=L9if3ON2aRz7xRlRLIoGA6mXlQ6GAMXQ1zaqFl7myT8,1333
3
+ rle/core/aoo.py,sha256=mR7sCWNG0RlpM42BXUCr0vXcwfPyBd_VHqgeEed-WJU,29730
4
+ rle/core/aoo_grid.py,sha256=F5WzEbZ5RcYcjjbaGOeMz8EdlQ5B6tRwjhHw8DV6_Hc,1934
5
+ rle/core/cli.py,sha256=2zTjmTPlEgTphIBiAf8tn2MSk9K1SuZVrGZVbxzfEt0,1250
6
+ rle/core/ecosystem_codes.py,sha256=Ybb5cNqW0kt8ljt1ayfTwMfgiRRL_LBp_LE-CGcnMdw,3840
7
+ rle/core/ecosystems.py,sha256=ynzi_tELB7194UoVkrpmVvuQ_xBZjpxim897Ligd9-U,26867
8
+ rle/core/eoo.py,sha256=2p4lRvyezY6EZDDYVBX7kI9dAjGDxRjr0GwnZHh-Pj8,8030
9
+ rle/core/registry.py,sha256=7KprFt9vg0tkgvafTJD6NtD6BtWoei3cSG0bmJIz-_I,2106
10
+ rle/core/rle.py,sha256=-3PosA-gqqVIcVlE2YHbbGHXVnJjQfWOzhF-Ah2zBCI,1731
11
+ rle/core/viz.py,sha256=iH7kfVjjaD__ywU2r5HfT7PlNWSS0mE1fxe7jncLwHE,5889
12
+ rle_python-0.2.0.dist-info/METADATA,sha256=nI5QKjkr1fA6-S2izNzhTReDEhlB3BKoQpn2KZKeaGw,3111
13
+ rle_python-0.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
14
+ rle_python-0.2.0.dist-info/entry_points.txt,sha256=mIPm-a2k-v17XI-X7ZZ91Ml5UkT0_O2tZyEv-sP2Ex4,95
15
+ rle_python-0.2.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
16
+ rle_python-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ rle = rle.core.cli:app
3
+
4
+ [rle.backends]
5
+ core = rle.core._entrypoints:register