eodash_catalog 0.3.2__tar.gz → 0.3.3__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 (50) hide show
  1. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/.bumpversion.cfg +1 -1
  2. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/Dockerfile +1 -1
  3. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/PKG-INFO +1 -1
  4. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/src/eodash_catalog/__about__.py +1 -1
  5. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/src/eodash_catalog/endpoints.py +60 -9
  6. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/src/eodash_catalog/stac_handling.py +8 -1
  7. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/src/eodash_catalog/utils.py +31 -4
  8. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/.dockerignore +0 -0
  9. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/.github/workflows/ci.yml +0 -0
  10. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/.github/workflows/python-publish.yml +0 -0
  11. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/.gitignore +0 -0
  12. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/.vscode/extensions.json +0 -0
  13. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/.vscode/settings.json +0 -0
  14. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/LICENSE.txt +0 -0
  15. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/README.md +0 -0
  16. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/pyproject.toml +0 -0
  17. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/requirements.txt +0 -0
  18. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/ruff.toml +0 -0
  19. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/src/eodash_catalog/__init__.py +0 -0
  20. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/src/eodash_catalog/duration.py +0 -0
  21. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/src/eodash_catalog/generate_indicators.py +0 -0
  22. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/src/eodash_catalog/sh_endpoint.py +0 -0
  23. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/src/eodash_catalog/thumbnails.py +0 -0
  24. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/__init__.py +0 -0
  25. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/test-data/regional_forecast.json +0 -0
  26. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/test_generate.py +0 -0
  27. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/test_geoparquet.py +0 -0
  28. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-catalogs/testing-json.json +0 -0
  29. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-catalogs/testing.yaml +0 -0
  30. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_CROPOMAT1.yaml +0 -0
  31. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_cmems.json +0 -0
  32. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_cog.json +0 -0
  33. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_geodb.yaml +0 -0
  34. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_geodb_locations.yaml +0 -0
  35. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_geojson.yaml +0 -0
  36. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_locations_processing.json +0 -0
  37. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_process.yaml +0 -0
  38. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_projection.json +0 -0
  39. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_see_solar_energy.yaml +0 -0
  40. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_sh_wms.json +0 -0
  41. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_sh_wms_locations.json +0 -0
  42. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_tif_demo_1.yaml +0 -0
  43. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_tif_demo_1_json.json +0 -0
  44. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_tif_demo_2.yaml +0 -0
  45. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_veda.json +0 -0
  46. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_veda_tiles.json +0 -0
  47. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-collections/test_wms_no_time.yaml +0 -0
  48. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-indicators/test_indicator.yaml +0 -0
  49. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-layers/baselayers.yaml +0 -0
  50. {eodash_catalog-0.3.2 → eodash_catalog-0.3.3}/tests/testing-layers/overlays.yaml +0 -0
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.3.2
2
+ current_version = 0.3.3
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+))?
@@ -29,4 +29,4 @@ RUN eodash_catalog --help
29
29
 
30
30
  CMD ["eodash_catalog"]
31
31
 
32
- LABEL version="0.3.2"
32
+ LABEL version="0.3.3"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eodash_catalog
3
- Version: 0.3.2
3
+ Version: 0.3.3
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.3.2"
4
+ __version__ = "0.3.3"
@@ -609,19 +609,31 @@ def handle_GeoDB_endpoint(
609
609
  items = []
610
610
  for v in values:
611
611
  # add items based on inputData fields for each time step available in values
612
- first_match = next(
612
+ first_match: dict = next(
613
613
  (item for item in input_data if item.get("Identifier") == v["input_data"]), None
614
614
  )
615
615
  time_object = datetime.fromisoformat(v["time"])
616
+ if endpoint_config.get("MapReplaceDates"):
617
+ # get mapping of AOI_ID to list of dates
618
+ available_dates_for_aoi_id = endpoint_config.get("MapReplaceDates").get(
619
+ v["aoi_id"]
620
+ )
621
+ if available_dates_for_aoi_id:
622
+ formatted_datetime = time_object.strftime("%Y-%m-%d")
623
+ if formatted_datetime not in available_dates_for_aoi_id:
624
+ # discard this date because not in available map dates
625
+ continue
616
626
  # extract wkt geometry from sub_aoi
617
627
  if "sub_aoi" in v and v["sub_aoi"] != "/":
618
628
  # create geometry from wkt
619
- geometry = mapping(wkt.loads(v["sub_aoi"]))
629
+ shapely_geometry = wkt.loads(v["sub_aoi"])
630
+ geometry = mapping(shapely_geometry)
620
631
  # converting multipolygon to polygon to avoid shapely throwing an exception
621
632
  # in collection extent from geoparquet table generation
622
633
  # while trying to create a multipolygon extent of all multipolygons
623
634
  if geometry["type"] == "MultiPolygon":
624
635
  geometry = {"type": "Polygon", "coordinates": geometry["coordinates"][0]}
636
+ bbox = shapely_geometry.bounds
625
637
  else:
626
638
  geometry = create_geometry_from_bbox(bbox)
627
639
  item = Item(
@@ -640,7 +652,7 @@ def handle_GeoDB_endpoint(
640
652
  "wms:layers": [first_match["Layers"]],
641
653
  "role": ["data"],
642
654
  }
643
- if url.startswith("https://services.sentinel-hub.com/ogc/wms/"):
655
+ if "sentinel-hub.com" in url:
644
656
  instanceId = os.getenv("SH_INSTANCE_ID")
645
657
  if "InstanceId" in endpoint_config:
646
658
  instanceId = endpoint_config["InstanceId"]
@@ -655,7 +667,7 @@ def handle_GeoDB_endpoint(
655
667
  {"wms:dimensions": {"TIME": f"{start_date}/{end_date}"}}
656
668
  )
657
669
  # we add the instance id to the url
658
- url = f"https://services.sentinel-hub.com/ogc/wms/{instanceId}"
670
+ url = f"{url}{instanceId}"
659
671
  else:
660
672
  extra_fields.update({"wms:dimensions": {"TIME": v["time"]}})
661
673
  link = Link(
@@ -667,6 +679,34 @@ def handle_GeoDB_endpoint(
667
679
  )
668
680
  item.add_link(link)
669
681
  items.append(item)
682
+ case "XYZ":
683
+ # handler for NASA apis
684
+ url = first_match["Url"]
685
+ extra_fields = {}
686
+ # replace time to a formatted version
687
+ date_formatted = time_object.strftime(
688
+ first_match.get("DateFormat", "%Y_%m_%d")
689
+ )
690
+ target_url = url.replace("{time}", date_formatted)
691
+ if SiteMapping := first_match.get("SiteMapping"):
692
+ # match with aoi_id
693
+ site = SiteMapping.get(v["aoi_id"])
694
+ # replace in URL
695
+ if site:
696
+ target_url = target_url.replace("{site}", site)
697
+ else:
698
+ LOGGER.info(
699
+ f"Warning: no match for SiteMapping in config for {site}"
700
+ )
701
+ link = Link(
702
+ rel="xyz",
703
+ target=target_url,
704
+ media_type="image/png",
705
+ title=collection_config["Name"],
706
+ extra_fields=extra_fields,
707
+ )
708
+ item.add_link(link)
709
+ items.append(item)
670
710
  save_items(
671
711
  locations_collection,
672
712
  items,
@@ -690,6 +730,7 @@ def handle_GeoDB_endpoint(
690
730
  link.extra_fields["latlng"] = latlon
691
731
  link.extra_fields["country"] = country
692
732
  link.extra_fields["name"] = city
733
+ add_collection_information(catalog_config, locations_collection, collection_config)
693
734
 
694
735
  if "yAxis" not in collection_config:
695
736
  # fetch yAxis and store it to data, preventing need to save it per dataset in yml
@@ -781,19 +822,24 @@ def handle_WMS_endpoint(
781
822
  # Create an item per time to allow visualization in stac clients
782
823
  if len(datetimes) > 0:
783
824
  for dt in datetimes:
825
+ # case of wms interval coming from config
826
+ dt_item = dt[0] if isinstance(dt, list) else dt
784
827
  item = Item(
785
- id=format_datetime_to_isostring_zulu(dt),
828
+ id=format_datetime_to_isostring_zulu(dt_item),
786
829
  bbox=spatial_extent,
787
830
  properties={},
788
831
  geometry=create_geometry_from_bbox(spatial_extent),
789
- datetime=dt,
832
+ datetime=dt_item,
790
833
  stac_extensions=[
791
834
  "https://stac-extensions.github.io/web-map-links/v1.1.0/schema.json",
792
835
  ],
793
836
  assets={"dummy_asset": Asset(href="")},
794
837
  )
795
838
  add_projection_info(endpoint_config, item)
796
- add_visualization_info(item, collection_config, endpoint_config, datetimes=[dt])
839
+ dt_visualization = dt if isinstance(dt, list) else [dt]
840
+ add_visualization_info(
841
+ item, collection_config, endpoint_config, datetimes=dt_visualization
842
+ )
797
843
  items.append(item)
798
844
  else:
799
845
  LOGGER.warn(f"NO datetimes returned for collection: {collection_config['Name']}!")
@@ -878,7 +924,6 @@ def add_visualization_info(
878
924
  start_isostring = format_datetime_to_isostring_zulu(dt)
879
925
  # SH WMS for public collections needs time interval, we use full day here
880
926
  end = dt + timedelta(days=1) - timedelta(milliseconds=1)
881
- # we have start_datetime and end_datetime
882
927
  if len(datetimes) == 2:
883
928
  end = datetimes[1]
884
929
  end_isostring = format_datetime_to_isostring_zulu(end)
@@ -916,7 +961,13 @@ def add_visualization_info(
916
961
  )
917
962
  dimensions[key] = value
918
963
  if datetimes is not None:
919
- dimensions["TIME"] = format_datetime_to_isostring_zulu(datetimes[0])
964
+ if len(datetimes) > 1:
965
+ start = format_datetime_to_isostring_zulu(datetimes[0])
966
+ end = format_datetime_to_isostring_zulu(datetimes[1])
967
+ interval = f"{start}/{end}"
968
+ dimensions["TIME"] = interval
969
+ else:
970
+ dimensions["TIME"] = format_datetime_to_isostring_zulu(datetimes[0])
920
971
  if dimensions != {}:
921
972
  extra_fields["wms:dimensions"] = dimensions
922
973
  if endpoint_config.get("Styles"):
@@ -18,6 +18,7 @@ from structlog import get_logger
18
18
  from eodash_catalog.utils import (
19
19
  generateDatetimesFromInterval,
20
20
  get_full_url,
21
+ make_intervals,
21
22
  parse_datestring_to_tz_aware_datetime,
22
23
  read_config_file,
23
24
  )
@@ -535,16 +536,22 @@ def add_extra_fields(
535
536
  def get_collection_datetimes_from_config(endpoint_config: dict) -> list[datetime]:
536
537
  times_datetimes: list[datetime] = []
537
538
  if endpoint_config:
539
+ interval_between_dates = endpoint_config.get("WMSIntervalsBetweenDates")
538
540
  if endpoint_config.get("Times"):
539
541
  times = list(endpoint_config.get("Times", []))
540
542
  times_datetimes = sorted(
541
543
  [parse_datestring_to_tz_aware_datetime(time) for time in times]
542
544
  )
545
+ if interval_between_dates:
546
+ # convert to list of datetime_start and datetime_end
547
+ times_datetimes = make_intervals(times_datetimes)
543
548
  elif endpoint_config.get("DateTimeInterval"):
544
549
  start = endpoint_config["DateTimeInterval"].get("Start", "2020-09-01T00:00:00Z")
545
550
  end = endpoint_config["DateTimeInterval"].get("End", "2020-10-01T00:00:00Z")
546
551
  timedelta_config = endpoint_config["DateTimeInterval"].get("Timedelta", {"days": 1})
547
- times_datetimes = generateDatetimesFromInterval(start, end, timedelta_config)
552
+ times_datetimes = generateDatetimesFromInterval(
553
+ start, end, timedelta_config, interval_between_dates
554
+ )
548
555
  return times_datetimes
549
556
 
550
557
 
@@ -214,7 +214,7 @@ def parse_duration(datestring):
214
214
 
215
215
 
216
216
  def generateDatetimesFromInterval(
217
- start: str, end: str, timedelta_config: dict | None = None
217
+ start: str, end: str, timedelta_config: dict | None = None, interval_between_dates: bool = False
218
218
  ) -> list[datetime]:
219
219
  if timedelta_config is None:
220
220
  timedelta_config = {}
@@ -226,7 +226,10 @@ def generateDatetimesFromInterval(
226
226
  delta = timedelta(**timedelta_config)
227
227
  dates = []
228
228
  while start_dt <= end_dt:
229
- dates.append(start_dt)
229
+ if interval_between_dates:
230
+ dates.append([start_dt, start_dt + delta - timedelta(seconds=1)])
231
+ else:
232
+ dates.append(start_dt)
230
233
  start_dt += delta
231
234
  return dates
232
235
 
@@ -444,8 +447,9 @@ def update_extents_from_collection_children(collection: Collection):
444
447
  ):
445
448
  individual_datetimes.extend(c_child.extent.temporal.intervals[0]) # type: ignore
446
449
  individual_datetimes = list(filter(lambda x: x is not None, individual_datetimes))
447
- time_extent = [min(individual_datetimes), max(individual_datetimes)]
448
- collection.extent.temporal = TemporalExtent([time_extent])
450
+ if individual_datetimes:
451
+ time_extent = [min(individual_datetimes), max(individual_datetimes)]
452
+ collection.extent.temporal = TemporalExtent([time_extent])
449
453
 
450
454
 
451
455
  def extract_extent_from_geoparquet(table) -> tuple[TemporalExtent, SpatialExtent]:
@@ -615,3 +619,26 @@ def merge_bboxes(bboxes: list[list[float]]) -> list[float]:
615
619
  max_lat = max(b[3] for b in bboxes)
616
620
 
617
621
  return [min_lon, min_lat, max_lon, max_lat]
622
+
623
+
624
+ def make_intervals(datetimes: list[datetime]) -> list[list[datetime]]:
625
+ """
626
+ Converts a list of datetimes into list of lists of datetimes in format of [start,end]
627
+ where end is next element in original list minus 1 second
628
+ """
629
+ intervals = []
630
+ n = len(datetimes)
631
+ for i in range(n):
632
+ start = datetimes[i]
633
+ if i < n - 1:
634
+ # end is next datetime minus one second
635
+ end = datetimes[i + 1] - timedelta(seconds=1)
636
+ else:
637
+ prev_interval = timedelta(seconds=0)
638
+ # last item: use previous interval length added to last start
639
+ if n > 1:
640
+ prev_interval = datetimes[-1] - datetimes[-2]
641
+ end = start + prev_interval
642
+ intervals.append([start, end])
643
+ LOGGER.info(intervals)
644
+ return intervals
File without changes
File without changes