cubexpress 0.1.0__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.
Potentially problematic release.
This version of cubexpress might be problematic. Click here for more details.
- cubexpress-0.1.0/LICENSE +22 -0
- cubexpress-0.1.0/PKG-INFO +305 -0
- cubexpress-0.1.0/README.md +282 -0
- cubexpress-0.1.0/cubexpress/__init__.py +18 -0
- cubexpress-0.1.0/cubexpress/conversion.py +73 -0
- cubexpress-0.1.0/cubexpress/download.py +347 -0
- cubexpress-0.1.0/cubexpress/geotyping.py +488 -0
- cubexpress-0.1.0/pyproject.toml +118 -0
cubexpress-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, AndesDataCube
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: cubexpress
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python package for efficient processing of cubic earth observation (EO) data
|
|
5
|
+
Home-page: https://github.com/andesdatacube/cubexpress/
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: Julio Contreras
|
|
8
|
+
Author-email: contrerasnetk@gmail.com
|
|
9
|
+
Requires-Python: >=3.9,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Dist: numpy (>=1.25.2)
|
|
17
|
+
Requires-Dist: pandas (>=2.0.3)
|
|
18
|
+
Requires-Dist: utm (>=0.8.0,<0.9.0)
|
|
19
|
+
Project-URL: Documentation, https://andesdatacube.github.io/cubexpress/
|
|
20
|
+
Project-URL: Repository, https://github.com/andesdatacube/cubexpress/
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
<h1></h1>
|
|
24
|
+
|
|
25
|
+
<p align="center">
|
|
26
|
+
<img src="./docs/logo_cubexpress.png" width="39%">
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<p align="center">
|
|
30
|
+
<em>A Python package for efficient processing of cubic earth observation (EO) data</em> 🚀
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
<p align="center">
|
|
34
|
+
<a href='https://pypi.python.org/pypi/cubexpress'>
|
|
35
|
+
<img src='https://img.shields.io/pypi/v/cubexpress.svg' alt='PyPI' />
|
|
36
|
+
</a>
|
|
37
|
+
<a href="https://opensource.org/licenses/MIT" target="_blank">
|
|
38
|
+
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License">
|
|
39
|
+
</a>
|
|
40
|
+
<a href="https://github.com/psf/black" target="_blank">
|
|
41
|
+
<img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Black">
|
|
42
|
+
</a>
|
|
43
|
+
<a href="https://pycqa.github.io/isort/" target="_blank">
|
|
44
|
+
<img src="https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336" alt="isort">
|
|
45
|
+
</a>
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
**GitHub**: [https://github.com/andesdatacube/cubexpress/](https://github.com/andesdatacube/cubexpress/) 🌐
|
|
51
|
+
|
|
52
|
+
**PyPI**: [https://pypi.org/project/cubexpress/](https://pypi.org/project/cubexpress/) 🛠️
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## **Overview**
|
|
57
|
+
|
|
58
|
+
**CubeXpress** is a Python package designed to **simplify and accelerate** the process of working with Google Earth Engine (GEE) data cubes. With features like multi-threaded downloads, automatic subdivision of large requests, and direct pixel-level computations on GEE, **CubeXpress** helps you handle massive datasets with ease.
|
|
59
|
+
|
|
60
|
+
## **Key Features**
|
|
61
|
+
- **Fast Image and Collection Downloads**
|
|
62
|
+
Retrieve single images or entire collections at once, taking advantage of multi-threaded requests.
|
|
63
|
+
- **Automatic Tiling**
|
|
64
|
+
Large images are split ("quadsplit") into smaller sub-tiles, preventing errors with GEE’s size limits.
|
|
65
|
+
- **Direct Pixel Computations**
|
|
66
|
+
Perform computations (e.g., band math) directly on GEE, then fetch results in a single step.
|
|
67
|
+
- **Scalable & Efficient**
|
|
68
|
+
Optimized memory usage and parallelism let you handle complex tasks in big data environments.
|
|
69
|
+
|
|
70
|
+
## **Installation**
|
|
71
|
+
Install the latest version from PyPI:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install cubexpress
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
> **Note**: You need a valid Google Earth Engine account and `earthengine-api` installed (`pip install earthengine-api`). Also run `ee.Initialize()` before using CubeXpress.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## **Basic Usage**
|
|
82
|
+
|
|
83
|
+
### **Download a single `ee.Image`**
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import ee
|
|
87
|
+
import cubexpress
|
|
88
|
+
|
|
89
|
+
# Initialize Earth Engine
|
|
90
|
+
ee.Initialize(project="your-project-id")
|
|
91
|
+
|
|
92
|
+
# Create a raster transform
|
|
93
|
+
geotransform = cubexpress.lonlat2rt(
|
|
94
|
+
lon=-76.5,
|
|
95
|
+
lat=-9.5,
|
|
96
|
+
edge_size=128, # Width=Height=128 pixels
|
|
97
|
+
scale=90 # 90m resolution
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Define a single Request
|
|
101
|
+
request = cubexpress.Request(
|
|
102
|
+
id="dem_test",
|
|
103
|
+
raster_transform=geotransform,
|
|
104
|
+
bands=["elevation"],
|
|
105
|
+
image="NASA/NASADEM_HGT/001" # Note: you can wrap with ee.Image("NASA/NASADEM_HGT/001").divide(10000) if needed
|
|
106
|
+
|
|
107
|
+
# Build the RequestSet
|
|
108
|
+
cube_requests = cubexpress.RequestSet(requestset=[request])
|
|
109
|
+
|
|
110
|
+
# Download with multi-threading
|
|
111
|
+
cubexpress.getcube(
|
|
112
|
+
request=cube_requests,
|
|
113
|
+
output_path="output_dem",
|
|
114
|
+
nworkers=4,
|
|
115
|
+
max_deep_level=5
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This will create a GeoTIFF named `dem_test.tif` in the `output_dem` folder, containing the elevation band.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
### **Download pixel values from an ee.ImageCollection**
|
|
125
|
+
|
|
126
|
+
You can fetch multiple images by constructing a `RequestSet` with several `Request` objects. For example, filter Sentinel-2 images near a point:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
import ee
|
|
130
|
+
import cubexpress
|
|
131
|
+
|
|
132
|
+
ee.Initialize(project="your-project-id")
|
|
133
|
+
|
|
134
|
+
# Filter a Sentinel-2 collection
|
|
135
|
+
point = ee.Geometry.Point([-97.59, 33.37])
|
|
136
|
+
collection = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED") \
|
|
137
|
+
.filterBounds(point) \
|
|
138
|
+
.filterDate('2024-01-01', '2024-01-31')
|
|
139
|
+
|
|
140
|
+
# Extract image IDs
|
|
141
|
+
image_ids = collection.aggregate_array('system:id').getInfo()
|
|
142
|
+
|
|
143
|
+
# Set the geotransform
|
|
144
|
+
geotransform = cubexpress.lonlat2rt(
|
|
145
|
+
lon=-97.59,
|
|
146
|
+
lat=33.37,
|
|
147
|
+
edge_size=512,
|
|
148
|
+
scale=10
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Build multiple requests
|
|
152
|
+
requests = [
|
|
153
|
+
cubexpress.Request(
|
|
154
|
+
id=f"s2test_{i}",
|
|
155
|
+
raster_transform=geotransform,
|
|
156
|
+
bands=["B4", "B3", "B2"],
|
|
157
|
+
image=image_id # Note: you can wrap with ee.Image(image_id).divide(10000) if needed
|
|
158
|
+
)
|
|
159
|
+
for i, image_id in enumerate(image_ids)
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
# Create the RequestSet
|
|
163
|
+
cube_requests = cubexpress.RequestSet(requestset=requests)
|
|
164
|
+
|
|
165
|
+
# Download
|
|
166
|
+
cubexpress.getcube(
|
|
167
|
+
request=cube_requests,
|
|
168
|
+
output_path="output_sentinel",
|
|
169
|
+
nworkers=4,
|
|
170
|
+
max_deep_level=5
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### **Process and extract a pixel from an ee.Image**
|
|
177
|
+
If you provide an `ee.Image` with custom calculations (e.g., `.divide(10000)`, `.normalizedDifference(...)`), CubeXpress can run those on GEE, then download the result. For large results, it automatically splits the image into sub-tiles.
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
import ee
|
|
181
|
+
import cubexpress
|
|
182
|
+
|
|
183
|
+
ee.Initialize(project="your-project-id")
|
|
184
|
+
|
|
185
|
+
# Example: NDVI from Sentinel-2
|
|
186
|
+
image = ee.Image("COPERNICUS/S2_HARMONIZED/20170804T154911_20170804T155116_T18SUJ") \
|
|
187
|
+
.normalizedDifference(["B8", "B4"]) \
|
|
188
|
+
.rename("NDVI")
|
|
189
|
+
|
|
190
|
+
geotransform = cubexpress.lonlat2rt(
|
|
191
|
+
lon=-76.59,
|
|
192
|
+
lat=38.89,
|
|
193
|
+
edge_size=256,
|
|
194
|
+
scale=10
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
request = cubexpress.Request(
|
|
198
|
+
id="ndvi_test",
|
|
199
|
+
raster_transform=geotransform,
|
|
200
|
+
bands=["NDVI"],
|
|
201
|
+
image=image # custom expression
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
cube_requests = cubexpress.RequestSet(requestset=[request])
|
|
205
|
+
|
|
206
|
+
cubexpress.getcube(
|
|
207
|
+
request=cube_requests,
|
|
208
|
+
output_path="output_ndvi",
|
|
209
|
+
nworkers=2,
|
|
210
|
+
max_deep_level=5
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## **Advanced Usage**
|
|
217
|
+
|
|
218
|
+
### **Same Set of Sentinel-2 Images for Multiple Points**
|
|
219
|
+
|
|
220
|
+
Below is a **advanced example** demonstrating how to work with **multiple points** and a **Sentinel-2** image collection in one script. We first create a global collection but then filter it on a point-by-point basis, extracting only the images that intersect each coordinate. Finally, we download them in parallel using **CubeXpress**.
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
import ee
|
|
225
|
+
import cubexpress
|
|
226
|
+
|
|
227
|
+
# Initialize Earth Engine with your project
|
|
228
|
+
ee.Initialize(project="your-project-id")
|
|
229
|
+
|
|
230
|
+
# Define multiple points (longitude, latitude)
|
|
231
|
+
points = [
|
|
232
|
+
(-97.64, 33.37),
|
|
233
|
+
(-97.59, 33.37)
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
# Start with a broad Sentinel-2 collection
|
|
237
|
+
collection = (
|
|
238
|
+
ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
|
|
239
|
+
.filterDate("2024-01-01", "2024-01-31")
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Build a list of Request objects
|
|
243
|
+
requestset = []
|
|
244
|
+
for i, (lon, lat) in enumerate(points):
|
|
245
|
+
# Create a point geometry for the current coordinates
|
|
246
|
+
point_geom = ee.Geometry.Point([lon, lat])
|
|
247
|
+
collection_filtered = collection.filterBounds(point_geom)
|
|
248
|
+
|
|
249
|
+
# Convert the filtered collection into a list of asset IDs
|
|
250
|
+
image_ids = collection_filtered.aggregate_array("system:id").getInfo()
|
|
251
|
+
|
|
252
|
+
# Define a geotransform for this point
|
|
253
|
+
geotransform = cubexpress.lonlat2rt(
|
|
254
|
+
lon=lon,
|
|
255
|
+
lat=lat,
|
|
256
|
+
edge_size=512, # Adjust the image size in pixels
|
|
257
|
+
scale=10 # 10m resolution for Sentinel-2
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Create one Request per image found for this point
|
|
261
|
+
requestset.extend([
|
|
262
|
+
cubexpress.Request(
|
|
263
|
+
id=f"s2test_{i}_{idx}",
|
|
264
|
+
raster_transform=geotransform,
|
|
265
|
+
bands=["B4", "B3", "B2"],
|
|
266
|
+
image=image_id
|
|
267
|
+
)
|
|
268
|
+
for idx, image_id in enumerate(image_ids)
|
|
269
|
+
])
|
|
270
|
+
|
|
271
|
+
# Combine into a RequestSet
|
|
272
|
+
cube_requests = cubexpress.RequestSet(requestset=requestset)
|
|
273
|
+
|
|
274
|
+
# Download everything in parallel
|
|
275
|
+
results = cubexpress.getcube(
|
|
276
|
+
request=cube_requests,
|
|
277
|
+
nworkers=4,
|
|
278
|
+
output_path="images_s2",
|
|
279
|
+
max_deep_level=5
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
print("Downloaded files:", results)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
**How it works**:
|
|
287
|
+
|
|
288
|
+
1. **Points:** We define multiple coordinates in `points`.
|
|
289
|
+
2. **Global collection:** We retrieve a broad Sentinel-2 collection covering the desired date range.
|
|
290
|
+
3. **Per-point filter:** For each point, we call `.filterBounds(...)` to get only images intersecting that location.
|
|
291
|
+
4. **Geotransform:** We create a local geotransform (`edge_size`, `scale`) defining the spatial extent and resolution around each point.
|
|
292
|
+
5. **Requests:** Each point-image pair becomes a `Request`, stored in a single list.
|
|
293
|
+
6. **Parallel download:** With `cubexpress.getcube()`, all requests are fetched simultaneously, automatically splitting large outputs into sub-tiles if needed (up to `max_deep_level`).
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
## **License**
|
|
298
|
+
This project is licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
<p align="center">
|
|
303
|
+
Built with 🌎 and ❤️ by the <strong>CubeXpress</strong> team
|
|
304
|
+
</p>
|
|
305
|
+
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
<h1></h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="./docs/logo_cubexpress.png" width="39%">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<em>A Python package for efficient processing of cubic earth observation (EO) data</em> 🚀
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href='https://pypi.python.org/pypi/cubexpress'>
|
|
13
|
+
<img src='https://img.shields.io/pypi/v/cubexpress.svg' alt='PyPI' />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://opensource.org/licenses/MIT" target="_blank">
|
|
16
|
+
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License">
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://github.com/psf/black" target="_blank">
|
|
19
|
+
<img src="https://img.shields.io/badge/code%20style-black-000000.svg" alt="Black">
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://pycqa.github.io/isort/" target="_blank">
|
|
22
|
+
<img src="https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336" alt="isort">
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
**GitHub**: [https://github.com/andesdatacube/cubexpress/](https://github.com/andesdatacube/cubexpress/) 🌐
|
|
29
|
+
|
|
30
|
+
**PyPI**: [https://pypi.org/project/cubexpress/](https://pypi.org/project/cubexpress/) 🛠️
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## **Overview**
|
|
35
|
+
|
|
36
|
+
**CubeXpress** is a Python package designed to **simplify and accelerate** the process of working with Google Earth Engine (GEE) data cubes. With features like multi-threaded downloads, automatic subdivision of large requests, and direct pixel-level computations on GEE, **CubeXpress** helps you handle massive datasets with ease.
|
|
37
|
+
|
|
38
|
+
## **Key Features**
|
|
39
|
+
- **Fast Image and Collection Downloads**
|
|
40
|
+
Retrieve single images or entire collections at once, taking advantage of multi-threaded requests.
|
|
41
|
+
- **Automatic Tiling**
|
|
42
|
+
Large images are split ("quadsplit") into smaller sub-tiles, preventing errors with GEE’s size limits.
|
|
43
|
+
- **Direct Pixel Computations**
|
|
44
|
+
Perform computations (e.g., band math) directly on GEE, then fetch results in a single step.
|
|
45
|
+
- **Scalable & Efficient**
|
|
46
|
+
Optimized memory usage and parallelism let you handle complex tasks in big data environments.
|
|
47
|
+
|
|
48
|
+
## **Installation**
|
|
49
|
+
Install the latest version from PyPI:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install cubexpress
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> **Note**: You need a valid Google Earth Engine account and `earthengine-api` installed (`pip install earthengine-api`). Also run `ee.Initialize()` before using CubeXpress.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## **Basic Usage**
|
|
60
|
+
|
|
61
|
+
### **Download a single `ee.Image`**
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import ee
|
|
65
|
+
import cubexpress
|
|
66
|
+
|
|
67
|
+
# Initialize Earth Engine
|
|
68
|
+
ee.Initialize(project="your-project-id")
|
|
69
|
+
|
|
70
|
+
# Create a raster transform
|
|
71
|
+
geotransform = cubexpress.lonlat2rt(
|
|
72
|
+
lon=-76.5,
|
|
73
|
+
lat=-9.5,
|
|
74
|
+
edge_size=128, # Width=Height=128 pixels
|
|
75
|
+
scale=90 # 90m resolution
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Define a single Request
|
|
79
|
+
request = cubexpress.Request(
|
|
80
|
+
id="dem_test",
|
|
81
|
+
raster_transform=geotransform,
|
|
82
|
+
bands=["elevation"],
|
|
83
|
+
image="NASA/NASADEM_HGT/001" # Note: you can wrap with ee.Image("NASA/NASADEM_HGT/001").divide(10000) if needed
|
|
84
|
+
|
|
85
|
+
# Build the RequestSet
|
|
86
|
+
cube_requests = cubexpress.RequestSet(requestset=[request])
|
|
87
|
+
|
|
88
|
+
# Download with multi-threading
|
|
89
|
+
cubexpress.getcube(
|
|
90
|
+
request=cube_requests,
|
|
91
|
+
output_path="output_dem",
|
|
92
|
+
nworkers=4,
|
|
93
|
+
max_deep_level=5
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
This will create a GeoTIFF named `dem_test.tif` in the `output_dem` folder, containing the elevation band.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
### **Download pixel values from an ee.ImageCollection**
|
|
103
|
+
|
|
104
|
+
You can fetch multiple images by constructing a `RequestSet` with several `Request` objects. For example, filter Sentinel-2 images near a point:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import ee
|
|
108
|
+
import cubexpress
|
|
109
|
+
|
|
110
|
+
ee.Initialize(project="your-project-id")
|
|
111
|
+
|
|
112
|
+
# Filter a Sentinel-2 collection
|
|
113
|
+
point = ee.Geometry.Point([-97.59, 33.37])
|
|
114
|
+
collection = ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED") \
|
|
115
|
+
.filterBounds(point) \
|
|
116
|
+
.filterDate('2024-01-01', '2024-01-31')
|
|
117
|
+
|
|
118
|
+
# Extract image IDs
|
|
119
|
+
image_ids = collection.aggregate_array('system:id').getInfo()
|
|
120
|
+
|
|
121
|
+
# Set the geotransform
|
|
122
|
+
geotransform = cubexpress.lonlat2rt(
|
|
123
|
+
lon=-97.59,
|
|
124
|
+
lat=33.37,
|
|
125
|
+
edge_size=512,
|
|
126
|
+
scale=10
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Build multiple requests
|
|
130
|
+
requests = [
|
|
131
|
+
cubexpress.Request(
|
|
132
|
+
id=f"s2test_{i}",
|
|
133
|
+
raster_transform=geotransform,
|
|
134
|
+
bands=["B4", "B3", "B2"],
|
|
135
|
+
image=image_id # Note: you can wrap with ee.Image(image_id).divide(10000) if needed
|
|
136
|
+
)
|
|
137
|
+
for i, image_id in enumerate(image_ids)
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
# Create the RequestSet
|
|
141
|
+
cube_requests = cubexpress.RequestSet(requestset=requests)
|
|
142
|
+
|
|
143
|
+
# Download
|
|
144
|
+
cubexpress.getcube(
|
|
145
|
+
request=cube_requests,
|
|
146
|
+
output_path="output_sentinel",
|
|
147
|
+
nworkers=4,
|
|
148
|
+
max_deep_level=5
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### **Process and extract a pixel from an ee.Image**
|
|
155
|
+
If you provide an `ee.Image` with custom calculations (e.g., `.divide(10000)`, `.normalizedDifference(...)`), CubeXpress can run those on GEE, then download the result. For large results, it automatically splits the image into sub-tiles.
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
import ee
|
|
159
|
+
import cubexpress
|
|
160
|
+
|
|
161
|
+
ee.Initialize(project="your-project-id")
|
|
162
|
+
|
|
163
|
+
# Example: NDVI from Sentinel-2
|
|
164
|
+
image = ee.Image("COPERNICUS/S2_HARMONIZED/20170804T154911_20170804T155116_T18SUJ") \
|
|
165
|
+
.normalizedDifference(["B8", "B4"]) \
|
|
166
|
+
.rename("NDVI")
|
|
167
|
+
|
|
168
|
+
geotransform = cubexpress.lonlat2rt(
|
|
169
|
+
lon=-76.59,
|
|
170
|
+
lat=38.89,
|
|
171
|
+
edge_size=256,
|
|
172
|
+
scale=10
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
request = cubexpress.Request(
|
|
176
|
+
id="ndvi_test",
|
|
177
|
+
raster_transform=geotransform,
|
|
178
|
+
bands=["NDVI"],
|
|
179
|
+
image=image # custom expression
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
cube_requests = cubexpress.RequestSet(requestset=[request])
|
|
183
|
+
|
|
184
|
+
cubexpress.getcube(
|
|
185
|
+
request=cube_requests,
|
|
186
|
+
output_path="output_ndvi",
|
|
187
|
+
nworkers=2,
|
|
188
|
+
max_deep_level=5
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## **Advanced Usage**
|
|
195
|
+
|
|
196
|
+
### **Same Set of Sentinel-2 Images for Multiple Points**
|
|
197
|
+
|
|
198
|
+
Below is a **advanced example** demonstrating how to work with **multiple points** and a **Sentinel-2** image collection in one script. We first create a global collection but then filter it on a point-by-point basis, extracting only the images that intersect each coordinate. Finally, we download them in parallel using **CubeXpress**.
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
import ee
|
|
203
|
+
import cubexpress
|
|
204
|
+
|
|
205
|
+
# Initialize Earth Engine with your project
|
|
206
|
+
ee.Initialize(project="your-project-id")
|
|
207
|
+
|
|
208
|
+
# Define multiple points (longitude, latitude)
|
|
209
|
+
points = [
|
|
210
|
+
(-97.64, 33.37),
|
|
211
|
+
(-97.59, 33.37)
|
|
212
|
+
]
|
|
213
|
+
|
|
214
|
+
# Start with a broad Sentinel-2 collection
|
|
215
|
+
collection = (
|
|
216
|
+
ee.ImageCollection("COPERNICUS/S2_SR_HARMONIZED")
|
|
217
|
+
.filterDate("2024-01-01", "2024-01-31")
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Build a list of Request objects
|
|
221
|
+
requestset = []
|
|
222
|
+
for i, (lon, lat) in enumerate(points):
|
|
223
|
+
# Create a point geometry for the current coordinates
|
|
224
|
+
point_geom = ee.Geometry.Point([lon, lat])
|
|
225
|
+
collection_filtered = collection.filterBounds(point_geom)
|
|
226
|
+
|
|
227
|
+
# Convert the filtered collection into a list of asset IDs
|
|
228
|
+
image_ids = collection_filtered.aggregate_array("system:id").getInfo()
|
|
229
|
+
|
|
230
|
+
# Define a geotransform for this point
|
|
231
|
+
geotransform = cubexpress.lonlat2rt(
|
|
232
|
+
lon=lon,
|
|
233
|
+
lat=lat,
|
|
234
|
+
edge_size=512, # Adjust the image size in pixels
|
|
235
|
+
scale=10 # 10m resolution for Sentinel-2
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Create one Request per image found for this point
|
|
239
|
+
requestset.extend([
|
|
240
|
+
cubexpress.Request(
|
|
241
|
+
id=f"s2test_{i}_{idx}",
|
|
242
|
+
raster_transform=geotransform,
|
|
243
|
+
bands=["B4", "B3", "B2"],
|
|
244
|
+
image=image_id
|
|
245
|
+
)
|
|
246
|
+
for idx, image_id in enumerate(image_ids)
|
|
247
|
+
])
|
|
248
|
+
|
|
249
|
+
# Combine into a RequestSet
|
|
250
|
+
cube_requests = cubexpress.RequestSet(requestset=requestset)
|
|
251
|
+
|
|
252
|
+
# Download everything in parallel
|
|
253
|
+
results = cubexpress.getcube(
|
|
254
|
+
request=cube_requests,
|
|
255
|
+
nworkers=4,
|
|
256
|
+
output_path="images_s2",
|
|
257
|
+
max_deep_level=5
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
print("Downloaded files:", results)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
**How it works**:
|
|
265
|
+
|
|
266
|
+
1. **Points:** We define multiple coordinates in `points`.
|
|
267
|
+
2. **Global collection:** We retrieve a broad Sentinel-2 collection covering the desired date range.
|
|
268
|
+
3. **Per-point filter:** For each point, we call `.filterBounds(...)` to get only images intersecting that location.
|
|
269
|
+
4. **Geotransform:** We create a local geotransform (`edge_size`, `scale`) defining the spatial extent and resolution around each point.
|
|
270
|
+
5. **Requests:** Each point-image pair becomes a `Request`, stored in a single list.
|
|
271
|
+
6. **Parallel download:** With `cubexpress.getcube()`, all requests are fetched simultaneously, automatically splitting large outputs into sub-tiles if needed (up to `max_deep_level`).
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
## **License**
|
|
276
|
+
This project is licensed under the [MIT License](https://opensource.org/licenses/MIT).
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
<p align="center">
|
|
281
|
+
Built with 🌎 and ❤️ by the <strong>CubeXpress</strong> team
|
|
282
|
+
</p>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from cubexpress.conversion import lonlat2rt
|
|
2
|
+
from cubexpress.download import getcube, getGeoTIFF
|
|
3
|
+
from cubexpress.geotyping import RasterTransform, Request, RequestSet
|
|
4
|
+
|
|
5
|
+
# Export the functions
|
|
6
|
+
__all__ = [
|
|
7
|
+
"lonlat2rt",
|
|
8
|
+
"RasterTransform",
|
|
9
|
+
"Request",
|
|
10
|
+
"RequestSet",
|
|
11
|
+
"getcube",
|
|
12
|
+
"getGeoTIFF",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
# Dynamic version import
|
|
16
|
+
import importlib.metadata
|
|
17
|
+
|
|
18
|
+
__version__ = importlib.metadata.version("cubexpress")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import utm
|
|
2
|
+
|
|
3
|
+
from cubexpress.geotyping import RasterTransform
|
|
4
|
+
|
|
5
|
+
# Define your GeotransformDict type if not already defined
|
|
6
|
+
GeotransformDict = dict[str, float]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def geo2utm(lon: float, lat: float) -> tuple[float, float, str]:
|
|
10
|
+
"""
|
|
11
|
+
Converts latitude and longitude coordinates to UTM coordinates and returns the EPSG code.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
lon (float): Longitude.
|
|
15
|
+
lat (float): Latitude.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Tuple[float, float, str]: UTM coordinates (x, y) and the EPSG code.
|
|
19
|
+
"""
|
|
20
|
+
x, y, zone, _ = utm.from_latlon(lat, lon)
|
|
21
|
+
epsg_code = f"326{zone:02d}" if lat >= 0 else f"327{zone:02d}"
|
|
22
|
+
return x, y, f"EPSG:{epsg_code}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def lonlat2rt(lon: float, lat: float, edge_size: int, scale: int) -> RasterTransform:
|
|
26
|
+
"""
|
|
27
|
+
Generates a ``RasterTransform`` for a given point by converting geographic (lon, lat) coordinates
|
|
28
|
+
to UTM projection and building the necessary geotransform metadata.
|
|
29
|
+
|
|
30
|
+
This function:
|
|
31
|
+
1. Converts the input (lon, lat) to UTM coordinates using :func:`geo2utm`.
|
|
32
|
+
2. Defines the extent of the raster in UTM meters based on the specified ``edge_size`` (width/height in pixels)
|
|
33
|
+
and ``scale`` (meters per pixel).
|
|
34
|
+
3. Sets the Y-scale to be negative (``-scale``) because geospatial images typically consider the origin at
|
|
35
|
+
the top-left corner, resulting in a downward Y axis.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
lon (float): The longitude coordinate.
|
|
39
|
+
lat (float): The latitude coordinate.
|
|
40
|
+
edge_size (int): Width and height of the output raster in pixels.
|
|
41
|
+
scale (int): Spatial resolution in meters per pixel.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
RasterTransform: A Pydantic model containing:
|
|
45
|
+
- ``crs``: The EPSG code in the form ``"EPSG:XYZ"``,
|
|
46
|
+
- ``geotransform``: A dictionary with the affine transform parameters,
|
|
47
|
+
- ``width`` and ``height``.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
>>> import cubexpress
|
|
51
|
+
>>> rt = cubexpress.lonlat2rt(
|
|
52
|
+
... lon=-76.0,
|
|
53
|
+
... lat=40.0,
|
|
54
|
+
... edge_size=512,
|
|
55
|
+
... scale=30
|
|
56
|
+
... )
|
|
57
|
+
>>> print(rt)
|
|
58
|
+
"""
|
|
59
|
+
x, y, crs = geo2utm(lon, lat)
|
|
60
|
+
half_extent = (edge_size * scale) / 2
|
|
61
|
+
|
|
62
|
+
geotransform = GeotransformDict(
|
|
63
|
+
scaleX=scale,
|
|
64
|
+
shearX=0,
|
|
65
|
+
translateX=x - half_extent,
|
|
66
|
+
scaleY=-scale, # Y-axis is inverted in geospatial images
|
|
67
|
+
shearY=0,
|
|
68
|
+
translateY=y + half_extent,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return RasterTransform(
|
|
72
|
+
crs=crs, geotransform=geotransform, width=edge_size, height=edge_size
|
|
73
|
+
)
|