anemoi-datasets 0.5.25__py3-none-any.whl → 0.5.27__py3-none-any.whl

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 (126) hide show
  1. anemoi/datasets/__init__.py +1 -2
  2. anemoi/datasets/_version.py +16 -3
  3. anemoi/datasets/commands/check.py +1 -1
  4. anemoi/datasets/commands/copy.py +1 -2
  5. anemoi/datasets/commands/create.py +1 -1
  6. anemoi/datasets/commands/grib-index.py +1 -1
  7. anemoi/datasets/commands/inspect.py +27 -35
  8. anemoi/datasets/commands/validate.py +59 -0
  9. anemoi/datasets/compute/recentre.py +3 -6
  10. anemoi/datasets/create/__init__.py +22 -25
  11. anemoi/datasets/create/check.py +10 -12
  12. anemoi/datasets/create/chunks.py +1 -2
  13. anemoi/datasets/create/config.py +3 -6
  14. anemoi/datasets/create/filter.py +21 -24
  15. anemoi/datasets/create/input/__init__.py +1 -2
  16. anemoi/datasets/create/input/action.py +3 -5
  17. anemoi/datasets/create/input/concat.py +5 -8
  18. anemoi/datasets/create/input/context.py +3 -6
  19. anemoi/datasets/create/input/data_sources.py +5 -8
  20. anemoi/datasets/create/input/empty.py +1 -2
  21. anemoi/datasets/create/input/filter.py +2 -3
  22. anemoi/datasets/create/input/function.py +1 -2
  23. anemoi/datasets/create/input/join.py +4 -5
  24. anemoi/datasets/create/input/misc.py +4 -6
  25. anemoi/datasets/create/input/repeated_dates.py +13 -18
  26. anemoi/datasets/create/input/result.py +29 -33
  27. anemoi/datasets/create/input/step.py +6 -24
  28. anemoi/datasets/create/input/template.py +3 -4
  29. anemoi/datasets/create/input/trace.py +1 -1
  30. anemoi/datasets/create/patch.py +1 -2
  31. anemoi/datasets/create/persistent.py +3 -5
  32. anemoi/datasets/create/size.py +1 -3
  33. anemoi/datasets/create/sources/accumulations.py +47 -52
  34. anemoi/datasets/create/sources/accumulations2.py +4 -8
  35. anemoi/datasets/create/sources/constants.py +1 -3
  36. anemoi/datasets/create/sources/empty.py +1 -2
  37. anemoi/datasets/create/sources/fdb.py +133 -0
  38. anemoi/datasets/create/sources/forcings.py +1 -2
  39. anemoi/datasets/create/sources/grib.py +6 -10
  40. anemoi/datasets/create/sources/grib_index.py +13 -15
  41. anemoi/datasets/create/sources/hindcasts.py +2 -5
  42. anemoi/datasets/create/sources/legacy.py +1 -1
  43. anemoi/datasets/create/sources/mars.py +17 -21
  44. anemoi/datasets/create/sources/netcdf.py +1 -2
  45. anemoi/datasets/create/sources/opendap.py +1 -3
  46. anemoi/datasets/create/sources/patterns.py +4 -6
  47. anemoi/datasets/create/sources/planetary_computer.py +44 -0
  48. anemoi/datasets/create/sources/recentre.py +8 -11
  49. anemoi/datasets/create/sources/source.py +3 -6
  50. anemoi/datasets/create/sources/tendencies.py +2 -5
  51. anemoi/datasets/create/sources/xarray.py +4 -6
  52. anemoi/datasets/create/sources/xarray_support/__init__.py +15 -32
  53. anemoi/datasets/create/sources/xarray_support/coordinates.py +16 -12
  54. anemoi/datasets/create/sources/xarray_support/field.py +17 -16
  55. anemoi/datasets/create/sources/xarray_support/fieldlist.py +11 -15
  56. anemoi/datasets/create/sources/xarray_support/flavour.py +83 -45
  57. anemoi/datasets/create/sources/xarray_support/grid.py +15 -9
  58. anemoi/datasets/create/sources/xarray_support/metadata.py +19 -128
  59. anemoi/datasets/create/sources/xarray_support/patch.py +47 -6
  60. anemoi/datasets/create/sources/xarray_support/time.py +10 -13
  61. anemoi/datasets/create/sources/xarray_support/variable.py +27 -23
  62. anemoi/datasets/create/sources/xarray_zarr.py +1 -2
  63. anemoi/datasets/create/sources/zenodo.py +3 -5
  64. anemoi/datasets/create/statistics/__init__.py +3 -6
  65. anemoi/datasets/create/testing.py +2 -74
  66. anemoi/datasets/create/typing.py +1 -2
  67. anemoi/datasets/create/utils.py +1 -2
  68. anemoi/datasets/create/zarr.py +7 -2
  69. anemoi/datasets/data/__init__.py +15 -6
  70. anemoi/datasets/data/complement.py +52 -23
  71. anemoi/datasets/data/concat.py +5 -8
  72. anemoi/datasets/data/dataset.py +42 -47
  73. anemoi/datasets/data/debug.py +7 -9
  74. anemoi/datasets/data/ensemble.py +4 -6
  75. anemoi/datasets/data/fill_missing.py +7 -10
  76. anemoi/datasets/data/forwards.py +30 -28
  77. anemoi/datasets/data/grids.py +12 -16
  78. anemoi/datasets/data/indexing.py +9 -12
  79. anemoi/datasets/data/interpolate.py +7 -15
  80. anemoi/datasets/data/join.py +8 -12
  81. anemoi/datasets/data/masked.py +6 -11
  82. anemoi/datasets/data/merge.py +5 -9
  83. anemoi/datasets/data/misc.py +41 -45
  84. anemoi/datasets/data/missing.py +11 -16
  85. anemoi/datasets/data/observations/__init__.py +8 -14
  86. anemoi/datasets/data/padded.py +3 -5
  87. anemoi/datasets/data/records/backends/__init__.py +2 -2
  88. anemoi/datasets/data/rescale.py +5 -12
  89. anemoi/datasets/data/select.py +13 -16
  90. anemoi/datasets/data/statistics.py +4 -7
  91. anemoi/datasets/data/stores.py +23 -77
  92. anemoi/datasets/data/subset.py +8 -11
  93. anemoi/datasets/data/unchecked.py +7 -11
  94. anemoi/datasets/data/xy.py +25 -21
  95. anemoi/datasets/dates/__init__.py +13 -18
  96. anemoi/datasets/dates/groups.py +7 -10
  97. anemoi/datasets/grids.py +11 -12
  98. anemoi/datasets/testing.py +93 -7
  99. anemoi/datasets/validate.py +598 -0
  100. {anemoi_datasets-0.5.25.dist-info → anemoi_datasets-0.5.27.dist-info}/METADATA +5 -4
  101. anemoi_datasets-0.5.27.dist-info/RECORD +134 -0
  102. anemoi/datasets/create/filters/__init__.py +0 -33
  103. anemoi/datasets/create/filters/empty.py +0 -37
  104. anemoi/datasets/create/filters/legacy.py +0 -93
  105. anemoi/datasets/create/filters/noop.py +0 -37
  106. anemoi/datasets/create/filters/orog_to_z.py +0 -58
  107. anemoi/datasets/create/filters/pressure_level_relative_humidity_to_specific_humidity.py +0 -83
  108. anemoi/datasets/create/filters/pressure_level_specific_humidity_to_relative_humidity.py +0 -84
  109. anemoi/datasets/create/filters/rename.py +0 -205
  110. anemoi/datasets/create/filters/rotate_winds.py +0 -105
  111. anemoi/datasets/create/filters/single_level_dewpoint_to_relative_humidity.py +0 -78
  112. anemoi/datasets/create/filters/single_level_relative_humidity_to_dewpoint.py +0 -84
  113. anemoi/datasets/create/filters/single_level_relative_humidity_to_specific_humidity.py +0 -163
  114. anemoi/datasets/create/filters/single_level_specific_humidity_to_relative_humidity.py +0 -451
  115. anemoi/datasets/create/filters/speeddir_to_uv.py +0 -95
  116. anemoi/datasets/create/filters/sum.py +0 -68
  117. anemoi/datasets/create/filters/transform.py +0 -51
  118. anemoi/datasets/create/filters/unrotate_winds.py +0 -105
  119. anemoi/datasets/create/filters/uv_to_speeddir.py +0 -94
  120. anemoi/datasets/create/filters/wz_to_w.py +0 -98
  121. anemoi/datasets/utils/__init__.py +0 -8
  122. anemoi_datasets-0.5.25.dist-info/RECORD +0 -150
  123. {anemoi_datasets-0.5.25.dist-info → anemoi_datasets-0.5.27.dist-info}/WHEEL +0 -0
  124. {anemoi_datasets-0.5.25.dist-info → anemoi_datasets-0.5.27.dist-info}/entry_points.txt +0 -0
  125. {anemoi_datasets-0.5.25.dist-info → anemoi_datasets-0.5.27.dist-info}/licenses/LICENSE +0 -0
  126. {anemoi_datasets-0.5.25.dist-info → anemoi_datasets-0.5.27.dist-info}/top_level.txt +0 -0
@@ -11,11 +11,8 @@
11
11
  import logging
12
12
  from abc import ABC
13
13
  from abc import abstractmethod
14
+ from collections.abc import Hashable
14
15
  from typing import Any
15
- from typing import Dict
16
- from typing import Hashable
17
- from typing import Optional
18
- from typing import Tuple
19
16
 
20
17
  import xarray as xr
21
18
  from anemoi.utils.config import DotDict
@@ -26,6 +23,7 @@ from .coordinates import EnsembleCoordinate
26
23
  from .coordinates import LatitudeCoordinate
27
24
  from .coordinates import LevelCoordinate
28
25
  from .coordinates import LongitudeCoordinate
26
+ from .coordinates import PointCoordinate
29
27
  from .coordinates import ScalarCoordinate
30
28
  from .coordinates import StepCoordinate
31
29
  from .coordinates import TimeCoordinate
@@ -60,11 +58,11 @@ class CoordinateGuesser(ABC):
60
58
  The dataset to guess coordinates from.
61
59
  """
62
60
  self.ds = ds
63
- self._coordinate_cache: Dict[Hashable, Coordinate] = {}
64
- self._grid_cache: Dict[Hashable, Grid] = {}
61
+ self._coordinate_cache: dict[Hashable, Coordinate] = {}
62
+ self._grid_cache: dict[Hashable, Grid] = {}
65
63
 
66
64
  @classmethod
67
- def from_flavour(cls, ds: xr.Dataset, flavour: Optional[Dict[str, Any]]) -> "CoordinateGuesser":
65
+ def from_flavour(cls, ds: xr.Dataset, flavour: dict[str, Any] | None) -> "CoordinateGuesser":
68
66
  """Creates a CoordinateGuesser from a flavour.
69
67
 
70
68
  Parameters
@@ -132,7 +130,11 @@ class CoordinateGuesser(ABC):
132
130
  units=units,
133
131
  )
134
132
 
135
- d: Optional[Coordinate] = None
133
+ d: Coordinate | None = None
134
+
135
+ d = self._is_point(coordinate, attributes)
136
+ if d is not None:
137
+ return d
136
138
 
137
139
  d = self._is_longitude(coordinate, attributes)
138
140
  if d is not None:
@@ -209,7 +211,7 @@ class CoordinateGuesser(ABC):
209
211
 
210
212
  raise NotImplementedError(f"Cannot establish grid {coordinates}")
211
213
 
212
- def _check_dims(self, variable: Any, x_or_lon: Any, y_or_lat: Any) -> Tuple[Any, bool]:
214
+ def _check_dims(self, variable: Any, x_or_lon: Any, y_or_lat: Any) -> tuple[Any, bool]:
213
215
  """Checks the dimensions of the variable against the coordinates.
214
216
 
215
217
  Parameters
@@ -308,9 +310,9 @@ class CoordinateGuesser(ABC):
308
310
  return self._grid_cache[(x.name, y.name, dim_vars)]
309
311
 
310
312
  grid_mapping = variable.attrs.get("grid_mapping", None)
311
- if grid_mapping is not None:
312
- print(f"grid_mapping: {grid_mapping}")
313
- print(self.ds[grid_mapping])
313
+ # if grid_mapping is not None:
314
+ # print(f"grid_mapping: {grid_mapping}")
315
+ # print(self.ds[grid_mapping])
314
316
 
315
317
  if grid_mapping is None:
316
318
  LOG.warning(f"No 'grid_mapping' attribute provided for '{variable.name}'")
@@ -357,7 +359,7 @@ class CoordinateGuesser(ABC):
357
359
  grid_mapping = self.ds.attrs["crs"]
358
360
  LOG.warning(f"Using CRS {grid_mapping} from global attributes")
359
361
 
360
- grid: Optional[Grid] = None
362
+ grid: Grid | None = None
361
363
  if grid_mapping is not None:
362
364
 
363
365
  grid_mapping = dict(self.ds[grid_mapping].attrs)
@@ -375,7 +377,7 @@ class CoordinateGuesser(ABC):
375
377
  raise NotImplementedError(f"Unstructured grid {x.name} {y.name}")
376
378
 
377
379
  @abstractmethod
378
- def _is_longitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[LongitudeCoordinate]:
380
+ def _is_longitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> LongitudeCoordinate | None:
379
381
  """Checks if the coordinate is a longitude.
380
382
 
381
383
  Parameters
@@ -393,7 +395,11 @@ class CoordinateGuesser(ABC):
393
395
  pass
394
396
 
395
397
  @abstractmethod
396
- def _is_latitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[LatitudeCoordinate]:
398
+ def _is_point(self, c: xr.DataArray, attributes: CoordinateAttributes) -> PointCoordinate | None:
399
+ pass
400
+
401
+ @abstractmethod
402
+ def _is_latitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> LatitudeCoordinate | None:
397
403
  """Checks if the coordinate is a latitude.
398
404
 
399
405
  Parameters
@@ -411,7 +417,7 @@ class CoordinateGuesser(ABC):
411
417
  pass
412
418
 
413
419
  @abstractmethod
414
- def _is_x(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[XCoordinate]:
420
+ def _is_x(self, c: xr.DataArray, attributes: CoordinateAttributes) -> XCoordinate | None:
415
421
  """Checks if the coordinate is an x coordinate.
416
422
 
417
423
  Parameters
@@ -429,7 +435,7 @@ class CoordinateGuesser(ABC):
429
435
  pass
430
436
 
431
437
  @abstractmethod
432
- def _is_y(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[YCoordinate]:
438
+ def _is_y(self, c: xr.DataArray, attributes: CoordinateAttributes) -> YCoordinate | None:
433
439
  """Checks if the coordinate is a y coordinate.
434
440
 
435
441
  Parameters
@@ -447,7 +453,7 @@ class CoordinateGuesser(ABC):
447
453
  pass
448
454
 
449
455
  @abstractmethod
450
- def _is_time(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[TimeCoordinate]:
456
+ def _is_time(self, c: xr.DataArray, attributes: CoordinateAttributes) -> TimeCoordinate | None:
451
457
  """Checks if the coordinate is a time coordinate.
452
458
 
453
459
  Parameters
@@ -465,7 +471,7 @@ class CoordinateGuesser(ABC):
465
471
  pass
466
472
 
467
473
  @abstractmethod
468
- def _is_date(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[DateCoordinate]:
474
+ def _is_date(self, c: xr.DataArray, attributes: CoordinateAttributes) -> DateCoordinate | None:
469
475
  """Checks if the coordinate is a date coordinate.
470
476
 
471
477
  Parameters
@@ -483,7 +489,7 @@ class CoordinateGuesser(ABC):
483
489
  pass
484
490
 
485
491
  @abstractmethod
486
- def _is_step(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[StepCoordinate]:
492
+ def _is_step(self, c: xr.DataArray, attributes: CoordinateAttributes) -> StepCoordinate | None:
487
493
  """Checks if the coordinate is a step coordinate.
488
494
 
489
495
  Parameters
@@ -501,7 +507,7 @@ class CoordinateGuesser(ABC):
501
507
  pass
502
508
 
503
509
  @abstractmethod
504
- def _is_level(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[LevelCoordinate]:
510
+ def _is_level(self, c: xr.DataArray, attributes: CoordinateAttributes) -> LevelCoordinate | None:
505
511
  """Checks if the coordinate is a level coordinate.
506
512
 
507
513
  Parameters
@@ -519,7 +525,7 @@ class CoordinateGuesser(ABC):
519
525
  pass
520
526
 
521
527
  @abstractmethod
522
- def _is_number(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[EnsembleCoordinate]:
528
+ def _is_number(self, c: xr.DataArray, attributes: CoordinateAttributes) -> EnsembleCoordinate | None:
523
529
  """Checks if the coordinate is an ensemble coordinate.
524
530
 
525
531
  Parameters
@@ -550,7 +556,16 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
550
556
  """
551
557
  super().__init__(ds)
552
558
 
553
- def _is_longitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[LongitudeCoordinate]:
559
+ def _is_point(self, c: xr.DataArray, attributes: CoordinateAttributes) -> PointCoordinate | None:
560
+ if attributes.standard_name in ["cell", "station", "poi", "point"]:
561
+ return PointCoordinate(c)
562
+
563
+ if attributes.name in ["cell", "station", "poi", "point"]: # WeatherBench
564
+ return PointCoordinate(c)
565
+
566
+ return None
567
+
568
+ def _is_longitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> LongitudeCoordinate | None:
554
569
  """Checks if the coordinate is a longitude.
555
570
 
556
571
  Parameters
@@ -579,7 +594,7 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
579
594
 
580
595
  return None
581
596
 
582
- def _is_latitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[LatitudeCoordinate]:
597
+ def _is_latitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> LatitudeCoordinate | None:
583
598
  """Checks if the coordinate is a latitude.
584
599
 
585
600
  Parameters
@@ -608,7 +623,7 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
608
623
 
609
624
  return None
610
625
 
611
- def _is_x(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[XCoordinate]:
626
+ def _is_x(self, c: xr.DataArray, attributes: CoordinateAttributes) -> XCoordinate | None:
612
627
  """Checks if the coordinate is an x coordinate.
613
628
 
614
629
  Parameters
@@ -631,7 +646,7 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
631
646
 
632
647
  return None
633
648
 
634
- def _is_y(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[YCoordinate]:
649
+ def _is_y(self, c: xr.DataArray, attributes: CoordinateAttributes) -> YCoordinate | None:
635
650
  """Checks if the coordinate is a y coordinate.
636
651
 
637
652
  Parameters
@@ -654,7 +669,7 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
654
669
 
655
670
  return None
656
671
 
657
- def _is_time(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[TimeCoordinate]:
672
+ def _is_time(self, c: xr.DataArray, attributes: CoordinateAttributes) -> TimeCoordinate | None:
658
673
  """Checks if the coordinate is a time coordinate.
659
674
 
660
675
  Parameters
@@ -677,7 +692,7 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
677
692
 
678
693
  return None
679
694
 
680
- def _is_date(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[DateCoordinate]:
695
+ def _is_date(self, c: xr.DataArray, attributes: CoordinateAttributes) -> DateCoordinate | None:
681
696
  """Checks if the coordinate is a date coordinate.
682
697
 
683
698
  Parameters
@@ -700,7 +715,7 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
700
715
 
701
716
  return None
702
717
 
703
- def _is_step(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[StepCoordinate]:
718
+ def _is_step(self, c: xr.DataArray, attributes: CoordinateAttributes) -> StepCoordinate | None:
704
719
  """Checks if the coordinate is a step coordinate.
705
720
 
706
721
  Parameters
@@ -726,7 +741,7 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
726
741
 
727
742
  return None
728
743
 
729
- def _is_level(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[LevelCoordinate]:
744
+ def _is_level(self, c: xr.DataArray, attributes: CoordinateAttributes) -> LevelCoordinate | None:
730
745
  """Checks if the coordinate is a level coordinate.
731
746
 
732
747
  Parameters
@@ -744,12 +759,18 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
744
759
  if attributes.standard_name == "atmosphere_hybrid_sigma_pressure_coordinate":
745
760
  return LevelCoordinate(c, "ml")
746
761
 
762
+ if attributes.standard_name == "model_level_number":
763
+ return LevelCoordinate(c, "ml")
764
+
747
765
  if attributes.long_name == "height" and attributes.units == "m":
748
766
  return LevelCoordinate(c, "height")
749
767
 
750
768
  if attributes.standard_name == "air_pressure" and attributes.units == "hPa":
751
769
  return LevelCoordinate(c, "pl")
752
770
 
771
+ if attributes.long_name == "pressure" and attributes.units in ["hPa", "Pa"]:
772
+ return LevelCoordinate(c, "pl")
773
+
753
774
  if attributes.name == "level":
754
775
  return LevelCoordinate(c, "pl")
755
776
 
@@ -759,12 +780,9 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
759
780
  if attributes.standard_name == "depth":
760
781
  return LevelCoordinate(c, "depth")
761
782
 
762
- if attributes.name == "vertical" and attributes.units == "hPa":
763
- return LevelCoordinate(c, "pl")
764
-
765
783
  return None
766
784
 
767
- def _is_number(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[EnsembleCoordinate]:
785
+ def _is_number(self, c: xr.DataArray, attributes: CoordinateAttributes) -> EnsembleCoordinate | None:
768
786
  """Checks if the coordinate is an ensemble coordinate.
769
787
 
770
788
  Parameters
@@ -788,7 +806,7 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
788
806
  class FlavourCoordinateGuesser(CoordinateGuesser):
789
807
  """Implementation of CoordinateGuesser that uses a flavour for guessing."""
790
808
 
791
- def __init__(self, ds: xr.Dataset, flavour: Dict[str, Any]) -> None:
809
+ def __init__(self, ds: xr.Dataset, flavour: dict[str, Any]) -> None:
792
810
  """Initializes the FlavourCoordinateGuesser.
793
811
 
794
812
  Parameters
@@ -801,7 +819,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
801
819
  super().__init__(ds)
802
820
  self.flavour = flavour
803
821
 
804
- def _match(self, c: xr.DataArray, key: str, attributes: CoordinateAttributes) -> Optional[Dict[str, Any]]:
822
+ def _match(self, c: xr.DataArray, key: str, attributes: CoordinateAttributes) -> dict[str, Any] | None:
805
823
  """Matches the coordinate against the flavour rules.
806
824
 
807
825
  Parameters
@@ -836,7 +854,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
836
854
 
837
855
  return None
838
856
 
839
- def _is_longitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[LongitudeCoordinate]:
857
+ def _is_longitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> LongitudeCoordinate | None:
840
858
  """Checks if the coordinate is a longitude using the flavour rules.
841
859
 
842
860
  Parameters
@@ -856,7 +874,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
856
874
 
857
875
  return None
858
876
 
859
- def _is_latitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[LatitudeCoordinate]:
877
+ def _is_latitude(self, c: xr.DataArray, attributes: CoordinateAttributes) -> LatitudeCoordinate | None:
860
878
  """Checks if the coordinate is a latitude using the flavour rules.
861
879
 
862
880
  Parameters
@@ -876,7 +894,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
876
894
 
877
895
  return None
878
896
 
879
- def _is_x(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[XCoordinate]:
897
+ def _is_x(self, c: xr.DataArray, attributes: CoordinateAttributes) -> XCoordinate | None:
880
898
  """Checks if the coordinate is an x coordinate using the flavour rules.
881
899
 
882
900
  Parameters
@@ -896,7 +914,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
896
914
 
897
915
  return None
898
916
 
899
- def _is_y(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[YCoordinate]:
917
+ def _is_y(self, c: xr.DataArray, attributes: CoordinateAttributes) -> YCoordinate | None:
900
918
  """Checks if the coordinate is a y coordinate using the flavour rules.
901
919
 
902
920
  Parameters
@@ -916,7 +934,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
916
934
 
917
935
  return None
918
936
 
919
- def _is_time(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[TimeCoordinate]:
937
+ def _is_time(self, c: xr.DataArray, attributes: CoordinateAttributes) -> TimeCoordinate | None:
920
938
  """Checks if the coordinate is a time coordinate using the flavour rules.
921
939
 
922
940
  Parameters
@@ -936,7 +954,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
936
954
 
937
955
  return None
938
956
 
939
- def _is_step(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[StepCoordinate]:
957
+ def _is_step(self, c: xr.DataArray, attributes: CoordinateAttributes) -> StepCoordinate | None:
940
958
  """Checks if the coordinate is a step coordinate using the flavour rules.
941
959
 
942
960
  Parameters
@@ -956,7 +974,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
956
974
 
957
975
  return None
958
976
 
959
- def _is_date(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[DateCoordinate]:
977
+ def _is_date(self, c: xr.DataArray, attributes: CoordinateAttributes) -> DateCoordinate | None:
960
978
  """Checks if the coordinate is a date coordinate using the flavour rules.
961
979
 
962
980
  Parameters
@@ -976,7 +994,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
976
994
 
977
995
  return None
978
996
 
979
- def _is_level(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[LevelCoordinate]:
997
+ def _is_level(self, c: xr.DataArray, attributes: CoordinateAttributes) -> LevelCoordinate | None:
980
998
  """Checks if the coordinate is a level coordinate using the flavour rules.
981
999
 
982
1000
  Parameters
@@ -1021,7 +1039,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
1021
1039
 
1022
1040
  raise NotImplementedError(f"levtype for {c=}")
1023
1041
 
1024
- def _is_number(self, c: xr.DataArray, attributes: CoordinateAttributes) -> Optional[EnsembleCoordinate]:
1042
+ def _is_number(self, c: xr.DataArray, attributes: CoordinateAttributes) -> EnsembleCoordinate | None:
1025
1043
  """Checks if the coordinate is an ensemble coordinate using the flavour rules.
1026
1044
 
1027
1045
  Parameters
@@ -1040,3 +1058,23 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
1040
1058
  return EnsembleCoordinate(c)
1041
1059
 
1042
1060
  return None
1061
+
1062
+ def _is_point(self, c: xr.DataArray, attributes: CoordinateAttributes) -> PointCoordinate | None:
1063
+ """Checks if the coordinate is a point coordinate using the flavour rules.
1064
+
1065
+ Parameters
1066
+ ----------
1067
+ c : xr.DataArray
1068
+ The coordinate to check.
1069
+ attributes : CoordinateAttributes
1070
+ The attributes of the coordinate.
1071
+
1072
+ Returns
1073
+ -------
1074
+ Optional[PointCoordinate]
1075
+ The StepCoorPointCoordinateinate if matched, else None.
1076
+ """
1077
+ if self._match(c, "point", attributes):
1078
+ return PointCoordinate(c)
1079
+
1080
+ return None
@@ -13,7 +13,6 @@ from abc import ABC
13
13
  from abc import abstractmethod
14
14
  from functools import cached_property
15
15
  from typing import Any
16
- from typing import Tuple
17
16
 
18
17
  import numpy as np
19
18
 
@@ -38,7 +37,7 @@ class Grid(ABC):
38
37
 
39
38
  @property
40
39
  @abstractmethod
41
- def grid_points(self) -> Tuple[Any, Any]:
40
+ def grid_points(self) -> tuple[Any, Any]:
42
41
  """Get the grid points."""
43
42
  pass
44
43
 
@@ -85,7 +84,7 @@ class MeshedGrid(LatLonGrid):
85
84
  """Grid class for meshed latitude and longitude coordinates."""
86
85
 
87
86
  @cached_property
88
- def grid_points(self) -> Tuple[Any, Any]:
87
+ def grid_points(self) -> tuple[Any, Any]:
89
88
  """Get the grid points for the meshed grid."""
90
89
 
91
90
  if self.variable_dims == (self.lon.variable.name, self.lat.variable.name):
@@ -128,7 +127,7 @@ class UnstructuredGrid(LatLonGrid):
128
127
  assert set(self.variable_dims) == set(self.grid_dims), (self.variable_dims, self.grid_dims)
129
128
 
130
129
  @cached_property
131
- def grid_points(self) -> Tuple[Any, Any]:
130
+ def grid_points(self) -> tuple[Any, Any]:
132
131
  """Get the grid points for the unstructured grid."""
133
132
  assert 1 <= len(self.variable_dims) <= 2
134
133
 
@@ -191,7 +190,7 @@ class MeshProjectionGrid(ProjectionGrid):
191
190
  """Grid class for meshed projected coordinates."""
192
191
 
193
192
  @cached_property
194
- def grid_points(self) -> Tuple[Any, Any]:
193
+ def grid_points(self) -> tuple[Any, Any]:
195
194
  """Get the grid points for the mesh projection grid."""
196
195
  transformer = self.transformer()
197
196
  xv, yv = np.meshgrid(self.x.variable.values, self.y.variable.values) # , indexing="ij")
@@ -199,10 +198,17 @@ class MeshProjectionGrid(ProjectionGrid):
199
198
  return lat.flatten(), lon.flatten()
200
199
 
201
200
 
202
- class UnstructuredProjectionGrid(XYGrid):
201
+ class UnstructuredProjectionGrid(ProjectionGrid):
203
202
  """Grid class for unstructured projected coordinates."""
204
203
 
205
204
  @cached_property
206
- def grid_points(self) -> Tuple[Any, Any]:
207
- """Get the grid points for the unstructured projection grid."""
208
- raise NotImplementedError("UnstructuredProjectionGrid")
205
+ def grid_points(self) -> tuple[Any, Any]:
206
+ """Get the grid points for the unstructured grid."""
207
+ if self.projection == "epsg:4326":
208
+ # WGS84, no transformation needed
209
+ return self.y.variable.values.flatten(), self.x.variable.values.flatten()
210
+ transformer = self.transformer()
211
+ xv, yv = np.meshgrid(self.x.variable.values, self.y.variable.values) # , indexing="ij")
212
+ lon, lat = transformer.transform(xv, yv)
213
+
214
+ return lat.flatten(), lon.flatten()