giswqs commited on
Commit
5688b14
·
unverified ·
1 Parent(s): d67cb2c

Add JRC water statistics endpoint (#7)

Browse files

Add POST /jrc-water-stats endpoint that computes JRC monthly water
history and water occurrence statistics for a given bounding box and
scale. Returns JSON with monthly/yearly water area data and occurrence
stats (mean, min, max, stdDev, histogram) suitable for creating plots.

Files changed (3) hide show
  1. README.md +79 -5
  2. main.py +134 -0
  3. requirements.txt +1 -1
README.md CHANGED
@@ -67,15 +67,16 @@ docker run -p 7860:7860 -e EARTHENGINE_TOKEN="your_token" ee-tile-request
67
 
68
  - **Web UI**: http://localhost:7860
69
  - **API Documentation**: http://localhost:7860/docs
70
- - **API Endpoint**: POST http://localhost:7860/tile
 
71
 
72
- ## API Usage
73
 
74
- ### Endpoint
75
 
76
  `POST /tile`
77
 
78
- ### Request Parameters
79
 
80
  | Parameter | Type | Required | Description |
81
  |-----------|------|----------|-------------|
@@ -159,7 +160,7 @@ curl -X POST "http://localhost:7860/tile" \
159
  }'
160
  ```
161
 
162
- ### Response
163
 
164
  ```json
165
  {
@@ -167,6 +168,79 @@ curl -X POST "http://localhost:7860/tile" \
167
  }
168
  ```
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  ### Using with Web Mapping Libraries
171
 
172
  #### Leaflet
 
67
 
68
  - **Web UI**: http://localhost:7860
69
  - **API Documentation**: http://localhost:7860/docs
70
+ - **Tile Endpoint**: POST http://localhost:7860/tile
71
+ - **JRC Water Stats Endpoint**: POST http://localhost:7860/jrc-water-stats
72
 
73
+ ## Tile URL API
74
 
75
+ ### Tile Endpoint
76
 
77
  `POST /tile`
78
 
79
+ ### Tile Request Parameters
80
 
81
  | Parameter | Type | Required | Description |
82
  |-----------|------|----------|-------------|
 
160
  }'
161
  ```
162
 
163
+ ### Tile Response
164
 
165
  ```json
166
  {
 
168
  }
169
  ```
170
 
171
+ ## JRC Water Statistics API
172
+
173
+ ### JRC Endpoint
174
+
175
+ `POST /jrc-water-stats`
176
+
177
+ Computes JRC monthly water history and water occurrence statistics for a given bounding box and scale. Returns JSON data suitable for creating plots.
178
+
179
+ ### JRC Request Parameters
180
+
181
+ | Parameter | Type | Required | Default | Description |
182
+ |-----------|------|----------|---------|-------------|
183
+ | `bbox` | array | Yes | — | Bounding box [west, south, east, north] in degrees |
184
+ | `scale` | number | No | 30 | Scale in meters for computation |
185
+ | `start_date` | string | No | "1984-03-16" | Start date (format: "YYYY-MM-DD") |
186
+ | `end_date` | string | No | today | End date (format: "YYYY-MM-DD") |
187
+ | `start_month` | integer | No | 1 | Start month for calendar filtering (1-12) |
188
+ | `end_month` | integer | No | 12 | End month for calendar filtering (1-12) |
189
+ | `frequency` | string | No | "year" | Aggregation frequency: "month" or "year" |
190
+ | `denominator` | number | No | 10000 | Area unit conversion (10000 = hectares) |
191
+
192
+ ### Example
193
+
194
+ ```bash
195
+ curl -X POST "http://localhost:7860/jrc-water-stats" \
196
+ -H "Content-Type: application/json" \
197
+ -d '{
198
+ "bbox": [-90.5, 29.5, -90.0, 30.0],
199
+ "scale": 30,
200
+ "start_month": 5,
201
+ "end_month": 10,
202
+ "frequency": "year"
203
+ }'
204
+ ```
205
+
206
+ ### JRC Response
207
+
208
+ ```json
209
+ {
210
+ "monthly_history": {
211
+ "frequency": "year",
212
+ "unit": "hectares",
213
+ "data": [
214
+ {"Year": "1984", "Area": 123.45},
215
+ {"Year": "1985", "Area": 130.20}
216
+ ]
217
+ },
218
+ "water_occurrence": {
219
+ "stats": {
220
+ "mean": 45.2,
221
+ "min": 0,
222
+ "max": 100,
223
+ "stdDev": 28.3
224
+ },
225
+ "histogram": {
226
+ "bin_edges": [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
227
+ "counts": [1500, 200, 150, 100, 80, 60, 50, 40, 30, 300]
228
+ }
229
+ },
230
+ "parameters": {
231
+ "bbox": [-90.5, 29.5, -90.0, 30.0],
232
+ "scale": 30,
233
+ "start_date": "1984-03-16",
234
+ "end_date": "2026-03-05",
235
+ "start_month": 5,
236
+ "end_month": 10,
237
+ "frequency": "year"
238
+ }
239
+ }
240
+ ```
241
+
242
+ When `frequency` is `"month"`, the `data` array contains `{"Month": "Jan", "Area": ...}` entries instead of `Year`.
243
+
244
  ### Using with Web Mapping Libraries
245
 
246
  #### Leaflet
main.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  import json
 
3
  import ee
4
  import geemap
5
  import gradio as gr
@@ -211,6 +212,19 @@ class TileRequest(BaseModel):
211
  bbox: list[float] | None = None # [west, south, east, north]
212
 
213
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  @app.post("/tile")
215
  def get_tile_api(req: TileRequest):
216
  result = get_tile(
@@ -221,6 +235,126 @@ def get_tile_api(req: TileRequest):
221
  return {"tile_url": result}
222
 
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  # ---- Gradio UI ----
225
  def get_tile_gradio(asset_id, vis_params, start_date, end_date, bbox_str):
226
  """Wrapper for Gradio that converts string inputs to proper types."""
 
1
  import os
2
  import json
3
+ import datetime
4
  import ee
5
  import geemap
6
  import gradio as gr
 
212
  bbox: list[float] | None = None # [west, south, east, north]
213
 
214
 
215
+ class JRCWaterStatsRequest(BaseModel):
216
+ """Request model for JRC water statistics endpoint."""
217
+
218
+ bbox: list[float] # [west, south, east, north]
219
+ scale: float | None = 30
220
+ start_date: str | None = "1984-03-16"
221
+ end_date: str | None = None # defaults to today
222
+ start_month: int | None = 1
223
+ end_month: int | None = 12
224
+ frequency: str | None = "year" # "month" or "year"
225
+ denominator: float | None = 10000.0 # m² to hectares
226
+
227
+
228
  @app.post("/tile")
229
  def get_tile_api(req: TileRequest):
230
  result = get_tile(
 
235
  return {"tile_url": result}
236
 
237
 
238
+ @app.post("/jrc-water-stats")
239
+ def get_jrc_water_stats(req: JRCWaterStatsRequest):
240
+ """Compute JRC monthly water history and water occurrence statistics.
241
+
242
+ Args:
243
+ req: Request with bbox, scale, date range, month range, and frequency.
244
+
245
+ Returns:
246
+ dict: Monthly history data and water occurrence statistics.
247
+ """
248
+ try:
249
+ if len(req.bbox) != 4:
250
+ raise ValueError(
251
+ "bbox must be a list of 4 values: [west, south, east, north]"
252
+ )
253
+ if req.frequency not in ("month", "year"):
254
+ raise ValueError("frequency must be 'month' or 'year'")
255
+
256
+ region = ee.Geometry.BBox(*req.bbox)
257
+ end_date = req.end_date or datetime.date.today().strftime("%Y-%m-%d")
258
+
259
+ # Compute monthly water history from JRC MonthlyHistory
260
+ collection = ee.ImageCollection("JRC/GSW1_4/MonthlyHistory")
261
+ images = (
262
+ collection.filterDate(req.start_date, end_date)
263
+ .filter(ee.Filter.calendarRange(req.start_month, req.end_month, "month"))
264
+ .map(lambda img: img.eq(2).selfMask())
265
+ )
266
+
267
+ def cal_area(img):
268
+ pixel_area = img.multiply(ee.Image.pixelArea()).divide(req.denominator)
269
+ img_area = pixel_area.reduceRegion(
270
+ geometry=region,
271
+ reducer=ee.Reducer.sum(),
272
+ scale=req.scale,
273
+ maxPixels=1e12,
274
+ bestEffort=True,
275
+ )
276
+ return img.set({"area": img_area})
277
+
278
+ areas = images.map(cal_area)
279
+ stats_list = areas.aggregate_array("area").getInfo()
280
+ values = [item["water"] for item in stats_list]
281
+ labels = areas.aggregate_array("system:index").getInfo()
282
+
283
+ if req.frequency == "month":
284
+ history_data = [
285
+ {"Month": label, "Area": area} for label, area in zip(labels, values)
286
+ ]
287
+ else:
288
+ # Group by year and compute mean
289
+ year_areas = {}
290
+ for label, area in zip(labels, values):
291
+ year = label[:4]
292
+ year_areas.setdefault(year, []).append(area)
293
+ history_data = [
294
+ {"Year": year, "Area": sum(areas) / len(areas)}
295
+ for year, areas in sorted(year_areas.items())
296
+ ]
297
+
298
+ # Compute water occurrence statistics
299
+ occurrence = ee.Image("JRC/GSW1_4/GlobalSurfaceWater").select("occurrence")
300
+ stats = occurrence.reduceRegion(
301
+ reducer=ee.Reducer.mean()
302
+ .combine(ee.Reducer.min(), sharedInputs=True)
303
+ .combine(ee.Reducer.max(), sharedInputs=True)
304
+ .combine(ee.Reducer.stdDev(), sharedInputs=True),
305
+ geometry=region,
306
+ scale=req.scale,
307
+ maxPixels=1e12,
308
+ bestEffort=True,
309
+ ).getInfo()
310
+
311
+ # Compute occurrence histogram (10 bins from 0 to 100)
312
+ hist_result = occurrence.reduceRegion(
313
+ reducer=ee.Reducer.fixedHistogram(0, 100, 10),
314
+ geometry=region,
315
+ scale=req.scale,
316
+ maxPixels=1e12,
317
+ bestEffort=True,
318
+ ).getInfo()
319
+
320
+ # Parse histogram result
321
+ histogram = {"bin_edges": [], "counts": []}
322
+ if hist_result and "occurrence" in hist_result:
323
+ hist_list = hist_result["occurrence"]
324
+ bin_edges = [row[0] for row in hist_list]
325
+ bin_edges.append(hist_list[-1][0] + 10) # add right edge
326
+ counts = [row[1] for row in hist_list]
327
+ histogram = {"bin_edges": bin_edges, "counts": counts}
328
+
329
+ return {
330
+ "monthly_history": {
331
+ "frequency": req.frequency,
332
+ "unit": "hectares",
333
+ "data": history_data,
334
+ },
335
+ "water_occurrence": {
336
+ "stats": {
337
+ "mean": stats.get("occurrence_mean"),
338
+ "min": stats.get("occurrence_min"),
339
+ "max": stats.get("occurrence_max"),
340
+ "stdDev": stats.get("occurrence_stdDev"),
341
+ },
342
+ "histogram": histogram,
343
+ },
344
+ "parameters": {
345
+ "bbox": req.bbox,
346
+ "scale": req.scale,
347
+ "start_date": req.start_date,
348
+ "end_date": end_date,
349
+ "start_month": req.start_month,
350
+ "end_month": req.end_month,
351
+ "frequency": req.frequency,
352
+ },
353
+ }
354
+ except Exception as e:
355
+ raise HTTPException(status_code=400, detail=str(e))
356
+
357
+
358
  # ---- Gradio UI ----
359
  def get_tile_gradio(asset_id, vis_params, start_date, end_date, bbox_str):
360
  """Wrapper for Gradio that converts string inputs to proper types."""
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
  fastapi
2
- git+https://github.com/gee-community/geemap.git
3
  gradio
4
  uvicorn
 
1
  fastapi
2
+ geemap
3
  gradio
4
  uvicorn