EOValidator 0.1__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 (95) hide show
  1. eovalidator-0.1/EOValidator.egg-info/PKG-INFO +3 -0
  2. eovalidator-0.1/EOValidator.egg-info/SOURCES.txt +93 -0
  3. eovalidator-0.1/EOValidator.egg-info/dependency_links.txt +1 -0
  4. eovalidator-0.1/EOValidator.egg-info/top_level.txt +1 -0
  5. eovalidator-0.1/PKG-INFO +3 -0
  6. eovalidator-0.1/eo_validator_main/__init__.py +0 -0
  7. eovalidator-0.1/eo_validator_main/eo_validator_old_package/__init__.py +4 -0
  8. eovalidator-0.1/eo_validator_main/eo_validator_old_package/__main__.py +5 -0
  9. eovalidator-0.1/eo_validator_main/eo_validator_old_package/main.py +25 -0
  10. eovalidator-0.1/eo_validator_main/eo_validator_old_package/validate_netcdf.py +345 -0
  11. eovalidator-0.1/eo_validator_main/eo_validator_old_package/validate_shapefile.py +399 -0
  12. eovalidator-0.1/eo_validator_main/eo_validator_old_package/validator.py +68 -0
  13. eovalidator-0.1/eo_validator_main/excluded/__init__.py +0 -0
  14. eovalidator-0.1/eo_validator_main/excluded/base_validator.py +106 -0
  15. eovalidator-0.1/eo_validator_main/excluded/csv_validator.py +1 -0
  16. eovalidator-0.1/eo_validator_main/excluded/errors/__init__.py +0 -0
  17. eovalidator-0.1/eo_validator_main/excluded/errors/file.py +2 -0
  18. eovalidator-0.1/eo_validator_main/excluded/errors/unsupported_file_type.py +3 -0
  19. eovalidator-0.1/eo_validator_main/excluded/errors/validation.py +2 -0
  20. eovalidator-0.1/eo_validator_main/excluded/failed_validator_wrapper.py +43 -0
  21. eovalidator-0.1/eo_validator_main/excluded/file.py +5 -0
  22. eovalidator-0.1/eo_validator_main/excluded/geodatabase_validator.py +1 -0
  23. eovalidator-0.1/eo_validator_main/excluded/move_2_loc_validator.py +3 -0
  24. eovalidator-0.1/eo_validator_main/excluded/validate_geojson.py +341 -0
  25. eovalidator-0.1/eo_validator_main/excluded/validate_geopackage.py +380 -0
  26. eovalidator-0.1/eo_validator_main/setup.py +0 -0
  27. eovalidator-0.1/eo_validator_main/src/__init__.py +0 -0
  28. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/__init__.py +0 -0
  29. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/api/__init__.py +0 -0
  30. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/api/file_format_validator.py +250 -0
  31. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/api/raster_file_validator.py +233 -0
  32. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/api/validation_mode.py +19 -0
  33. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/api/validation_request.py +21 -0
  34. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/api/vector_file_validator.py +44 -0
  35. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/core/__init__.py +0 -0
  36. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/core/file_input.py +37 -0
  37. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/core/format.py +36 -0
  38. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/core/multi_file_input.py +38 -0
  39. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/core/shape_type.py +16 -0
  40. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/core/validator_registry.py +190 -0
  41. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/__init__.py +0 -0
  42. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/builder/__init__.py +0 -0
  43. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/builder/cli_request_builder.py +15 -0
  44. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/builder/cwl_utils_request_builder.py +14 -0
  45. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/builder/cwltool_request_builder.py +14 -0
  46. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/builder/http_request_builder.py +15 -0
  47. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/builder/validation_request_builder.py +47 -0
  48. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/cwltool_facade.py +0 -0
  49. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/handler/__init__.py +0 -0
  50. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/handler/cli_response_handler.py +6 -0
  51. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/handler/cwl_utils_response_handler.py +17 -0
  52. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/handler/cwltool_response_handler.py +18 -0
  53. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/handler/http_response_handler.py +6 -0
  54. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/handler/validation_response_handler.py +54 -0
  55. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/integration/validation_manager.py +81 -0
  56. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/main/__main__.py +5 -0
  57. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/main/eo_validator.py +38 -0
  58. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/main/main.py +52 -0
  59. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/result/__init__.py +0 -0
  60. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/result/issue_severity.py +7 -0
  61. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/result/validation_issue.py +27 -0
  62. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/result/validaton_result.py +132 -0
  63. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/utils/__init__.py +0 -0
  64. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/utils/crs_operations.py +209 -0
  65. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/utils/loader.py +76 -0
  66. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/utils/open_operations.py +107 -0
  67. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/utils/shape_operations.py +154 -0
  68. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/utils/structural_checks.py +29 -0
  69. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/utils/temp_path.py +98 -0
  70. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/TODO/__init__.py +0 -0
  71. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/TODO/geozarr_validator.py +13 -0
  72. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/TODO/gml_validator.py +11 -0
  73. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/TODO/gpx_validator.py +11 -0
  74. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/TODO/netcdf_validator.py +17 -0
  75. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/TODO/osm_json_validator.py +1 -0
  76. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/TODO/osm_xml_validator.py +1 -0
  77. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/TODO/pbf_validator.py +10 -0
  78. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/TODO/vrt_validator.py +11 -0
  79. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/TODO/wkb_validator.py +11 -0
  80. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/__init__.py +0 -0
  81. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/cog_validator.py +151 -0
  82. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/dem_validator.py +16 -0
  83. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/dsm_validator.py +16 -0
  84. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/geojson_validator.py +168 -0
  85. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/geopackage_validator.py +235 -0
  86. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/geoparquet_validator.py +167 -0
  87. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/geotiff_validator.py +124 -0
  88. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/grib_validator.py +130 -0
  89. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/kml_validator.py +213 -0
  90. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/shapefile_validator.py +135 -0
  91. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/wkt_validator.py +241 -0
  92. eovalidator-0.1/eo_validator_main/src/eo_validator_refactored/validators/xdmf_validator.py +267 -0
  93. eovalidator-0.1/eo_validator_main/tests/__init__.py +0 -0
  94. eovalidator-0.1/pyproject.toml +3 -0
  95. eovalidator-0.1/setup.cfg +4 -0
@@ -0,0 +1,3 @@
1
+ Metadata-Version: 2.4
2
+ Name: EOValidator
3
+ Version: 0.1
@@ -0,0 +1,93 @@
1
+ pyproject.toml
2
+ EOValidator.egg-info/PKG-INFO
3
+ EOValidator.egg-info/SOURCES.txt
4
+ EOValidator.egg-info/dependency_links.txt
5
+ EOValidator.egg-info/top_level.txt
6
+ eo_validator_main/__init__.py
7
+ eo_validator_main/setup.py
8
+ eo_validator_main/eo_validator_old_package/__init__.py
9
+ eo_validator_main/eo_validator_old_package/__main__.py
10
+ eo_validator_main/eo_validator_old_package/main.py
11
+ eo_validator_main/eo_validator_old_package/validate_netcdf.py
12
+ eo_validator_main/eo_validator_old_package/validate_shapefile.py
13
+ eo_validator_main/eo_validator_old_package/validator.py
14
+ eo_validator_main/excluded/__init__.py
15
+ eo_validator_main/excluded/base_validator.py
16
+ eo_validator_main/excluded/csv_validator.py
17
+ eo_validator_main/excluded/failed_validator_wrapper.py
18
+ eo_validator_main/excluded/file.py
19
+ eo_validator_main/excluded/geodatabase_validator.py
20
+ eo_validator_main/excluded/move_2_loc_validator.py
21
+ eo_validator_main/excluded/validate_geojson.py
22
+ eo_validator_main/excluded/validate_geopackage.py
23
+ eo_validator_main/excluded/errors/__init__.py
24
+ eo_validator_main/excluded/errors/file.py
25
+ eo_validator_main/excluded/errors/unsupported_file_type.py
26
+ eo_validator_main/excluded/errors/validation.py
27
+ eo_validator_main/src/__init__.py
28
+ eo_validator_main/src/eo_validator_refactored/__init__.py
29
+ eo_validator_main/src/eo_validator_refactored/api/__init__.py
30
+ eo_validator_main/src/eo_validator_refactored/api/file_format_validator.py
31
+ eo_validator_main/src/eo_validator_refactored/api/raster_file_validator.py
32
+ eo_validator_main/src/eo_validator_refactored/api/validation_mode.py
33
+ eo_validator_main/src/eo_validator_refactored/api/validation_request.py
34
+ eo_validator_main/src/eo_validator_refactored/api/vector_file_validator.py
35
+ eo_validator_main/src/eo_validator_refactored/core/__init__.py
36
+ eo_validator_main/src/eo_validator_refactored/core/file_input.py
37
+ eo_validator_main/src/eo_validator_refactored/core/format.py
38
+ eo_validator_main/src/eo_validator_refactored/core/multi_file_input.py
39
+ eo_validator_main/src/eo_validator_refactored/core/shape_type.py
40
+ eo_validator_main/src/eo_validator_refactored/core/validator_registry.py
41
+ eo_validator_main/src/eo_validator_refactored/integration/__init__.py
42
+ eo_validator_main/src/eo_validator_refactored/integration/cwltool_facade.py
43
+ eo_validator_main/src/eo_validator_refactored/integration/validation_manager.py
44
+ eo_validator_main/src/eo_validator_refactored/integration/builder/__init__.py
45
+ eo_validator_main/src/eo_validator_refactored/integration/builder/cli_request_builder.py
46
+ eo_validator_main/src/eo_validator_refactored/integration/builder/cwl_utils_request_builder.py
47
+ eo_validator_main/src/eo_validator_refactored/integration/builder/cwltool_request_builder.py
48
+ eo_validator_main/src/eo_validator_refactored/integration/builder/http_request_builder.py
49
+ eo_validator_main/src/eo_validator_refactored/integration/builder/validation_request_builder.py
50
+ eo_validator_main/src/eo_validator_refactored/integration/handler/__init__.py
51
+ eo_validator_main/src/eo_validator_refactored/integration/handler/cli_response_handler.py
52
+ eo_validator_main/src/eo_validator_refactored/integration/handler/cwl_utils_response_handler.py
53
+ eo_validator_main/src/eo_validator_refactored/integration/handler/cwltool_response_handler.py
54
+ eo_validator_main/src/eo_validator_refactored/integration/handler/http_response_handler.py
55
+ eo_validator_main/src/eo_validator_refactored/integration/handler/validation_response_handler.py
56
+ eo_validator_main/src/eo_validator_refactored/main/__main__.py
57
+ eo_validator_main/src/eo_validator_refactored/main/eo_validator.py
58
+ eo_validator_main/src/eo_validator_refactored/main/main.py
59
+ eo_validator_main/src/eo_validator_refactored/result/__init__.py
60
+ eo_validator_main/src/eo_validator_refactored/result/issue_severity.py
61
+ eo_validator_main/src/eo_validator_refactored/result/validation_issue.py
62
+ eo_validator_main/src/eo_validator_refactored/result/validaton_result.py
63
+ eo_validator_main/src/eo_validator_refactored/utils/__init__.py
64
+ eo_validator_main/src/eo_validator_refactored/utils/crs_operations.py
65
+ eo_validator_main/src/eo_validator_refactored/utils/loader.py
66
+ eo_validator_main/src/eo_validator_refactored/utils/open_operations.py
67
+ eo_validator_main/src/eo_validator_refactored/utils/shape_operations.py
68
+ eo_validator_main/src/eo_validator_refactored/utils/structural_checks.py
69
+ eo_validator_main/src/eo_validator_refactored/utils/temp_path.py
70
+ eo_validator_main/src/eo_validator_refactored/validators/__init__.py
71
+ eo_validator_main/src/eo_validator_refactored/validators/cog_validator.py
72
+ eo_validator_main/src/eo_validator_refactored/validators/dem_validator.py
73
+ eo_validator_main/src/eo_validator_refactored/validators/dsm_validator.py
74
+ eo_validator_main/src/eo_validator_refactored/validators/geojson_validator.py
75
+ eo_validator_main/src/eo_validator_refactored/validators/geopackage_validator.py
76
+ eo_validator_main/src/eo_validator_refactored/validators/geoparquet_validator.py
77
+ eo_validator_main/src/eo_validator_refactored/validators/geotiff_validator.py
78
+ eo_validator_main/src/eo_validator_refactored/validators/grib_validator.py
79
+ eo_validator_main/src/eo_validator_refactored/validators/kml_validator.py
80
+ eo_validator_main/src/eo_validator_refactored/validators/shapefile_validator.py
81
+ eo_validator_main/src/eo_validator_refactored/validators/wkt_validator.py
82
+ eo_validator_main/src/eo_validator_refactored/validators/xdmf_validator.py
83
+ eo_validator_main/src/eo_validator_refactored/validators/TODO/__init__.py
84
+ eo_validator_main/src/eo_validator_refactored/validators/TODO/geozarr_validator.py
85
+ eo_validator_main/src/eo_validator_refactored/validators/TODO/gml_validator.py
86
+ eo_validator_main/src/eo_validator_refactored/validators/TODO/gpx_validator.py
87
+ eo_validator_main/src/eo_validator_refactored/validators/TODO/netcdf_validator.py
88
+ eo_validator_main/src/eo_validator_refactored/validators/TODO/osm_json_validator.py
89
+ eo_validator_main/src/eo_validator_refactored/validators/TODO/osm_xml_validator.py
90
+ eo_validator_main/src/eo_validator_refactored/validators/TODO/pbf_validator.py
91
+ eo_validator_main/src/eo_validator_refactored/validators/TODO/vrt_validator.py
92
+ eo_validator_main/src/eo_validator_refactored/validators/TODO/wkb_validator.py
93
+ eo_validator_main/tests/__init__.py
@@ -0,0 +1 @@
1
+ eo_validator_main
@@ -0,0 +1,3 @@
1
+ Metadata-Version: 2.4
2
+ Name: EOValidator
3
+ Version: 0.1
File without changes
@@ -0,0 +1,4 @@
1
+ """EO file formats validator."""
2
+
3
+ __author__ = "moritz.pataky@gmx.de"
4
+ __version__ = "0.1.0"
@@ -0,0 +1,5 @@
1
+ """Default entrypoint for the cwltool module."""
2
+
3
+ from . import main
4
+
5
+ main.run()
@@ -0,0 +1,25 @@
1
+ from eo_validator_main.eo_validator.validate_geotiff import validate_geotiff_strong
2
+ from eo_validator_main.eo_validator.validate_geotiff import validate_geotiff_simple
3
+ from pathlib import Path
4
+ import json, sys
5
+ #@build_sniff_from_prefix
6
+ #class Tiff(Text):
7
+
8
+ #@build_sniff_from_prefix
9
+ #class GeoTiff(Tiff):
10
+ # file_ext = "tif"
11
+
12
+
13
+ def run():
14
+ return None
15
+
16
+ def test():
17
+ return None
18
+ if __name__ == '__main__':
19
+ p = "C:/Tests/WSF2019_v1_8_48.tif"
20
+ r = validate_geotiff_strong(p, require_tags=False, require_crs=True)
21
+ print(json.dumps(r, indent=2))
22
+
23
+ #p2 = "C:/Tests/OntologyEx1.png"
24
+ #r = validate_geotiff_strong(p2, require_tags=False, require_crs=True)
25
+ #print(json.dumps(r, indent=2))
@@ -0,0 +1,345 @@
1
+ from pathlib import Path
2
+ from typing import Dict, Any
3
+
4
+ try:
5
+ import netCDF4
6
+ NETCDF4_INSTALLED = True
7
+ except Exception:
8
+ NETCDF4_INSTALLED = False
9
+
10
+ try:
11
+ import xarray as xr
12
+ XARRAY_INSTALLED = True
13
+ except Exception:
14
+ XARRAY_INSTALLED = False
15
+
16
+ def validate_netcdf_simple(path, require_tags, require_crs):
17
+ path = Path(path)
18
+ #report = {"path": str(path), "exists": path.exists(), "warnings": [], "errors": []}
19
+ report: Dict[str, Any] = {
20
+ "path": path,
21
+ "exists": False,
22
+ "extension_ok": False,
23
+ "header": None,
24
+ "open_info": None,
25
+ "cf_checks": None,
26
+ "is_valid": False,
27
+ "errors": [],
28
+ "warnings": []
29
+ }
30
+ # simple validation using path and file extension
31
+ # check path
32
+ if not path.exists():
33
+ report["errors"].append("File not found")
34
+ return report
35
+ else:
36
+ report["exists"] = True
37
+ # check file extension
38
+ if path.suffix.lower() not in ('.cdf', ".nc4", '.nc'):
39
+ report["warnings"] = ["extension not .cdf/.nc4/.nc"]
40
+ else:
41
+ report["extension_ok"] = True
42
+
43
+ return report
44
+
45
+ def sniff_header(path) -> Dict[str, Any]:
46
+ report = {"magic": "None", "is_netcdf3_magic": False, "is_hdf5_magic": False}
47
+ try:
48
+ with open(path, "rb") as fh:
49
+ header = fh.read(16)
50
+ report["magic"] = header[:16].hex()
51
+ if header[:3] == b"CDF":
52
+ report["is_netcdf3_magic"] = True
53
+ if header.startswith(b"\x89HDF\r\n\x1a\n"):
54
+ report["is_hdf5_magic"] = True
55
+ except Exception as e:
56
+ report["error"] = f"Header read error: {e}"
57
+ return report
58
+
59
+ def open_with_netcdf4(path: str) -> Dict[str, Any]:
60
+ report: Dict[str, Any] = {
61
+ "opened": False,
62
+ "error": None,
63
+ "file_format": None,
64
+ "global_attributes": {},
65
+ "dimensions": {},
66
+ "variables": {}
67
+
68
+ }
69
+ try:
70
+ dataset = netCDF4.Dataset(path, mode="r")
71
+ except Exception as e:
72
+ report["error"] = f"netCDF4 open failed: {e}"
73
+ return report
74
+ report["opened"] = True
75
+ # global attributes
76
+ try:
77
+ for k in dataset.ncattrs():
78
+ try:
79
+ report["global_attributes"][k] = getattr(dataset, k)
80
+ except Exception:
81
+ report["global_attributes"][k] = None
82
+ except Exception:
83
+ pass
84
+ # dimensions
85
+ try:
86
+ for d_name, dim in dataset.dimensions.items():
87
+ report["dimensions"][d_name] = {"size": len(dim), "isunlimited": bool(dim.isunlimited())}
88
+ except Exception:
89
+ pass
90
+ # variables
91
+ try:
92
+ for v_name, var in dataset.variables.items():
93
+ try:
94
+ attributes = {k: getattr(var, k) for k in var.ncattrs()}
95
+ except Exception:
96
+ attributes = {}
97
+ report["variables"][v_name] = {
98
+ "dtype": str(var.dtype),
99
+ "dimensions": tuple(var.dimensions),
100
+ "shape": tuple(var.shape),
101
+ "attributes": attributes
102
+ }
103
+ except Exception:
104
+ pass
105
+ # file format hint
106
+ try:
107
+ report["file_format"] = getattr(dataset, "file_format", None)
108
+ except Exception:
109
+ report["file_format"] = None
110
+ try:
111
+ dataset.close()
112
+ except Exception:
113
+ pass
114
+ return report
115
+
116
+ def open_with_xarray(path: str) -> Dict[str, Any]:
117
+ report: Dict[str, Any] = {
118
+ "opened": False,
119
+ "error": None,
120
+ "file_format": None,
121
+ "global_attributes": {},
122
+ "dimensions": {},
123
+ "variables": {}
124
+ }
125
+
126
+ try:
127
+ dataset = xr.open_dataset(path, decode_times=False, mask_and_scale=False, autoclose=True)
128
+ except Exception as e:
129
+ report["error"] = f"xarray open failed: {e}"
130
+ return report
131
+ report["opened"] = True
132
+ try:
133
+ for k, v in dataset.attrs.items():
134
+ report["global_attributes"][k] = v
135
+ except Exception:
136
+ pass
137
+ try:
138
+ for d_name, length in dataset.sizes.items():
139
+ report["dimensions"][d_name] = {"size": int(length), "isunlimited": False}
140
+ except Exception:
141
+ pass
142
+ try:
143
+ for v_name, var in dataset.data_vars.items():
144
+ report["variables"][v_name] = {
145
+ "dtype": str(var.dtype),
146
+ "dimensions": tuple(var.dims),
147
+ "shape": tuple(var.shape),
148
+ "attributes": dict(var.attrs)
149
+ }
150
+ # coordinate variables
151
+ for c_name, coord in dataset.coords.items():
152
+ if c_name not in report["variables"]:
153
+ report["variables"][c_name] = {
154
+ "dtype": str(coord.dtype),
155
+ "dimensions": tuple(coord.dims),
156
+ "shape": tuple(coord.shape),
157
+ "attributes": dict(coord.attrs)
158
+ }
159
+ except Exception:
160
+ pass
161
+ try:
162
+ dataset.close()
163
+ except Exception:
164
+ pass
165
+ return report
166
+
167
+ def collect_netcdf_info(path) -> Dict[str, Any]:
168
+ info = {
169
+ "path": path,
170
+ "header": sniff_header(path),
171
+ "reader": None,
172
+ "file_format": None,
173
+ "global_attributes": {},
174
+ "dimensions": {},
175
+ "variables": {},
176
+ "errors": [],
177
+ "warnings": []
178
+ }
179
+
180
+ # try netCDF4
181
+ if NETCDF4_INSTALLED:
182
+ report = open_with_netcdf4(path)
183
+ if report.get("opened"):
184
+ info["reader"] = "netCDF4"
185
+ info["file_format"] = report.get("file_format")
186
+ info["global_attributes"] = report.get("global_attributes", {})
187
+ info["dimensions"] = report.get("dimensions", {})
188
+ info["variables"] = report.get("variables", {})
189
+ return info
190
+ else:
191
+ info["errors"].append(report.get("error") or "netCDF4 open failed")
192
+
193
+ # fallback xarray
194
+ if XARRAY_INSTALLED:
195
+ report = open_with_xarray(path)
196
+ if report.get("opened"):
197
+ info["reader"] = "xarray"
198
+ info["file_format"] = report.get("file_format")
199
+ info["global_attributes"] = report.get("global_attributes", {})
200
+ info["dimensions"] = report.get("dimensions", {})
201
+ info["variables"] = report.get("variables", {})
202
+ return info
203
+ else:
204
+ info["errors"].append(report.get("error") or "xarray open failed")
205
+ else:
206
+ info["errors"].append("No netCDF4 or xarray available to open file")
207
+ return info
208
+
209
+ def cf_heuristic_checks(info: Dict[str, Any]) -> Dict[str, Any]:
210
+ vars = info.get("variables", {})
211
+ dims = info.get("dimensions", {})
212
+ global_attributes = info.get("global_attributes", {})
213
+ out = {"checks": {}, "warnings": [], "errors": []}
214
+
215
+ # Conventions attribute
216
+ conv = None
217
+ for k in ("Conventions", "convention", "conventions"):
218
+ if k in global_attributes:
219
+ conv = global_attributes[k]
220
+ break
221
+ if conv:
222
+ out["checks"]["Conventions_present"] = True
223
+ out["checks"]["Conventions_value"] = conv
224
+ else:
225
+ out["checks"]["Conventions_present"] = False
226
+ out["warnings"].append("Global attribute 'Conventions' missing (can't detect CF version).")
227
+
228
+ # coordinate variables: dims that lack a variable with the same name
229
+ missing_coord_vars = [d for d in dims.keys() if d not in vars]
230
+ if missing_coord_vars:
231
+ out["checks"]["missing_coord_vars"] = missing_coord_vars
232
+ out["warnings"].append(f"No coordinate variable(s) for dimension(s): {missing_coord_vars}")
233
+
234
+ # lat/lon/time candidates
235
+ lat_candidates = [n for n in ("lat", "latitude", "y") if n in vars]
236
+ lon_candidates = [n for n in ("lon", "longitude", "x") if n in vars]
237
+ time_candidates = [n for n in ("time", "times") if n in vars]
238
+
239
+ out["checks"]["lat_candidates"] = lat_candidates
240
+ out["checks"]["lon_candidates"] = lon_candidates
241
+ out["checks"]["time_candidates"] = time_candidates
242
+
243
+ if not lat_candidates or not lon_candidates:
244
+ out["warnings"].append("No obvious latitude/longitude coordinate variables found (lat/lon).")
245
+ else:
246
+ for name in lat_candidates + lon_candidates:
247
+ v = vars.get(name, {})
248
+ if len(v.get("dimensions", ())) != 1:
249
+ out["warnings"].append(f"Coordinate candidate '{name}' is not 1D (dims={v.get('dimensions')}).")
250
+ attributes = v.get("attributes", {})
251
+ if "units" not in attributes:
252
+ out["warnings"].append(f"Coordinate variable '{name}' missing 'units' attribute.")
253
+
254
+ # time checks
255
+ if not time_candidates:
256
+ out["warnings"].append("No obvious time variable found.")
257
+ else:
258
+ for t in time_candidates:
259
+ attributes = vars[t].get("attributes", {})
260
+ if "units" not in attributes:
261
+ out["warnings"].append(f"Time variable '{t}' missing 'units' attribute (CF expects units like 'days since ...').")
262
+
263
+ # variable dimension consistency
264
+ var_shape_issues = []
265
+ for v_name, vinfo in vars.items():
266
+ for dim in vinfo.get("dimensions", ()):
267
+ if dim not in dims:
268
+ var_shape_issues.append((v_name, dim))
269
+ if var_shape_issues:
270
+ out["errors"].append(f"Variables reference undefined dimensions: {var_shape_issues}")
271
+
272
+ # missing _FillValue / missing_value for data vars (warn)
273
+ suspect = []
274
+ for v_name, v_info in vars.items():
275
+ attrs = v_info.get("attributes", {})
276
+ # treat coordinate vars as less important (if var name == dim and 1D)
277
+ if v_name in dims and len(v_info.get("dimensions", ())) == 1:
278
+ continue
279
+ if not any(k in attrs for k in ("_FillValue", "missing_value")):
280
+ suspect.append(v_name)
281
+ if suspect:
282
+ out["warnings"].append(f"Data variables without _FillValue/missing_value: {suspect[:10]} (up to 10 shown)")
283
+
284
+ return out
285
+
286
+ def validate_netcdf_strong(path, require_tags, require_crs):
287
+ #resultGeoPackage = {"path": str(path), "exists": path.exists(),
288
+ # "extension_ok": False, "sqlite_header_ok": False, "sqlite_open_ok": False,
289
+ # "integrity_ok": None, "required_tables_present": False, "missing_required_tables": [],
290
+ # "recommended_tables_present": [], "missing_recommended_tables": [], "gpkg_contents": [],
291
+ # "gpkg_spatial_ref_sys": [], "srs_reference_ok": None, "geometry_columns_ok": None,
292
+ # "table_sample_checks": {}, "warnings": [], "errors": []}
293
+
294
+ #resultGeoTIFF = {"path": str(path), "exists": path.exists(), "is_tiff": False,
295
+ # "driver": None, "crs": None, "transform": None,
296
+ # "bands": None, "dtypes": None, "nodatavals": None,
297
+ # "geo_tags": {}, "warnings": [], "errors": []}
298
+
299
+ path = Path(path)
300
+ #report = {"path": str(path), "exists": path.exists(), "extension_ok": False, "warnings": [], "errors": []}
301
+
302
+ # simple validation using path and file extension
303
+ # check path
304
+ #if not path.exists():
305
+ # report["errors"].append("File not found")
306
+ # return report
307
+
308
+ # check file extension
309
+ #if path.suffix.lower() not in ('.cdf', '.nc'):
310
+ # report["warnings"] = ["extension not .cdf/.nc"]
311
+ # return report
312
+ report = validate_netcdf_simple(path, require_tags, require_crs)
313
+
314
+ # strong checks
315
+ report["header"] = sniff_header(path)
316
+
317
+ info = collect_netcdf_info(path)
318
+ report["open_info"] = info
319
+ if info.get("errors"):
320
+ report["errors"].extend(info.get("errors", []))
321
+
322
+ cf = cf_heuristic_checks(info)
323
+ report["cf_checks"] = cf
324
+ if cf.get("warnings"):
325
+ report["warnings"].extend(cf.get("warnings", []))
326
+ if cf.get("errors"):
327
+ report["errors"].extend(cf.get("errors", []))
328
+
329
+ # decide validity
330
+ # For strong validation Conventions present and at least lat/lon/time candidates required
331
+
332
+ conv_ok = cf["checks"].get("Conventions_present", False)
333
+ coords_ok = bool(cf["checks"].get("lat_candidates")) and bool(cf["checks"].get("lon_candidates"))
334
+ time_ok = bool(cf["checks"].get("time_candidates"))
335
+ report["is_valid"] = (info.get("reader") is not None) and conv_ok and coords_ok and time_ok and not bool(
336
+ cf.get("errors", []))
337
+ if not conv_ok:
338
+ report["errors"].append("Strong validation mode: missing 'Conventions' global attribute.")
339
+ if not coords_ok:
340
+ report["errors"].append("Strong validation mode: missing lat/lon coordinate variables.")
341
+ if not time_ok:
342
+ report["errors"].append("Strong validation mode: missing time variable.")
343
+
344
+
345
+ return report