covjsonkit 0.2.19__tar.gz → 0.2.20__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.
Files changed (113) hide show
  1. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/PKG-INFO +1 -1
  2. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/encoder.py +70 -36
  3. covjsonkit-0.2.20/covjsonkit/version.py +1 -0
  4. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit.egg-info/PKG-INFO +1 -1
  5. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/pyproject.toml +1 -1
  6. covjsonkit-0.2.19/covjsonkit/version.py +0 -1
  7. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/.github/ci-hpc-config.yml +0 -0
  8. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/.github/workflows/ci.yaml +0 -0
  9. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/.github/workflows/label-public-pr.yml +0 -0
  10. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/.gitignore +0 -0
  11. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/.pre-commit-config.yaml +0 -0
  12. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/ACKNOWLEDGEMENTS.rst +0 -0
  13. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/LICENSE +0 -0
  14. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/MANIFEST.in +0 -0
  15. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/Makefile +0 -0
  16. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/README.md +0 -0
  17. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/Coverage.py +0 -0
  18. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/CoverageCollection.py +0 -0
  19. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/__init__.py +0 -0
  20. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/api.py +0 -0
  21. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/config.py +0 -0
  22. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/data/__init__.py +0 -0
  23. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/data/dwd/param.json +0 -0
  24. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/data/dwd/param_id.json +0 -0
  25. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/data/dwd/unit.json +0 -0
  26. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/data/ecmwf/param.json +0 -0
  27. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/data/ecmwf/param_id.json +0 -0
  28. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/data/ecmwf/unit.json +0 -0
  29. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/BoundingBox.py +0 -0
  30. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/Circle.py +0 -0
  31. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/Frame.py +0 -0
  32. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/Grid.py +0 -0
  33. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/Path.py +0 -0
  34. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/Position.py +0 -0
  35. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/Shapefile.py +0 -0
  36. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/TimeSeries.py +0 -0
  37. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/VerticalProfile.py +0 -0
  38. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/Wkt.py +0 -0
  39. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/__init__.py +0 -0
  40. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/decoder/decoder.py +0 -0
  41. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/BoundingBox.py +0 -0
  42. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/Circle.py +0 -0
  43. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/Frame.py +0 -0
  44. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/Grid.py +0 -0
  45. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/Path.py +0 -0
  46. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/Position.py +0 -0
  47. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/Shapefile.py +0 -0
  48. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/TimeSeries.py +0 -0
  49. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/VerticalProfile.py +0 -0
  50. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/Wkt.py +0 -0
  51. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/encoder/__init__.py +0 -0
  52. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/param_db.py +0 -0
  53. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit/utils.py +0 -0
  54. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit.egg-info/SOURCES.txt +0 -0
  55. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit.egg-info/dependency_links.txt +0 -0
  56. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit.egg-info/not-zip-safe +0 -0
  57. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit.egg-info/requires.txt +0 -0
  58. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/covjsonkit.egg-info/top_level.txt +0 -0
  59. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/docs/images/ECMWF_logo.svg.png +0 -0
  60. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/docs/images/Logo_Destination_Earth_Colours.png +0 -0
  61. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/example_config.json +0 -0
  62. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/requirements.txt +0 -0
  63. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/scripts/generate_param_db.py +0 -0
  64. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/setup.cfg +0 -0
  65. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/setup.py +0 -0
  66. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/conftest.py +0 -0
  67. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/data/test_coverage1.json +0 -0
  68. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/data/test_coverage2.json +0 -0
  69. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/data/test_geotiff.json +0 -0
  70. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/data/test_multipoint_coverage.json +0 -0
  71. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/data/test_path_coverage.json +0 -0
  72. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/data/test_timeseries_coverage.json +0 -0
  73. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/data/test_timeseries_param_t.json +0 -0
  74. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/data/test_timeseries_xyz_coverage.json +0 -0
  75. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/data/test_verticalprofile_coverage.json +0 -0
  76. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/data/test_verticalprofile_param_t.json +0 -0
  77. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/data/test_verticalprofile_xyz_coverage.json +0 -0
  78. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/geojsontest.py +0 -0
  79. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/requirements_test.txt +0 -0
  80. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_decoder_bounding_box.py +0 -0
  81. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_decoder_circle.py +0 -0
  82. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_decoder_frame.py +0 -0
  83. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_decoder_grid.py +0 -0
  84. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_decoder_position.py +0 -0
  85. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_decoder_shapefile.py +0 -0
  86. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_decoder_time_series.py +0 -0
  87. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_decoder_vertical_profile.py +0 -0
  88. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_decoder_wkt.py +0 -0
  89. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_bounding_box.py +0 -0
  90. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_bounding_box_from_polytope.py +0 -0
  91. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_circle_from_polytope.py +0 -0
  92. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_frame.py +0 -0
  93. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_frame_from_polytope.py +0 -0
  94. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_grid.py +0 -0
  95. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_grid_from_polytope.py +0 -0
  96. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_path.py +0 -0
  97. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_path_from_polytope.py +0 -0
  98. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_position_from_polytope.py +0 -0
  99. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_shapefile.py +0 -0
  100. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_shapefile_from_polytope.py +0 -0
  101. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_time_series.py +0 -0
  102. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_time_series_from_polytope.py +0 -0
  103. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_vertical_profile_from_polytope.py +0 -0
  104. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_wkt.py +0 -0
  105. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_encoder_wkt_from_polytope.py +0 -0
  106. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_geojson.py +0 -0
  107. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_geotiff.py +0 -0
  108. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_util_merge.py +0 -0
  109. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_xarray_multipoint.py +0 -0
  110. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_xarray_path.py +0 -0
  111. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_xarray_ts.py +0 -0
  112. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tests/test_xarray_vp.py +0 -0
  113. {covjsonkit-0.2.19 → covjsonkit-0.2.20}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: covjsonkit
3
- Version: 0.2.19
3
+ Version: 0.2.20
4
4
  Home-page: https://github.com/ecmwf/covjsonkit
5
5
  Author: ECMWF
6
6
  Author-email: James.Hawkes@ecmwf.int, Adam.Warde@ecmwf.int, Mathilde.Leuridan@ecmwf.int
@@ -589,7 +589,7 @@ class Encoder(ABC):
589
589
  ]
590
590
  )
591
591
 
592
- def walk_tree_month(self, tree, fields, coords, mars_metadata, range_dict):
592
+ def walk_tree_month(self, tree, fields, coords, mars_metadata, range_dict, _ctx=None):
593
593
  """Walk the result tree for monthly-mean streams (e.g. clmn).
594
594
 
595
595
  These streams use ``year`` and ``month`` axes instead of ``date``/``time``/``step``.
@@ -597,28 +597,21 @@ class Encoder(ABC):
597
597
  ``"YYYY-MM"`` that plays the same role as ``date`` does in the standard
598
598
  step-based tree walker.
599
599
 
600
- Because the tree ordering can place ``month`` before ``year`` (or vice
601
- versa), both axes are collected independently into ``fields["months"]``
602
- and ``fields["years"]``. The combined "YYYY-MM" keys are only built
603
- once both are available — either when the second axis is encountered, or
604
- at the leaf node.
600
+ Dates are accumulated in the order they are actually encountered during
601
+ tree traversal, so the ordering correctly reflects the tree structure
602
+ (e.g. month-major when the tree has month as the outer axis).
605
603
  """
604
+ if _ctx is None:
605
+ _ctx = {}
606
606
 
607
607
  def _year_month_key(year, month):
608
608
  return f"{int(year):04d}-{int(month):02d}"
609
609
 
610
- def _ensure_date_keys():
611
- """Populate ``fields["dates"]`` and ``coords`` once both year and
612
- month values are known."""
613
- if not fields.get("years") or not fields.get("months"):
614
- return
615
- for year in fields["years"]:
616
- for month in fields["months"]:
617
- key = _year_month_key(year, month)
618
- if key not in fields["dates"]:
619
- fields["dates"].append(key)
620
- if key not in coords:
621
- coords[key] = {"composite": [], "t": [key]}
610
+ def _register_date_key(key):
611
+ if key not in fields["dates"]:
612
+ fields["dates"].append(key)
613
+ if key not in coords:
614
+ coords[key] = {"composite": [], "t": [key]}
622
615
 
623
616
  def handle_non_leaf_node_month(child):
624
617
  non_leaf_axes = ["latitude", "longitude", "param", "year", "month"]
@@ -651,6 +644,10 @@ class Encoder(ABC):
651
644
  for child in tree.children:
652
645
  handle_non_leaf_node_month(child)
653
646
  result = handle_specific_axes_month(child)
647
+
648
+ # Build a child context that inherits the current year/month
649
+ child_ctx = dict(_ctx)
650
+
654
651
  if result is not None:
655
652
  if child.axis.name == "latitude":
656
653
  fields["lat"] = result
@@ -661,35 +658,72 @@ class Encoder(ABC):
661
658
  elif child.axis.name == "param":
662
659
  fields["param"] = result
663
660
  elif child.axis.name == "year":
664
- fields["years"] = result
665
- _ensure_date_keys()
661
+ fields["years"] = list(result) if fields.get("years") == [] else fields["years"]
662
+ child_ctx["years"] = result
663
+ child_ctx["_axis_order"] = _ctx.get("_axis_order", []) + ["year"]
664
+ # If month is already fixed in context, register dates now.
665
+ if "months" in _ctx:
666
+ for y in result:
667
+ for m in _ctx["months"]:
668
+ _register_date_key(_year_month_key(y, m))
666
669
  elif child.axis.name == "month":
667
- fields["months"] = result
668
- _ensure_date_keys()
670
+ fields["months"] = list(result) if fields.get("months") == [] else fields["months"]
671
+ child_ctx["months"] = result
672
+ # If year is already fixed in context, register dates now.
673
+ if "years" in _ctx:
674
+ for y in _ctx["years"]:
675
+ for m in result:
676
+ _register_date_key(_year_month_key(y, m))
677
+ # Track that month is the inner axis relative to year
678
+ child_ctx["_axis_order"] = _ctx.get("_axis_order", []) + ["month"]
669
679
  elif child.axis.name == "number":
670
680
  fields["number"] = result
671
681
 
672
- self.walk_tree_month(child, fields, coords, mars_metadata, range_dict)
682
+ self.walk_tree_month(child, fields, coords, mars_metadata, range_dict, _ctx=child_ctx)
673
683
  else:
674
- # Leaf node — make sure date keys are built before processing.
675
- _ensure_date_keys()
684
+ # Leaf node — ensure all (year, month) combinations from context are registered.
685
+ ctx_years = _ctx.get("years", fields.get("years", []))
686
+ ctx_months = _ctx.get("months", fields.get("months", []))
687
+ for y in ctx_years:
688
+ for m in ctx_months:
689
+ _register_date_key(_year_month_key(y, m))
690
+
691
+ # Determine the dates in scope for this specific leaf. The loop order
692
+ # must match the actual tree axis order (outermost axis first) so that
693
+ # the flat result array is sliced correctly.
694
+ # _axis_order records axes in the order they were encountered top-down;
695
+ # the first entry is the outer axis at this leaf.
696
+ if ctx_years and ctx_months:
697
+ axis_order = _ctx.get("_axis_order", [])
698
+ # Default: if year was seen before month in the tree, year is outer.
699
+ year_is_outer = (
700
+ axis_order.index("year") < axis_order.index("month")
701
+ if ("year" in axis_order and "month" in axis_order)
702
+ else True
703
+ )
704
+ if year_is_outer:
705
+ leaf_dates = [_year_month_key(y, m) for y in ctx_years for m in ctx_months]
706
+ else:
707
+ leaf_dates = [_year_month_key(y, m) for m in ctx_months for y in ctx_years]
708
+ else:
709
+ leaf_dates = fields["dates"]
676
710
 
677
711
  tree.values = [float(val) for val in tree.values]
678
712
  if all(val is None for val in tree.result):
679
- # Remove the last date entry that produced no data.
680
- if fields["dates"]:
681
- last_date = fields["dates"][-1]
682
- fields["dates"] = fields["dates"][:-1]
713
+ # Remove date entries for this leaf that produced no data.
714
+ for key in leaf_dates:
715
+ if key in fields["dates"]:
716
+ fields["dates"].remove(key)
683
717
  for level in fields["levels"]:
684
718
  for num in fields["number"]:
685
719
  for para in fields["param"]:
686
- key = (last_date, level, num, para)
687
- if key in range_dict:
688
- del range_dict[key]
720
+ rkey = (key, level, num, para)
721
+ if rkey in range_dict:
722
+ del range_dict[rkey]
689
723
  else:
690
724
  tree.result = [float(val) if val is not None else val for val in tree.result]
691
725
 
692
- n_dates = len(fields["dates"])
726
+ n_dates = len(leaf_dates)
693
727
  n_levels = len(fields["levels"])
694
728
  n_params = len(fields["param"])
695
729
 
@@ -698,11 +732,11 @@ class Encoder(ABC):
698
732
  para_len = level_len / n_params if n_params else level_len
699
733
 
700
734
  # Append this leaf's longitude values to composite coords for
701
- # every date key so spatial points are recorded.
702
- for date in fields["dates"]:
735
+ # every date key in scope.
736
+ for date in leaf_dates:
703
737
  append_composite_coords_month(date, tree.values, fields["lat"])
704
738
 
705
- for d, date in enumerate(fields["dates"]):
739
+ for d, date in enumerate(leaf_dates):
706
740
  for l, level in enumerate(fields["levels"]): # noqa: E741
707
741
  for i, num in enumerate(fields["number"]):
708
742
  for j, para in enumerate(fields["param"]):
@@ -0,0 +1 @@
1
+ __version__ = "0.2.20"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: covjsonkit
3
- Version: 0.2.19
3
+ Version: 0.2.20
4
4
  Home-page: https://github.com/ecmwf/covjsonkit
5
5
  Author: ECMWF
6
6
  Author-email: James.Hawkes@ecmwf.int, Adam.Warde@ecmwf.int, Mathilde.Leuridan@ecmwf.int
@@ -8,7 +8,7 @@ markers = ["data: uses test data (deselect with '-m \"not data\"')",]
8
8
 
9
9
  [project]
10
10
  name = "covjsonkit"
11
- version = "0.2.19"
11
+ version = "0.2.20"
12
12
  dependencies = [
13
13
  "pandas<3",
14
14
  "orjson",
@@ -1 +0,0 @@
1
- __version__ = "0.2.19"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes