eodash_catalog 0.0.13__tar.gz → 0.0.15__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 eodash_catalog might be problematic. Click here for more details.

Files changed (34) hide show
  1. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/.bumpversion.cfg +1 -1
  2. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/.github/workflows/ci.yml +3 -1
  3. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/PKG-INFO +1 -1
  4. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/src/eodash_catalog/__about__.py +1 -1
  5. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/src/eodash_catalog/endpoints.py +100 -46
  6. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/src/eodash_catalog/generate_indicators.py +7 -0
  7. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/src/eodash_catalog/stac_handling.py +59 -28
  8. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/src/eodash_catalog/utils.py +16 -1
  9. eodash_catalog-0.0.15/tests/test-data/regional_forecast.json +1 -0
  10. eodash_catalog-0.0.15/tests/test_generate.py +187 -0
  11. eodash_catalog-0.0.15/tests/testing-catalogs/testing.yaml +12 -0
  12. eodash_catalog-0.0.15/tests/testing-collections/test_CROPOMAT1.yaml +51 -0
  13. eodash_catalog-0.0.15/tests/testing-collections/test_see_solar_energy.yaml +25 -0
  14. eodash_catalog-0.0.15/tests/testing-collections/test_tif_demo_1.yaml +19 -0
  15. eodash_catalog-0.0.15/tests/testing-collections/test_tif_demo_2.yaml +19 -0
  16. eodash_catalog-0.0.15/tests/testing-collections/test_wms_no_time.yaml +21 -0
  17. eodash_catalog-0.0.15/tests/testing-indicators/test_indicator.yaml +20 -0
  18. eodash_catalog-0.0.15/tests/testing-layers/baselayers.yaml +7 -0
  19. eodash_catalog-0.0.15/tests/testing-layers/overlays.yaml +7 -0
  20. eodash_catalog-0.0.13/tests/test_generate.py +0 -2
  21. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/.github/workflows/python-publish.yml +0 -0
  22. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/.gitignore +0 -0
  23. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/.vscode/extensions.json +0 -0
  24. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/.vscode/settings.json +0 -0
  25. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/LICENSE.txt +0 -0
  26. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/README.md +0 -0
  27. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/pyproject.toml +0 -0
  28. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/requirements.txt +0 -0
  29. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/ruff.toml +0 -0
  30. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/src/eodash_catalog/__init__.py +0 -0
  31. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/src/eodash_catalog/duration.py +0 -0
  32. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/src/eodash_catalog/sh_endpoint.py +0 -0
  33. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/src/eodash_catalog/thumbnails.py +0 -0
  34. {eodash_catalog-0.0.13 → eodash_catalog-0.0.15}/tests/__init__.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.0.13
2
+ current_version = 0.0.15
3
3
  commit = True
4
4
  tag = True
5
5
  parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\-(?P<release>[a-z]+)\.(?P<build>\d+))?
@@ -22,5 +22,7 @@ jobs:
22
22
  run: python -m pip install -r requirements.txt
23
23
  - name: Lint
24
24
  run: python -m ruff check --no-cache .
25
+ - name: Install eodash catalog itself for tests
26
+ run: python -m pip install .
25
27
  - name: Test
26
- run: python -m pytest -p no:cacheprovider
28
+ run: cd tests && python -m pytest -p no:cacheprovider
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: eodash_catalog
3
- Version: 0.0.13
3
+ Version: 0.0.15
4
4
  Summary: This package is intended to help create a compatible STAC catalog for the eodash dashboard client. It supports configuration of multiple endpoint types for information extraction.
5
5
  Project-URL: Documentation, https://github.com/eodash/eodash_catalog#readme
6
6
  Project-URL: Issues, https://github.com/eodash/eodash_catalog/issues
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2024-present Daniel Santillan <daniel.santillan@eox.at>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.0.13"
4
+ __version__ = "0.0.15"
@@ -10,19 +10,21 @@ from operator import itemgetter
10
10
 
11
11
  import requests
12
12
  from dateutil import parser
13
- from pystac import Catalog, Collection, Item, Link, SpatialExtent, Summaries
13
+ from pystac import Asset, Catalog, Collection, Item, Link, SpatialExtent, Summaries
14
14
  from pystac_client import Client
15
15
 
16
16
  from eodash_catalog.sh_endpoint import get_SH_token
17
17
  from eodash_catalog.stac_handling import (
18
18
  add_collection_information,
19
19
  add_example_info,
20
+ add_projection_info,
20
21
  get_collection_times_from_config,
21
22
  get_or_create_collection,
22
23
  )
23
24
  from eodash_catalog.thumbnails import generate_thumbnail
24
25
  from eodash_catalog.utils import (
25
26
  Options,
27
+ create_geojson_from_bbox,
26
28
  create_geojson_point,
27
29
  generate_veda_cog_link,
28
30
  retrieveExtentFromWMSWMTS,
@@ -104,29 +106,17 @@ def handle_STAC_based_endpoint(
104
106
  catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
105
107
  )
106
108
  for location in collection_config["Locations"]:
107
- if "FilterDates" in location:
108
- collection = process_STACAPI_Endpoint(
109
- catalog_config=catalog_config,
110
- endpoint_config=endpoint_config,
111
- collection_config=collection_config,
112
- catalog=catalog,
113
- options=options,
114
- headers=headers,
115
- bbox=",".join(map(str, location["Bbox"])),
116
- filter_dates=location["FilterDates"],
117
- root_collection=root_collection,
118
- )
119
- else:
120
- collection = process_STACAPI_Endpoint(
121
- catalog_config=catalog_config,
122
- endpoint_config=endpoint_config,
123
- collection_config=collection_config,
124
- catalog=catalog,
125
- options=options,
126
- headers=headers,
127
- bbox=",".join(map(str, location["Bbox"])),
128
- root_collection=root_collection,
129
- )
109
+ collection = process_STACAPI_Endpoint(
110
+ catalog_config=catalog_config,
111
+ endpoint_config=endpoint_config,
112
+ collection_config=collection_config,
113
+ catalog=catalog,
114
+ options=options,
115
+ headers=headers,
116
+ filter_dates=location.get("FilterDates"),
117
+ bbox=",".join(map(str, location["Bbox"])),
118
+ root_collection=root_collection,
119
+ )
130
120
  # Update identifier to use location as well as title
131
121
  # TODO: should we use the name as id? it provides much more
132
122
  # information in the clients
@@ -157,25 +147,18 @@ def handle_STAC_based_endpoint(
157
147
  if isinstance(c_child, Collection):
158
148
  root_collection.extent.spatial.bboxes.append(c_child.extent.spatial.bboxes[0])
159
149
  else:
150
+ bbox = None
160
151
  if "Bbox" in endpoint_config:
161
- root_collection = process_STACAPI_Endpoint(
162
- catalog_config=catalog_config,
163
- endpoint_config=endpoint_config,
164
- collection_config=collection_config,
165
- catalog=catalog,
166
- options=options,
167
- headers=headers,
168
- bbox=",".join(map(str, endpoint_config["Bbox"])),
169
- )
170
- else:
171
- root_collection = process_STACAPI_Endpoint(
172
- catalog_config=catalog_config,
173
- endpoint_config=endpoint_config,
174
- collection_config=collection_config,
175
- catalog=catalog,
176
- options=options,
177
- headers=headers,
178
- )
152
+ bbox = ",".join(map(str, endpoint_config["Bbox"]))
153
+ root_collection = process_STACAPI_Endpoint(
154
+ catalog_config=catalog_config,
155
+ endpoint_config=endpoint_config,
156
+ collection_config=collection_config,
157
+ catalog=catalog,
158
+ options=options,
159
+ headers=headers,
160
+ bbox=bbox,
161
+ )
179
162
 
180
163
  add_example_info(root_collection, collection_config, endpoint_config, catalog_config)
181
164
  return root_collection
@@ -197,6 +180,12 @@ def process_STACAPI_Endpoint(
197
180
  collection = get_or_create_collection(
198
181
  catalog, endpoint_config["CollectionId"], collection_config, catalog_config, endpoint_config
199
182
  )
183
+ datetime_query = ["1900-01-01T00:00:00Z", "3000-01-01T00:00:00Z"]
184
+ if query := endpoint_config.get("Query"):
185
+ if start := query.get("Start"):
186
+ datetime_query[0] = start
187
+ if end := query.get("End"):
188
+ datetime_query[1] = end
200
189
 
201
190
  api = Client.open(endpoint_config["EndPoint"], headers=headers)
202
191
  if bbox is None:
@@ -204,7 +193,7 @@ def process_STACAPI_Endpoint(
204
193
  results = api.search(
205
194
  collections=[endpoint_config["CollectionId"]],
206
195
  bbox=bbox,
207
- datetime=["1900-01-01T00:00:00Z", "3000-01-01T00:00:00Z"],
196
+ datetime=datetime_query, # type: ignore
208
197
  )
209
198
  # We keep track of potential duplicate times in this list
210
199
  added_times = {}
@@ -263,7 +252,10 @@ def process_STACAPI_Endpoint(
263
252
  else:
264
253
  link.extra_fields["start_datetime"] = item.properties["start_datetime"]
265
254
  link.extra_fields["end_datetime"] = item.properties["end_datetime"]
266
-
255
+ add_projection_info(
256
+ endpoint_config,
257
+ item,
258
+ )
267
259
  collection.update_extent_from_items()
268
260
 
269
261
  # replace SH identifier with catalog identifier
@@ -346,6 +338,7 @@ def handle_SH_WMS_endpoint(
346
338
  "https://stac-extensions.github.io/web-map-links/v1.1.0/schema.json",
347
339
  ],
348
340
  )
341
+ add_projection_info(endpoint_config, item)
349
342
  add_visualization_info(item, collection_config, endpoint_config, time=time)
350
343
  item_link = collection.add_item(item)
351
344
  item_link.extra_fields["datetime"] = time
@@ -529,6 +522,7 @@ def handle_WMS_endpoint(
529
522
  "https://stac-extensions.github.io/web-map-links/v1.1.0/schema.json",
530
523
  ],
531
524
  )
525
+ add_projection_info(endpoint_config, item)
532
526
  add_visualization_info(item, collection_config, endpoint_config, time=t)
533
527
  link = collection.add_item(item)
534
528
  link.extra_fields["datetime"] = t
@@ -557,7 +551,7 @@ def generate_veda_tiles_link(endpoint_config: dict, item: str | None) -> str:
557
551
  if "NoData" in endpoint_config:
558
552
  no_data = "&no_data={}".format(endpoint_config["NoData"])
559
553
  item = f"&item={item}" if item else ""
560
- target_url = f"https://staging-raster.delta-backend.com/stac/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}?{collection}{item}{assets}{color_formula}{no_data}"
554
+ target_url = f"https://openveda.cloud/stac/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}?{collection}{item}{assets}{color_formula}{no_data}"
561
555
  return target_url
562
556
 
563
557
 
@@ -644,7 +638,10 @@ def add_visualization_info(
644
638
  if "Rescale" in endpoint_config:
645
639
  vmin = endpoint_config["Rescale"][0]
646
640
  vmax = endpoint_config["Rescale"][1]
647
- crs = endpoint_config.get("Crs", "EPSG:3857")
641
+ # depending on numerical input only
642
+ data_projection = endpoint_config.get("DataProjection", 3857)
643
+ epsg_prefix = "" if "EPSG:" in data_projection else "EPSG:"
644
+ crs = f"{epsg_prefix}{data_projection}"
648
645
  target_url = (
649
646
  "{}/tiles/{}/{}/{{z}}/{{y}}/{{x}}" "?crs={}&time={{time}}&vmin={}&vmax={}&cbar={}"
650
647
  ).format(
@@ -764,3 +761,60 @@ def handle_custom_endpoint(
764
761
  collection_config,
765
762
  )
766
763
  return collection
764
+
765
+
766
+ def handle_raw_source(
767
+ catalog_config: dict,
768
+ endpoint_config: dict,
769
+ collection_config: dict,
770
+ catalog: Catalog,
771
+ ) -> Collection:
772
+ collection = get_or_create_collection(
773
+ catalog, collection_config["Name"], collection_config, catalog_config, endpoint_config
774
+ )
775
+ if len(endpoint_config.get("TimeEntries", [])) > 0:
776
+ for time_entry in endpoint_config["TimeEntries"]:
777
+ assets = {}
778
+ media_type = "application/geo+json"
779
+ style_type = "text/vector-styles"
780
+ if endpoint_config["Name"] == "COG source":
781
+ style_type = "text/cog-styles"
782
+ media_type = "image/tiff"
783
+ for a in time_entry["Assets"]:
784
+ asset = Asset(
785
+ href=a["File"], roles=["data"], media_type=media_type, extra_fields={}
786
+ )
787
+ add_projection_info(endpoint_config, asset)
788
+ assets[a["Identifier"]] = asset
789
+ bbox = endpoint_config.get("Bbox", [-180, -85, 180, 85])
790
+ item = Item(
791
+ id=time_entry["Time"],
792
+ bbox=bbox,
793
+ properties={},
794
+ geometry=create_geojson_from_bbox(bbox),
795
+ datetime=parser.isoparse(time_entry["Time"]),
796
+ assets=assets,
797
+ extra_fields={},
798
+ )
799
+ add_projection_info(
800
+ endpoint_config,
801
+ item,
802
+ )
803
+ if ep_st := endpoint_config.get("Style"):
804
+ style_link = Link(
805
+ rel="style",
806
+ target=ep_st
807
+ if ep_st.startswith("http")
808
+ else f"{catalog_config['assets_endpoint']}/{ep_st}",
809
+ media_type=style_type,
810
+ extra_fields={
811
+ "asset:keys": list(assets),
812
+ },
813
+ )
814
+ item.add_link(style_link)
815
+ link = collection.add_item(item)
816
+ link.extra_fields["datetime"] = time_entry["Time"]
817
+ link.extra_fields["assets"] = [a["File"] for a in time_entry["Assets"]]
818
+ add_collection_information(catalog_config, collection, collection_config)
819
+ collection.update_extent_from_items()
820
+ return collection
@@ -20,6 +20,7 @@ from eodash_catalog.endpoints import (
20
20
  handle_collection_only,
21
21
  handle_custom_endpoint,
22
22
  handle_GeoDB_endpoint,
23
+ handle_raw_source,
23
24
  handle_SH_endpoint,
24
25
  handle_SH_WMS_endpoint,
25
26
  handle_VEDA_endpoint,
@@ -30,6 +31,7 @@ from eodash_catalog.stac_handling import (
30
31
  add_base_overlay_info,
31
32
  add_collection_information,
32
33
  add_extra_fields,
34
+ add_projection_info,
33
35
  get_or_create_collection,
34
36
  )
35
37
  from eodash_catalog.utils import (
@@ -240,10 +242,15 @@ def process_collection_file(
240
242
  collection_config,
241
243
  catalog,
242
244
  )
245
+ elif resource["Name"] in ["COG source", "GeoJSON source"]:
246
+ collection = handle_raw_source(
247
+ catalog_config, resource, collection_config, catalog
248
+ )
243
249
  else:
244
250
  raise ValueError("Type of Resource is not supported")
245
251
  if collection:
246
252
  add_single_item_if_collection_empty(collection)
253
+ add_projection_info(resource, collection)
247
254
  add_to_catalog(collection, catalog, resource, collection_config)
248
255
  else:
249
256
  raise Exception(f"No collection was generated for resource {resource}")
@@ -9,6 +9,7 @@ from pystac import (
9
9
  Catalog,
10
10
  Collection,
11
11
  Extent,
12
+ Item,
12
13
  Link,
13
14
  Provider,
14
15
  SpatialExtent,
@@ -87,36 +88,39 @@ def get_or_create_collection(
87
88
  return collection
88
89
 
89
90
 
90
- def create_web_map_link(layer: dict, role: str) -> Link:
91
+ def create_web_map_link(layer_config: dict, role: str) -> Link:
91
92
  extra_fields = {
92
93
  "roles": [role],
93
- "id": layer["id"],
94
+ "id": layer_config["id"],
94
95
  }
95
- if layer.get("default"):
96
+ if layer_config.get("default"):
96
97
  extra_fields["roles"].append("default")
97
- if layer.get("visible"):
98
+ if layer_config.get("visible"):
98
99
  extra_fields["roles"].append("visible")
99
- if "visible" in layer and not layer["visible"]:
100
+ if "visible" in layer_config and not layer_config["visible"]:
100
101
  extra_fields["roles"].append("invisible")
101
102
 
102
- match layer["protocol"]:
103
+ match layer_config["protocol"].lower():
103
104
  case "wms":
104
105
  # handle wms special config options
105
- extra_fields["wms:layers"] = layer["layers"]
106
- if "styles" in layer:
107
- extra_fields["wms:styles"] = layer["styles"]
108
- # TODO: handle wms dimensions extra_fields["wms:dimensions"]
106
+ extra_fields["wms:layers"] = layer_config["layers"]
107
+ if "styles" in layer_config:
108
+ extra_fields["wms:styles"] = layer_config["styles"]
109
+ if "dimensions" in layer_config:
110
+ extra_fields["wms:dimensions"] = layer_config["dimensions"]
109
111
  case "wmts":
110
- extra_fields["wmts:layer"] = layer["layer"]
111
- # TODO: handle wmts dimensions extra_fields["wmts:dimensions"]
112
+ extra_fields["wmts:layer"] = layer_config["layer"]
113
+ if "dimensions" in layer_config:
114
+ extra_fields["wmts:dimensions"] = layer_config["dimensions"]
112
115
 
113
116
  wml = Link(
114
- rel=layer["protocol"],
115
- target=layer["url"],
116
- media_type=layer.get("media_type", "image/png"),
117
- title=layer["name"],
117
+ rel=layer_config["protocol"],
118
+ target=layer_config["url"],
119
+ media_type=layer_config.get("media_type", "image/png"),
120
+ title=layer_config["name"],
118
121
  extra_fields=extra_fields,
119
122
  )
123
+ add_projection_info(layer_config, wml)
120
124
  return wml
121
125
 
122
126
 
@@ -321,25 +325,26 @@ def add_collection_information(
321
325
  def add_base_overlay_info(
322
326
  collection: Collection, catalog_config: dict, collection_config: dict
323
327
  ) -> None:
324
- # check if default base layers defined
325
- if "default_base_layers" in catalog_config:
328
+ # add custom baselayers specially for this indicator
329
+ if "BaseLayers" in collection_config:
330
+ for layer in collection_config["BaseLayers"]:
331
+ collection.add_link(create_web_map_link(layer, role="baselayer"))
332
+ # alternatively use default base layers defined
333
+ elif "default_base_layers" in catalog_config:
326
334
  with open(f'{catalog_config["default_base_layers"]}.yaml') as f:
327
335
  base_layers = yaml.load(f, Loader=SafeLoader)
328
336
  for layer in base_layers:
329
337
  collection.add_link(create_web_map_link(layer, role="baselayer"))
338
+ # add custom overlays just for this indicator
339
+ if "OverlayLayers" in collection_config:
340
+ for layer in collection_config["OverlayLayers"]:
341
+ collection.add_link(create_web_map_link(layer, role="overlay"))
330
342
  # check if default overlay layers defined
331
- if "default_overlay_layers" in catalog_config:
343
+ elif "default_overlay_layers" in catalog_config:
332
344
  with open("{}.yaml".format(catalog_config["default_overlay_layers"])) as f:
333
345
  overlay_layers = yaml.load(f, Loader=SafeLoader)
334
346
  for layer in overlay_layers:
335
347
  collection.add_link(create_web_map_link(layer, role="overlay"))
336
- if "BaseLayers" in collection_config:
337
- for layer in collection_config["BaseLayers"]:
338
- collection.add_link(create_web_map_link(layer, role="baselayer"))
339
- if "OverlayLayers" in collection_config:
340
- for layer in collection_config["OverlayLayers"]:
341
- collection.add_link(create_web_map_link(layer, role="overlay"))
342
- # TODO: possibility to overwrite default base and overlay layers
343
348
 
344
349
 
345
350
  def add_extra_fields(stac_object: Collection | Catalog | Link, collection_config: dict) -> None:
@@ -357,8 +362,6 @@ def add_extra_fields(stac_object: Collection | Catalog | Link, collection_config
357
362
  stac_object.extra_fields["sensor"] = collection_config["Sensor"]
358
363
  if "Agency" in collection_config:
359
364
  stac_object.extra_fields["agency"] = collection_config["Agency"]
360
- if "yAxis" in collection_config:
361
- stac_object.extra_fields["yAxis"] = collection_config["yAxis"]
362
365
  if "EodashIdentifier" in collection_config:
363
366
  stac_object.extra_fields["subcode"] = collection_config["EodashIdentifier"]
364
367
  if "DataSource" in collection_config:
@@ -375,6 +378,8 @@ def add_extra_fields(stac_object: Collection | Catalog | Link, collection_config
375
378
  stac_object.extra_fields["insituSources"] = collection_config["DataSource"]["InSitu"]
376
379
  if "Other" in collection_config["DataSource"]:
377
380
  stac_object.extra_fields["otherSources"] = collection_config["DataSource"]["Other"]
381
+ if "MapProjection" in collection_config:
382
+ stac_object.extra_fields["mapProjection"] = collection_config["MapProjection"]
378
383
 
379
384
 
380
385
  def get_collection_times_from_config(endpoint_config: dict) -> list[str]:
@@ -388,3 +393,29 @@ def get_collection_times_from_config(endpoint_config: dict) -> list[str]:
388
393
  timedelta_config = endpoint_config["DateTimeInterval"].get("Timedelta", {"days": 1})
389
394
  times = generateDateIsostringsFromInterval(start, end, timedelta_config)
390
395
  return times
396
+
397
+
398
+ def add_projection_info(
399
+ endpoint_config: dict, stac_object: Item | Asset | Collection | Link
400
+ ) -> None:
401
+ if proj := endpoint_config.get("DataProjection"):
402
+ if isinstance(proj, str):
403
+ if proj.lower().startswith("epsg"):
404
+ # consider input such as "EPSG:4326"
405
+ proj = proj.lower().split("EPSG:")[1]
406
+ # consider a number only
407
+ proj = int(proj)
408
+ if isinstance(proj, int):
409
+ # only set if not existing on source stac_object
410
+ if not stac_object.extra_fields.get("proj:epsg"):
411
+ # handling EPSG code for "proj:epsg"
412
+ stac_object.extra_fields["proj:epsg"] = proj
413
+ elif isinstance(proj, dict):
414
+ # custom handling due to incompatibility of proj4js supported syntax (WKT1)
415
+ # and STAC supported syntax (projjson or WKT2)
416
+ # so we are taking over the DataProjection as is and deal with it in the eodash client
417
+ # in a non-standard compliant way
418
+ # https://github.com/proj4js/proj4js/issues/400
419
+ stac_object.extra_fields["proj4_def"] = proj
420
+ else:
421
+ raise Exception(f"Incorrect type of proj definition {proj}")
@@ -35,6 +35,21 @@ def create_geojson_point(lon: int | float, lat: int | float) -> dict[str, Any]:
35
35
  return {"type": "Feature", "geometry": point, "properties": {}}
36
36
 
37
37
 
38
+ def create_geojson_from_bbox(bbox: list[float | int]) -> dict:
39
+ coordinates = [
40
+ [bbox[0], bbox[1]],
41
+ [bbox[2], bbox[1]],
42
+ [bbox[2], bbox[3]],
43
+ [bbox[0], bbox[3]],
44
+ [bbox[0], bbox[1]],
45
+ ]
46
+ polygon = {"type": "Polygon", "coordinates": [coordinates]}
47
+
48
+ feature = {"type": "Feature", "geometry": polygon, "properties": {}}
49
+ feature_collection = {"type": "FeatureCollection", "features": [feature]}
50
+ return feature_collection
51
+
52
+
38
53
  def retrieveExtentFromWMSWMTS(
39
54
  capabilities_url: str, layer: str, version: str = "1.1.1", wmts: bool = False
40
55
  ) -> tuple[list[float], list[str]]:
@@ -206,7 +221,7 @@ def generate_veda_cog_link(endpoint_config: dict, file_url: str | None) -> str:
206
221
 
207
222
  file_url = f"url={file_url}&" if file_url else ""
208
223
 
209
- target_url = f"https://staging-raster.delta-backend.com/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}?{file_url}resampling_method=nearest{bidx}{colormap}{colormap_name}{rescale}"
224
+ target_url = f"https://openveda.cloud/cog/tiles/WebMercatorQuad/{{z}}/{{x}}/{{y}}?{file_url}resampling_method=nearest{bidx}{colormap}{colormap_name}{rescale}"
210
225
  return target_url
211
226
 
212
227
 
@@ -0,0 +1 @@
1
+ {"type": "FeatureCollection", "name": "NUTS_AT_AGRI_3035", "crs": {"type": "name", "properties": {"name": "urn:ogc:def:crs:EPSG::3035"}}, "features": [{"type": "Feature", "properties": {"NUTS_ID": "AT111", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Mittelburgenland", "NUTS_NAME": "Mittelburgenland", "MOUNT_TYPE": 4, "URBN_TYPE": 3, "COAST_TYPE": 3, "FID": "AT111", "yield": {"Wheat": {"average": 9.19, "best": 9.15, "worst": 9.21}, "Maize": {"average": 12.6, "best": 13.64, "worst": 0.26}, "Sunflower": {"average": 4.91, "best": 5.59, "worst": 0.18}, "Soybean": {"average": 3.98, "best": 4.65, "worst": 1.9}}, "water_need": {"Wheat": {"average": 3, "best": 0, "worst": 37}, "Maize": {"average": 152, "best": 99, "worst": 345}, "Sunflower": {"average": 174, "best": 127, "worst": 367}, "Soybean": {"average": 170, "best": 126, "worst": 358}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT111&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT111&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT111&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT111&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT111&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT111&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT111&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT111&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT111&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT111&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT111&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT111&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4821838.7709, 2726165.9716], [4806751.430600001, 2714374.6019], [4794232.3982, 2724666.8182999995], [4799461.1095, 2746013.3567999993], [4802912.6987, 2748897.5074000005], [4821820.7984, 2743038.7753], [4821838.7709, 2726165.9716]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT112", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Nordburgenland", "NUTS_NAME": "Nordburgenland", "MOUNT_TYPE": 4, "URBN_TYPE": 3, "COAST_TYPE": 3, "FID": "AT112", "yield": {"Wheat": {"average": 8.95, "best": 8.82, "worst": 9.28}, "Maize": {"average": 8.69, "best": 11.72, "worst": 0.27}, "Sunflower": {"average": 3.58, "best": 4.77, "worst": 0.11}, "Soybean": {"average": 3.17, "best": 4.28, "worst": 1.53}}, "water_need": {"Wheat": {"average": 3, "best": 0, "worst": 18}, "Maize": {"average": 264, "best": 144, "worst": 387}, "Sunflower": {"average": 289, "best": 172, "worst": 405}, "Soybean": {"average": 265, "best": 179, "worst": 407}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT112&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT112&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT112&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT112&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT112&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT112&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT112&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT112&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT112&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT112&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT112&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT112&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4854633.2936, 2791782.4586999994], [4848182.930199999, 2758424.007200001], [4831130.8323, 2754284.3971999995], [4815471.18, 2758594.6459], [4802912.6987, 2748897.5074000005], [4799461.1095, 2746013.3567999993], [4794492.8671, 2758158.7359999996], [4798615.8835, 2772722.7161999997], [4805034.1021, 2779100.0143999998], [4813148.559699999, 2777119.8232000005], [4830660.8871, 2794709.8901000004], [4841049.4728, 2794508.9826999996], [4846489.8573, 2803511.3914], [4854633.2936, 2791782.4586999994]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT113", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "S\u00fcdburgenland", "NUTS_NAME": "S\u00fcdburgenland", "MOUNT_TYPE": 4, "URBN_TYPE": 3, "COAST_TYPE": 3, "FID": "AT113", "yield": {"Wheat": {"average": 9.18, "best": 9.23, "worst": 9.22}, "Maize": {"average": 13.82, "best": 14.31, "worst": 3.59}, "Sunflower": {"average": 5.54, "best": 6.21, "worst": 3.36}, "Soybean": {"average": 4.6, "best": 4.94, "worst": 3.11}}, "water_need": {"Wheat": {"average": 3, "best": 0, "worst": 41}, "Maize": {"average": 141, "best": 49, "worst": 281}, "Sunflower": {"average": 173, "best": 77, "worst": 298}, "Soybean": {"average": 130, "best": 81, "worst": 295}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT113&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT113&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT113&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT113&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT113&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT113&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT113&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT113&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT113&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT113&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT113&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT113&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4806751.430600001, 2714374.6019], [4812740.354599999, 2687723.0766000003], [4810258.654200001, 2677111.2202000003], [4800025.597100001, 2673793.8384000007], [4786947.8336, 2658724.9250000007], [4778298.5133, 2654263.0853000004], [4785713.0998, 2673003.2632], [4776791.9191, 2712431.4378999993], [4786359.1337, 2720266.3476], [4794232.3982, 2724666.8182999995], [4806751.430600001, 2714374.6019]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT121", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Mostviertel-Eisenwurzen", "NUTS_NAME": "Mostviertel-Eisenwurzen", "MOUNT_TYPE": 2, "URBN_TYPE": 3, "COAST_TYPE": 3, "FID": "AT121", "yield": {"Wheat": {"average": 8.41, "best": 8.35, "worst": 8.44}, "Maize": {"average": 12.73, "best": 14.06, "worst": 0.23}, "Sunflower": {"average": 5.23, "best": 6.26, "worst": 1.39}, "Soybean": {"average": 4.07, "best": 4.91, "worst": 1.98}}, "water_need": {"Wheat": {"average": 4, "best": 0, "worst": 23}, "Maize": {"average": 129, "best": 48, "worst": 304}, "Sunflower": {"average": 155, "best": 75, "worst": 320}, "Soybean": {"average": 142, "best": 66, "worst": 332}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT121&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT121&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT121&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT121&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT121&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT121&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT121&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT121&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT121&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT121&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT121&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT121&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4729365.276799999, 2815976.3673], [4726572.5578000005, 2790666.1566000003], [4715402.456700001, 2777884.0721000005], [4720089.821799999, 2767339.349300001], [4711489.3632, 2762353.5242999997], [4711804.5307, 2756071.9159999993], [4702486.772700001, 2749484.1655], [4680014.4482, 2746538.018100001], [4676275.6162, 2748475.8258999996], [4675665.1109, 2764056.0321999993], [4659253.963099999, 2774810.9908000007], [4654776.8215, 2787028.4026999995], [4656732.846899999, 2801817.7792000007], [4675427.5437, 2796440.8738], [4689776.2969, 2805059.8488999996], [4688301.636499999, 2819349.191400001], [4710448.6927000005, 2817966.0402000006], [4716448.241599999, 2810263.727], [4729365.276799999, 2815976.3673]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT122", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Nieder\u00f6sterreich-S\u00fcd", "NUTS_NAME": "Nieder\u00f6sterreich-S\u00fcd", "MOUNT_TYPE": 3, "URBN_TYPE": 2, "COAST_TYPE": 3, "FID": "AT122", "yield": {"Wheat": {"average": 7.95, "best": 7.92, "worst": 8.01}, "Maize": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Sunflower": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Soybean": {"average": 4.8, "best": 4.9, "worst": 3.33}}, "water_need": {"Wheat": {"average": 3, "best": 0, "worst": 14}, "Maize": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Sunflower": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Soybean": {"average": 71, "best": 31, "worst": 263}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT122&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT122&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT122&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "", "best": "", "worst": ""}, "Sunflower": {"average": "", "best": "", "worst": ""}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT122&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT122&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT122&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4798615.8835, 2772722.7161999997], [4794492.8671, 2758158.7359999996], [4799461.1095, 2746013.3567999993], [4794232.3982, 2724666.8182999995], [4786359.1337, 2720266.3476], [4780029.485200001, 2728347.4551999997], [4760796.640699999, 2734593.8402999993], [4750867.4233, 2744836.3982999995], [4723072.4749, 2758571.9596999995], [4711804.5307, 2756071.9159999993], [4711489.3632, 2762353.5242999997], [4720089.821799999, 2767339.349300001], [4735846.6910999995, 2787573.955600001], [4761466.483899999, 2792463.8223], [4783481.1602, 2776433.5638999995], [4798615.8835, 2772722.7161999997]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT123", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Sankt P\u00f6lten", "NUTS_NAME": "Sankt P\u00f6lten", "MOUNT_TYPE": 2, "URBN_TYPE": 3, "COAST_TYPE": 3, "FID": "AT123", "yield": {"Wheat": {"average": 9.16, "best": 8.8, "worst": 9.12}, "Maize": {"average": 11.99, "best": 13.64, "worst": 1.27}, "Sunflower": {"average": 4.67, "best": 5.77, "worst": 1.3}, "Soybean": {"average": 3.27, "best": 4.49, "worst": 1.05}}, "water_need": {"Wheat": {"average": 13, "best": 5, "worst": 70}, "Maize": {"average": 142, "best": 95, "worst": 312}, "Sunflower": {"average": 171, "best": 123, "worst": 325}, "Soybean": {"average": 174, "best": 65, "worst": 351}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT123&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT123&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT123&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT123&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT123&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT123&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT123&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT123&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT123&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT123&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT123&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT123&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4766617.899499999, 2799482.1423000004], [4761466.483899999, 2792463.8223], [4735846.6910999995, 2787573.955600001], [4720089.821799999, 2767339.349300001], [4715402.456700001, 2777884.0721000005], [4726572.5578000005, 2790666.1566000003], [4729365.276799999, 2815976.3673], [4745246.488, 2824285.150800001], [4754460.202099999, 2810040.6975999996], [4764125.1895, 2808772.2530000005], [4766617.899499999, 2799482.1423000004]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT124", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Waldviertel", "NUTS_NAME": "Waldviertel", "MOUNT_TYPE": 4, "URBN_TYPE": 3, "COAST_TYPE": 3, "FID": "AT124", "yield": {"Wheat": {"average": 8.72, "best": 9.13, "worst": 7.82}, "Maize": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Sunflower": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Soybean": {"average": 3.19, "best": 4.47, "worst": 1.57}}, "water_need": {"Wheat": {"average": 104, "best": 41, "worst": 193}, "Maize": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Sunflower": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Soybean": {"average": 217, "best": 115, "worst": 345}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT124&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT124&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT124&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "", "best": "", "worst": ""}, "Sunflower": {"average": "", "best": "", "worst": ""}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT124&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT124&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT124&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4726983.6402, 2881333.1931999996], [4742889.3682, 2876362.7248], [4750007.8452, 2869424.6975], [4755316.942299999, 2853526.1678], [4745940.7228999995, 2844752.0292000007], [4748191.413899999, 2835851.8060999997], [4745246.488, 2824285.150800001], [4729365.276799999, 2815976.3673], [4716448.241599999, 2810263.727], [4710448.6927000005, 2817966.0402000006], [4688301.636499999, 2819349.191400001], [4680267.486500001, 2833529.7031999994], [4666960.763, 2841107.5930000003], [4671696.735300001, 2855478.0228000004], [4683800.216, 2865231.0216000006], [4688408.726500001, 2889033.9690000005], [4726983.6402, 2881333.1931999996]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT125", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Weinviertel", "NUTS_NAME": "Weinviertel", "MOUNT_TYPE": 4, "URBN_TYPE": 3, "COAST_TYPE": 3, "FID": "AT125", "yield": {"Wheat": {"average": 9.12, "best": 8.94, "worst": 8.83}, "Maize": {"average": 8.96, "best": 10.79, "worst": 0.0}, "Sunflower": {"average": 3.14, "best": 4.01, "worst": 0.0}, "Soybean": {"average": 1.97, "best": 3.41, "worst": 0.38}}, "water_need": {"Wheat": {"average": 45, "best": 13, "worst": 103}, "Maize": {"average": 237, "best": 217, "worst": 392}, "Sunflower": {"average": 266, "best": 239, "worst": 399}, "Soybean": {"average": 251, "best": 247, "worst": 430}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT125&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT125&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT125&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT125&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT125&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT125&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT125&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT125&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT125&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT125&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT125&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT125&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4832035.225400001, 2857836.831599999], [4833565.827299999, 2848880.935799999], [4827291.811899999, 2837437.805400001], [4805108.2996, 2839377.4876000006], [4788044.0265, 2849928.2930999994], [4775034.5013, 2837808.2350999992], [4748191.413899999, 2835851.8060999997], [4745940.7228999995, 2844752.0292000007], [4755316.942299999, 2853526.1678], [4750007.8452, 2869424.6975], [4742889.3682, 2876362.7248], [4783217.797599999, 2866474.0178999994], [4802272.2919, 2876131.341499999], [4826938.591700001, 2867717.001], [4832035.225400001, 2857836.831599999]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT126", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Wiener Umland/Nordteil", "NUTS_NAME": "Wiener Umland/Nordteil", "MOUNT_TYPE": 4, "URBN_TYPE": 1, "COAST_TYPE": 3, "FID": "AT126", "yield": {"Wheat": {"average": 9.05, "best": 8.89, "worst": 9.4}, "Maize": {"average": 7.94, "best": 11.03, "worst": 0.53}, "Sunflower": {"average": 3.0, "best": 4.29, "worst": 0.3}, "Soybean": {"average": 2.53, "best": 3.87, "worst": 0.76}}, "water_need": {"Wheat": {"average": 12, "best": 0, "worst": 30}, "Maize": {"average": 256, "best": 160, "worst": 403}, "Sunflower": {"average": 276, "best": 194, "worst": 411}, "Soybean": {"average": 271, "best": 194, "worst": 438}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT126&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT126&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT126&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT126&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT126&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT126&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT126&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT126&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT126&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT126&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT126&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT126&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4827291.811899999, 2837437.805400001], [4839232.970000001, 2808802.6084000003], [4824388.316199999, 2801383.4985000007], [4810026.3159, 2802881.3099000007], [4805208.368100001, 2815402.4612000007], [4796625.635, 2819975.155200001], [4782534.532400001, 2811756.030099999], [4780681.6302000005, 2803583.5084000006], [4766617.899499999, 2799482.1423000004], [4764125.1895, 2808772.2530000005], [4754460.202099999, 2810040.6975999996], [4745246.488, 2824285.150800001], [4748191.413899999, 2835851.8060999997], [4775034.5013, 2837808.2350999992], [4788044.0265, 2849928.2930999994], [4805108.2996, 2839377.4876000006], [4827291.811899999, 2837437.805400001]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT127", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Wiener Umland/S\u00fcdteil", "NUTS_NAME": "Wiener Umland/S\u00fcdteil", "MOUNT_TYPE": 4, "URBN_TYPE": 2, "COAST_TYPE": 3, "FID": "AT127", "yield": {"Wheat": {"average": 9.36, "best": 9.05, "worst": 9.39}, "Maize": {"average": 8.46, "best": 9.36, "worst": 0.34}, "Sunflower": {"average": 3.15, "best": 3.83, "worst": 0.19}, "Soybean": {"average": 2.64, "best": 3.66, "worst": 0.77}}, "water_need": {"Wheat": {"average": 11, "best": 0, "worst": 59}, "Maize": {"average": 259, "best": 229, "worst": 416}, "Sunflower": {"average": 280, "best": 249, "worst": 428}, "Soybean": {"average": 268, "best": 220, "worst": 432}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT127&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT127&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT127&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT127&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT127&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT127&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT127&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT127&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT127&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT127&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT127&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT127&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4810026.3159, 2802881.3099000007], [4824388.316199999, 2801383.4985000007], [4839232.970000001, 2808802.6084000003], [4846489.8573, 2803511.3914], [4841049.4728, 2794508.9826999996], [4830660.8871, 2794709.8901000004], [4813148.559699999, 2777119.8232000005], [4805034.1021, 2779100.0143999998], [4798615.8835, 2772722.7161999997], [4783481.1602, 2776433.5638999995], [4761466.483899999, 2792463.8223], [4766617.899499999, 2799482.1423000004], [4780681.6302000005, 2803583.5084000006], [4810026.3159, 2802881.3099000007]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT221", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Graz", "NUTS_NAME": "Graz", "MOUNT_TYPE": 3, "URBN_TYPE": 2, "COAST_TYPE": 3, "FID": "AT221", "yield": {"Wheat": {"average": 8.93, "best": 8.98, "worst": 8.78}, "Maize": {"average": 14.22, "best": 14.32, "worst": 9.27}, "Sunflower": {"average": 6.29, "best": 6.37, "worst": 4.32}, "Soybean": {"average": 4.29, "best": 4.9, "worst": 2.82}}, "water_need": {"Wheat": {"average": 34, "best": 0, "worst": 93}, "Maize": {"average": 49, "best": 15, "worst": 236}, "Sunflower": {"average": 85, "best": 16, "worst": 256}, "Soybean": {"average": 77, "best": 0, "worst": 271}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT221&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT221&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT221&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT221&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT221&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT221&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT221&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT221&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT221&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT221&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT221&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT221&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4728914.8758000005, 2703560.558700001], [4734836.407, 2693157.1164999995], [4748250.701199999, 2685596.8812000006], [4753868.7206999995, 2671186.6733], [4748020.863600001, 2670535.9944], [4731988.534700001, 2657714.0924999993], [4711242.9835, 2688609.7184999995], [4703733.7897, 2690653.335000001], [4703272.800899999, 2692290.1689999998], [4716557.587200001, 2704792.4880999997], [4728914.8758000005, 2703560.558700001]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT224", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Oststeiermark", "NUTS_NAME": "Oststeiermark", "MOUNT_TYPE": 2, "URBN_TYPE": 3, "COAST_TYPE": 3, "FID": "AT224", "yield": {"Wheat": {"average": 8.93, "best": 9.0, "worst": 8.97}, "Maize": {"average": 14.64, "best": 14.67, "worst": 8.45}, "Sunflower": {"average": 6.4, "best": 6.54, "worst": 4.09}, "Soybean": {"average": 4.5, "best": 5.02, "worst": 3.06}}, "water_need": {"Wheat": {"average": 3, "best": 0, "worst": 56}, "Maize": {"average": 70, "best": 7, "worst": 241}, "Sunflower": {"average": 94, "best": 19, "worst": 260}, "Soybean": {"average": 105, "best": 6, "worst": 269}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT224&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT224&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT224&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT224&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT224&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT224&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT224&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT224&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT224&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT224&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT224&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT224&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4786359.1337, 2720266.3476], [4776791.9191, 2712431.4378999993], [4785713.0998, 2673003.2632], [4778298.5133, 2654263.0853000004], [4781138.73, 2636341.2249999996], [4763403.3181, 2638817.9725], [4753009.4882, 2637824.3693000004], [4756578.126700001, 2645372.801000001], [4747818.734200001, 2661943.1195], [4748020.863600001, 2670535.9944], [4753868.7206999995, 2671186.6733], [4748250.701199999, 2685596.8812000006], [4734836.407, 2693157.1164999995], [4728914.8758000005, 2703560.558700001], [4746033.6894000005, 2725243.121099999], [4760796.640699999, 2734593.8402999993], [4780029.485200001, 2728347.4551999997], [4786359.1337, 2720266.3476]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT225", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "West- und S\u00fcdsteiermark", "NUTS_NAME": "West- und S\u00fcdsteiermark", "MOUNT_TYPE": 3, "URBN_TYPE": 3, "COAST_TYPE": 3, "FID": "AT225", "yield": {"Wheat": {"average": 8.97, "best": 9.03, "worst": 8.99}, "Maize": {"average": 14.62, "best": 14.58, "worst": 8.12}, "Sunflower": {"average": 6.13, "best": 6.4, "worst": 4.15}, "Soybean": {"average": 4.55, "best": 5.03, "worst": 3.22}}, "water_need": {"Wheat": {"average": 3, "best": 0, "worst": 65}, "Maize": {"average": 91, "best": 4, "worst": 245}, "Sunflower": {"average": 130, "best": 8, "worst": 264}, "Soybean": {"average": 111, "best": 10, "worst": 272}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT225&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT225&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT225&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT225&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT225&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT225&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT225&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT225&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT225&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT225&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT225&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT225&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4748020.863600001, 2670535.9944], [4747818.734200001, 2661943.1195], [4756578.126700001, 2645372.801000001], [4753009.4882, 2637824.3693000004], [4741993.602, 2628691.8389999997], [4734492.067299999, 2630626.1513], [4708765.1296999995, 2628648.7771000005], [4702156.259299999, 2644741.085999999], [4701916.816299999, 2659466.3616000004], [4688942.901799999, 2671473.2507000007], [4703733.7897, 2690653.335000001], [4711242.9835, 2688609.7184999995], [4731988.534700001, 2657714.0924999993], [4748020.863600001, 2670535.9944]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT311", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Innviertel", "NUTS_NAME": "Innviertel", "MOUNT_TYPE": 4, "URBN_TYPE": 3, "COAST_TYPE": 3, "FID": "AT311", "yield": {"Wheat": {"average": 8.07, "best": 8.0, "worst": 8.11}, "Maize": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Sunflower": {"average": "N/A", "best": "N/A", "worst": 5.3}, "Soybean": {"average": 4.84, "best": 4.96, "worst": 3.7}}, "water_need": {"Wheat": {"average": 3, "best": 0, "worst": 10}, "Maize": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Sunflower": {"average": "N/A", "best": "N/A", "worst": 200}, "Soybean": {"average": 25, "best": 0, "worst": 208}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT311&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT311&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT311&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "", "best": "", "worst": ""}, "Sunflower": {"average": "", "best": "", "worst": "/crop_model/regional_forecast?nuts_id=AT311&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT311&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT311&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT311&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4596341.521500001, 2829133.7541000005], [4607259.8697, 2820043.5494], [4612711.6423, 2797314.6400000006], [4600506.891899999, 2785382.1982000005], [4581160.461200001, 2783670.4559000004], [4567548.3138999995, 2771539.2345000003], [4558250.4069, 2768274.2731], [4544161.352499999, 2773820.8451000005], [4534508.8895, 2768916.1735999994], [4525936.166999999, 2781511.587099999], [4539906.565199999, 2792493.4747], [4556759.177300001, 2802949.1988999993], [4572402.1347, 2812302.7816000003], [4574853.3708, 2832082.245100001], [4580163.4092999995, 2837016.5943], [4596341.521500001, 2829133.7541000005]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT312", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "Linz-Wels", "NUTS_NAME": "Linz-Wels", "MOUNT_TYPE": 4, "URBN_TYPE": 2, "COAST_TYPE": 3, "FID": "AT312", "yield": {"Wheat": {"average": 8.88, "best": 8.83, "worst": 8.88}, "Maize": {"average": 13.05, "best": 14.42, "worst": 4.13}, "Sunflower": {"average": 5.35, "best": 6.36, "worst": 1.88}, "Soybean": {"average": 4.01, "best": 4.83, "worst": 1.81}}, "water_need": {"Wheat": {"average": 3, "best": 0, "worst": 20}, "Maize": {"average": 136, "best": 51, "worst": 297}, "Sunflower": {"average": 162, "best": 89, "worst": 308}, "Soybean": {"average": 132, "best": 91, "worst": 318}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT312&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT312&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT312&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "/crop_model/regional_forecast?nuts_id=AT312&crop=MaizeGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT312&crop=MaizeGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT312&crop=MaizeGDD&scenario=worst"}, "Sunflower": {"average": "/crop_model/regional_forecast?nuts_id=AT312&crop=SunflowerGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT312&crop=SunflowerGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT312&crop=SunflowerGDD&scenario=worst"}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT312&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT312&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT312&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4656732.846899999, 2801817.7792000007], [4654776.8215, 2787028.4026999995], [4627633.078600001, 2782466.8113], [4618404.993799999, 2773478.673800001], [4600506.891899999, 2785382.1982000005], [4612711.6423, 2797314.6400000006], [4607259.8697, 2820043.5494], [4619451.7829, 2817006.0536], [4639241.3105, 2825433.0034999996], [4648489.1337, 2819704.3738], [4651649.188100001, 2810828.7657999992], [4649312.8651, 2803855.4235999994], [4656732.846899999, 2801817.7792000007]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}, {"type": "Feature", "properties": {"NUTS_ID": "AT313", "LEVL_CODE": 3, "CNTR_CODE": "AT", "NAME_LATN": "M\u00fchlviertel", "NUTS_NAME": "M\u00fchlviertel", "MOUNT_TYPE": 3, "URBN_TYPE": 3, "COAST_TYPE": 3, "FID": "AT313", "yield": {"Wheat": {"average": 8.93, "best": 8.98, "worst": 8.89}, "Maize": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Sunflower": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Soybean": {"average": 3.54, "best": 4.52, "worst": 1.14}}, "water_need": {"Wheat": {"average": 30, "best": 27, "worst": 69}, "Maize": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Sunflower": {"average": "N/A", "best": "N/A", "worst": "N/A"}, "Soybean": {"average": 134, "best": 89, "worst": 296}}, "links": {"Wheat": {"average": "/crop_model/regional_forecast?nuts_id=AT313&crop=WheatGDD&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT313&crop=WheatGDD&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT313&crop=WheatGDD&scenario=worst"}, "Maize": {"average": "", "best": "", "worst": ""}, "Sunflower": {"average": "", "best": "", "worst": ""}, "Soybean": {"average": "/crop_model/regional_forecast?nuts_id=AT313&crop=Soybean&scenario=average", "best": "/crop_model/regional_forecast?nuts_id=AT313&crop=Soybean&scenario=best", "worst": "/crop_model/regional_forecast?nuts_id=AT313&crop=Soybean&scenario=worst"}}}, "geometry": {"type": "MultiPolygon", "coordinates": [[[[4666960.763, 2841107.5930000003], [4680267.486500001, 2833529.7031999994], [4688301.636499999, 2819349.191400001], [4689776.2969, 2805059.8488999996], [4675427.5437, 2796440.8738], [4656732.846899999, 2801817.7792000007], [4649312.8651, 2803855.4235999994], [4651649.188100001, 2810828.7657999992], [4648489.1337, 2819704.3738], [4639241.3105, 2825433.0034999996], [4619451.7829, 2817006.0536], [4607259.8697, 2820043.5494], [4596341.521500001, 2829133.7541000005], [4601794.366800001, 2840349.2882000003], [4600312.774800001, 2851666.636], [4603174.0754, 2858273.119000001], [4624277.1894000005, 2839622.7402999997], [4641505.738500001, 2837187.209899999], [4651663.9683, 2845246.5511000007], [4666960.763, 2841107.5930000003]]]]}, "units": {"yield": "t/ha", "water_need": "mm"}}]}
@@ -0,0 +1,187 @@
1
+ import json
2
+ import os
3
+ import shutil
4
+ from datetime import datetime
5
+
6
+ import pytest
7
+ from dateutil import parser
8
+ from eodash_catalog.generate_indicators import process_catalog_file
9
+ from eodash_catalog.utils import (
10
+ Options,
11
+ )
12
+
13
+
14
+ @pytest.fixture
15
+ def test_options():
16
+ outputpath = "build"
17
+ # yield instead of return to run code below yield after fixture released from all tests
18
+ yield Options(
19
+ catalogspath="testing-catalogs",
20
+ collectionspath="testing-collections",
21
+ indicatorspath="testing-indicators",
22
+ outputpath=outputpath,
23
+ vd=None,
24
+ ni=None,
25
+ tn=None,
26
+ collections=[],
27
+ )
28
+ # cleanup output after tests finish
29
+ shutil.rmtree(outputpath)
30
+
31
+
32
+ @pytest.fixture()
33
+ def catalog_output_folder(process_catalog_fixture, test_options):
34
+ # not-used fixture needs to be here to trigger catalog generation
35
+ return os.path.join(test_options.outputpath, "testing-catalog-id")
36
+
37
+
38
+ @pytest.fixture
39
+ def catalog_location(test_options):
40
+ file_path = os.path.join(test_options.catalogspath, "testing.yaml")
41
+ return file_path
42
+
43
+
44
+ @pytest.fixture
45
+ def process_catalog_fixture(catalog_location, test_options):
46
+ process_catalog_file(catalog_location, test_options)
47
+
48
+
49
+ def test_catalog_file_exists(catalog_output_folder):
50
+ # test if catalog was created in target location
51
+ assert os.path.exists(catalog_output_folder)
52
+
53
+
54
+ def test_collection_no_wms_has_a_single_item(catalog_output_folder):
55
+ # test that following collections were created as we expect it
56
+ collection_name = "imperviousness_density_2018"
57
+ start_date = "1970-01-01T00:00:00Z"
58
+ root_collection_path = os.path.join(catalog_output_folder, collection_name)
59
+ with open(os.path.join(root_collection_path, "collection.json")) as fp:
60
+ collection_json = json.load(fp)
61
+ # test that custom bbox is set
62
+ assert [-180, -85, 180, 85] in collection_json["extent"]["spatial"]["bbox"]
63
+ # test that time interval is 1970-today
64
+ assert collection_json["extent"]["temporal"]["interval"][0][0] == start_date
65
+ assert (
66
+ datetime.today().date()
67
+ == parser.parse(collection_json["extent"]["temporal"]["interval"][0][1]).date()
68
+ )
69
+ child_collection_path = os.path.join(root_collection_path, collection_name)
70
+ child_child_collection_path = os.path.join(child_collection_path, collection_name)
71
+ item_dir = os.path.join(child_child_collection_path, "1970")
72
+ item_paths = os.listdir(item_dir)
73
+ assert len(item_paths) == 1
74
+ with open(os.path.join(item_dir, item_paths[0])) as fp:
75
+ item_json = json.load(fp)
76
+ assert item_json["properties"]["start_datetime"] == start_date
77
+ assert item_json["collection"] == collection_name
78
+
79
+
80
+ def test_indicator_groups_collections(catalog_output_folder):
81
+ collection_name = "test_indicator_grouping_collections"
82
+ root_collection_path = os.path.join(catalog_output_folder, collection_name)
83
+ with open(os.path.join(root_collection_path, "collection.json")) as fp:
84
+ indicator_json = json.load(fp)
85
+ # test that collection has two child links
86
+ child_links = [link for link in indicator_json["links"] if link["rel"] == "child"]
87
+ assert len(child_links) == 2
88
+ # test that summaries are aggregating individual properties of collections
89
+ assert len(indicator_json["summaries"]["themes"]) == 2
90
+ # test that bbox aggregating works
91
+ indicator_bbox = indicator_json["extent"]["spatial"]["bbox"]
92
+ assert len(indicator_bbox) == 3
93
+ assert [-45.24, 61.13, -35.15, 65.05] in indicator_bbox
94
+ assert [-145.24, -61.13, -135.15, -65.05] in indicator_bbox
95
+
96
+
97
+ def test_indicator_map_projection_added(catalog_output_folder):
98
+ collection_name = "test_indicator_grouping_collections"
99
+ root_collection_path = os.path.join(catalog_output_folder, collection_name)
100
+ with open(os.path.join(root_collection_path, "collection.json")) as fp:
101
+ indicator_json = json.load(fp)
102
+ # test that collection has map projection defined
103
+ assert indicator_json["mapProjection"] == 3035
104
+
105
+
106
+ def test_baselayers_and_overlays_added(catalog_output_folder):
107
+ collection_name = "imperviousness_density_2018"
108
+ root_collection_path = os.path.join(catalog_output_folder, collection_name)
109
+ with open(os.path.join(root_collection_path, "collection.json")) as fp:
110
+ collection_json = json.load(fp)
111
+ baselayer_links = [
112
+ link
113
+ for link in collection_json["links"]
114
+ if link.get("roles") and "baselayer" in link["roles"]
115
+ ]
116
+ overlay_links = [
117
+ link
118
+ for link in collection_json["links"]
119
+ if link.get("roles") and "overlay" in link["roles"]
120
+ ]
121
+ assert len(baselayer_links) == 1
122
+ assert len(overlay_links) == 1
123
+
124
+
125
+ def test_geojson_dataset_handled(catalog_output_folder):
126
+ collection_name = "crop_forecast_at"
127
+ root_collection_path = os.path.join(catalog_output_folder, collection_name)
128
+ child_collection_path = os.path.join(root_collection_path, collection_name)
129
+ child_child_collection_path = os.path.join(child_collection_path, collection_name)
130
+ item_dir = os.path.join(child_child_collection_path, "2024")
131
+ item_paths = os.listdir(item_dir)
132
+ assert len(item_paths) == 1
133
+ with open(os.path.join(child_collection_path, "collection.json")) as fp:
134
+ collection_json = json.load(fp)
135
+ geojson_links = [
136
+ link
137
+ for link in collection_json["links"]
138
+ if (link.get("rel", "") == "item" and len(link.get("assets", [])) > 0)
139
+ ]
140
+ # geojson link with assets exists
141
+ assert len(geojson_links) > 0
142
+ # and has a correct value
143
+ assert (
144
+ geojson_links[0]["assets"][0]
145
+ == "https://raw.githubusercontent.com/eodash/eodash_catalog/main/tests/test-data/regional_forecast.json"
146
+ )
147
+ # epsg code saved on collection
148
+ assert collection_json["proj:epsg"] == 3035
149
+ with open(os.path.join(item_dir, item_paths[0])) as fp:
150
+ item_json = json.load(fp)
151
+ # mimetype saved correctly
152
+ assert item_json["assets"]["vector_data"]["type"] == "application/geo+json"
153
+ assert item_json["collection"] == collection_name
154
+ # epsg code is saved to item
155
+ assert item_json["proj:epsg"] == 3035
156
+ # epsg code is saved to assets
157
+ assert item_json["assets"]["vector_data"]["proj:epsg"] == 3035
158
+
159
+
160
+ def test_cog_dataset_handled(catalog_output_folder):
161
+ collection_name = "solar_energy"
162
+ root_collection_path = os.path.join(catalog_output_folder, collection_name)
163
+ child_collection_path = os.path.join(root_collection_path, collection_name)
164
+ child_child_collection_path = os.path.join(child_collection_path, collection_name)
165
+ item_dir = os.path.join(child_child_collection_path, "2023")
166
+ item_paths = os.listdir(item_dir)
167
+ with open(os.path.join(item_dir, item_paths[0])) as fp:
168
+ item_json = json.load(fp)
169
+ assert item_json["assets"]["solar_power"]["type"] == "image/tiff"
170
+ assert item_json["collection"] == collection_name
171
+
172
+
173
+ def test_baselayer_with_custom_projection_added(catalog_output_folder):
174
+ collection_name = "test_indicator_grouping_collections"
175
+ root_collection_path = os.path.join(catalog_output_folder, collection_name)
176
+ with open(os.path.join(root_collection_path, "collection.json")) as fp:
177
+ indicator_json = json.load(fp)
178
+ baselayer_links = [
179
+ link
180
+ for link in indicator_json["links"]
181
+ if link.get("roles") and "baselayer" in link["roles"]
182
+ ]
183
+ # test that manual BaseLayers definition
184
+ # overwrites default_baselayers, so there is just 1
185
+ assert len(baselayer_links) == 1
186
+ # test that custom proj4 definition is added to link
187
+ assert baselayer_links[0]["proj4_def"]["name"] == "ORTHO:680500"
@@ -0,0 +1,12 @@
1
+ id: "testing-catalog-id"
2
+ title: "testing catalog for integration tests"
3
+ description: "testing catalog"
4
+ endpoint: "https://gtif-cerulean.github.io/catalog/cerulean/"
5
+ default_base_layers: "testing-layers/baselayers"
6
+ default_overlay_layers: "testing-layers/overlays"
7
+ assets_endpoint: "https://raw.githubusercontent.com/eurodatacube/eodash-assets/main/"
8
+ collections:
9
+ - test_wms_no_time
10
+ - test_indicator
11
+ - test_CROPOMAT1
12
+ - test_see_solar_energy
@@ -0,0 +1,51 @@
1
+ Name: crop_forecast_at
2
+ Title: Austria yield
3
+ EodashIdentifier: CROPOMAT1
4
+ Subtitle: Yield forecast under "what-if" scenarios
5
+ Description: ""
6
+ Themes:
7
+ - agriculture
8
+ - economy
9
+ Tags:
10
+ - Crop
11
+ - Yield
12
+ - Water
13
+ - Forecast
14
+ DataSource:
15
+ Spaceborne:
16
+ Satellite:
17
+ - Sentinel-2
18
+ - CLMS HR-VPP Vegetation Indices
19
+ - CLMS HR-VPP Seasonal Trajectory
20
+ InSitu:
21
+ - Meteorological data from local Meteorological offices (AT)
22
+ Agency:
23
+ - ESA
24
+ Provider:
25
+ - Name: CropOM
26
+ Url: https://cropom.com
27
+ - Name: Danube Data Cube
28
+ Url: https://ddc.cropom.com
29
+ - Name: Copernicus Land Monitoring Service
30
+ Url: https://land.copernicus.eu/en/dataset-catalog
31
+ - Name: FAO
32
+ Url: https://www.fao.org/home/en
33
+ License:
34
+ - Name: Commercial license
35
+ Url: https://cropom.com/terms_and_conditions.pdf
36
+ References:
37
+ - Name: Danube Data Cube documentation
38
+ Url: https://doc.cropom.com
39
+ - Name: AquaCrop
40
+ Url: https://www.fao.org/aquacrop/en/
41
+ Resources:
42
+ - Name: GeoJSON source
43
+ Style: crop_forecast_CropOM/style_yield.json
44
+ Bbox: [9.27, 46.2, 17.3, 49.2]
45
+ DataProjection: 3035
46
+ TimeEntries:
47
+ - Time: "20240101"
48
+ Assets:
49
+ - Identifier: vector_data
50
+ File: "https://raw.githubusercontent.com/eodash/eodash_catalog/main/tests/test-data/regional_forecast.json"
51
+ Legend: crop_forecast_CropOM/cm_legend.png
@@ -0,0 +1,25 @@
1
+ Name: solar_energy
2
+ Title: Solar energy cog example
3
+ EodashIdentifier: SEE
4
+ Description: testing
5
+ Themes:
6
+ - placeholder
7
+ Tags:
8
+ - placeholder
9
+ DataSource:
10
+ Spaceborne:
11
+ Satellite:
12
+ - Sentinel-2
13
+ Agency:
14
+ - ESA
15
+ Resources:
16
+ - Name: COG source
17
+ Style: test_assets/cog_style.json
18
+ Bbox: [11, 46.5, 15.5, 48.9]
19
+ TimeEntries:
20
+ - Time: "2023"
21
+ Assets:
22
+ - Identifier: solar_power
23
+ File: https://eox-gtif-public.s3.eu-central-1.amazonaws.com/DHI/v2/SolarPowerPotential_Annual_COG_clipped_3857_fixed.tif
24
+ - Identifier: wsf
25
+ File: https://eox-gtif-public.s3.eu-central-1.amazonaws.com/DHI/WSF_EucDist_Austria_3857_COG_fix.tif
@@ -0,0 +1,19 @@
1
+ Name: test_tif_demo_1
2
+ Title: test_tif_demo_1
3
+ EodashIdentifier: test_tif_demo_1
4
+ Description: ''
5
+ Themes:
6
+ - cryosphere
7
+ Tags:
8
+ - placeholder
9
+ Satellite:
10
+ - placeholder
11
+ Sensor:
12
+ - placeholder
13
+ Agency:
14
+ - ESA
15
+ Image: Polartep_SeaIceDetection_tif_demo/thumbnail.png # existing file
16
+ Resources:
17
+ - Name: Collection-only
18
+ EndPoint: Collection-only
19
+ OverwriteBBox: [-45.24, 61.13, -35.15, 65.05]
@@ -0,0 +1,19 @@
1
+ Name: test_tif_demo_2
2
+ Title: test_tif_demo_2
3
+ EodashIdentifier: test_tif_demo_2
4
+ Description: ''
5
+ Themes:
6
+ - agriculture
7
+ Tags:
8
+ - placeholder
9
+ Satellite:
10
+ - placeholder
11
+ Sensor:
12
+ - placeholder
13
+ Agency:
14
+ - ESA
15
+ Image: Polartep_SeaIceDetection_tif_demo/thumbnail.png # existing file
16
+ Resources:
17
+ - Name: Collection-only
18
+ EndPoint: Collection-only
19
+ OverwriteBBox: [-145.24, -61.13, -135.15, -65.05]
@@ -0,0 +1,21 @@
1
+ Name: imperviousness_density_2018
2
+ Title: Imperviousness Density 2018 WMS
3
+ EodashIdentifier: IMD
4
+ Description: CLMS Imperviousness Density 2018 10m resolution.
5
+ Themes:
6
+ - example theme
7
+ Tags:
8
+ - example tag
9
+ DataSource:
10
+ Spaceborne:
11
+ Satellite:
12
+ - example satellite
13
+ Sensor:
14
+ - example sensorname
15
+ Agency:
16
+ - EEA
17
+ Resources:
18
+ - EndPoint: https://image.discomap.eea.europa.eu/arcgis/services/GioLandPublic/HRL_ImperviousnessDensity_2018/ImageServer/WMSServer
19
+ Name: WMS
20
+ LayerId: 'HRL_ImperviousnessDensity_2018:IMD_MosaicSymbology.rft'
21
+ Version: 1.3.0
@@ -0,0 +1,20 @@
1
+ Name: test_indicator_grouping_collections
2
+ Title: test_indicator_grouping_collections
3
+ EodashIdentifier: test_indicator_1
4
+ Description: 'test_indicator_1_description'
5
+ MapProjection: 3035
6
+ Collections:
7
+ - test_tif_demo_1
8
+ - test_tif_demo_2
9
+ BaseLayers:
10
+ - id: sx-cat_ortho680500
11
+ name: Terrain Light Stereographic North
12
+ url: '//sxcat-demo.eox.at/sxcat_maps/wms'
13
+ layers: sx-cat_ortho680500
14
+ attribution: '{ Terrain light: Data &copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors and <a href="//maps.eox.at/#data" target="_blank">others</a>, Rendering &copy; <a href="http://eox.at" target="_blank">EOX</a> }'
15
+ visible: false
16
+ protocol: wms
17
+ DataProjection:
18
+ name: 'ORTHO:680500'
19
+ def: '+proj=ortho +lat_0=90 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +units=m +no_defs'
20
+
@@ -0,0 +1,7 @@
1
+ - id: cloudless-2022
2
+ name: EOxCloudless 2022
3
+ url: '//s2maps-tiles.eu/wmts/1.0.0/s2cloudless-2022_3857/default/g/{z}/{y}/{x}.jpeg'
4
+ media_type: image/jpeg
5
+ attribution: '{ EOxCloudless 2022: <a xmlns:dct="http://purl.org/dc/terms/" href="//s2maps.eu" target="_blank" property="dct:title">Sentinel-2 cloudless - s2maps.eu</a> by <a xmlns:cc="http://creativecommons.org/ns#" href="//eox.at" target="_blank" property="cc:attributionName" rel="cc:attributionURL">EOX IT Services GmbH</a> (Contains modified Copernicus Sentinel data 2022) }'
6
+ maxNativeZoom: 17
7
+ protocol: 'xyz'
@@ -0,0 +1,7 @@
1
+ - id: overlay_bright
2
+ name: 'Overlay labels'
3
+ url: '//s2maps-tiles.eu/wmts/1.0.0/overlay_base_bright_3857/default/g/{z}/{y}/{x}.png'
4
+ attribution: '{ Overlay: Data &copy; <a href="http://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors, Made with Natural Earth, Rendering &copy; <a href="//eox.at" target="_blank">EOX</a> }'
5
+ visible: true
6
+ maxNativeZoom: 14
7
+ protocol: 'xyz'
@@ -1,2 +0,0 @@
1
- def test_create_test_run():
2
- assert True