diff --git a/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/X/c/0 b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/X/c/0 new file mode 100644 index 000000000000..2831ac6ef344 Binary files /dev/null and b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/X/c/0 differ diff --git a/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/X/zarr.json b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/X/zarr.json new file mode 100644 index 000000000000..879873f300b0 --- /dev/null +++ b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/X/zarr.json @@ -0,0 +1,36 @@ +{ + "zarr_format":3, + "node_type":"array", + "shape":[ + 20 + ], + "data_type":"float64", + "chunk_grid":{ + "name":"regular", + "configuration":{ + "chunk_shape":[ + 20 + ] + } + }, + "chunk_key_encoding":{ + "name":"default", + "configuration":{ + "separator":"\/" + } + }, + "fill_value":"NaN", + "codecs":[ + { + "name":"bytes", + "configuration":{ + "endian":"little" + } + } + ], + "attributes":{ + }, + "dimension_names":[ + "X" + ] +} \ No newline at end of file diff --git a/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/Y/c/0 b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/Y/c/0 new file mode 100644 index 000000000000..27576b86dd57 Binary files /dev/null and b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/Y/c/0 differ diff --git a/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/Y/zarr.json b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/Y/zarr.json new file mode 100644 index 000000000000..f82be3571f46 --- /dev/null +++ b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/Y/zarr.json @@ -0,0 +1,36 @@ +{ + "zarr_format":3, + "node_type":"array", + "shape":[ + 20 + ], + "data_type":"float64", + "chunk_grid":{ + "name":"regular", + "configuration":{ + "chunk_shape":[ + 20 + ] + } + }, + "chunk_key_encoding":{ + "name":"default", + "configuration":{ + "separator":"\/" + } + }, + "fill_value":"NaN", + "codecs":[ + { + "name":"bytes", + "configuration":{ + "endian":"little" + } + } + ], + "attributes":{ + }, + "dimension_names":[ + "Y" + ] +} \ No newline at end of file diff --git a/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/byte_geo_proj/c/0/0 b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/byte_geo_proj/c/0/0 new file mode 100644 index 000000000000..1756836e731d --- /dev/null +++ b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/byte_geo_proj/c/0/0 @@ -0,0 +1 @@ +k{„s„„Œ„„„k„k„„k{sœ”s„k{”s¥sŒk{{c„{„„„cœs„Œ„{sŒkŒs„{k„„ssksk”„{{s„„{s{s{ks”ksŒs„„œ„Œ„„sss{”{¥{„kk„œ{½­­””s”{k„s„œc{s„„ÎkÅ­”ŒŒ„c„{sŒ„„c„{„­{s”{”s”{Œ{ks„sksc{cµck{s„s{„s„„{{„csc{„sskŒŒcŒcs{k„ksks{„{k{„„„„„{c„{k”cs{Œ­{k{{{k{{{kŒ{{ssZk­kkkkc„{s­”c{{k{ck½­ksskcŒk­Œ”„„k{ccsc„cŒs”{c„{”ŒŒkŒZkskZc{sss{{”s”c„¥”œ{kkksŒcsccks„sZ{s½­ŒŒ¥s„ZcsZcckc„ck„„œµŒ­{„cs{Jsc{Œœ„¥ŒŒc­÷ÿ΄kŒ{”„¥¥”Œ„{k{k{µµœ”œœœµ„”s„kkkkksck \ No newline at end of file diff --git a/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/byte_geo_proj/zarr.json b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/byte_geo_proj/zarr.json new file mode 100644 index 000000000000..5a14260ee308 --- /dev/null +++ b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/byte_geo_proj/zarr.json @@ -0,0 +1,54 @@ +{ + "zarr_format":3, + "node_type":"array", + "shape":[ + 20, + 20 + ], + "data_type":"uint8", + "chunk_grid":{ + "name":"regular", + "configuration":{ + "chunk_shape":[ + 20, + 20 + ] + } + }, + "chunk_key_encoding":{ + "name":"default", + "configuration":{ + "separator":"\/" + } + }, + "fill_value":0, + "codecs":[ + { + "name":"bytes", + "configuration":{ + "endian":"little" + } + } + ], + "attributes":{ + "zarr_conventions_version": "0.1.0", + "zarr_conventions": + { + "f17cb550-5864-4468-aeb7-f3180cfb622f": { + "version": "0.1", + "name": "geo/proj", + "description": "", + "schema": "http://example.com/geo.proj.schema.json", + "configuration": { + "code": "EPSG:26711", + "spatial_dimensions": ["Y", "X"], + "transform": [60, 0, 440720, 0, 60, 3750120] + } + } + } + }, + "dimension_names":[ + "Y", + "X" + ] +} diff --git a/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/zarr.json b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/zarr.json new file mode 100644 index 000000000000..849253ba9b49 --- /dev/null +++ b/autotest/gdrivers/data/zarr/byte_geo_proj_prototype.zarr/zarr.json @@ -0,0 +1,5 @@ +{ + "zarr_format": 3, + "node_type": "group", + "attributes": {} +} diff --git a/autotest/gdrivers/zarr_driver.py b/autotest/gdrivers/zarr_driver.py index 0ffce7250ad9..be6b50e11b71 100644 --- a/autotest/gdrivers/zarr_driver.py +++ b/autotest/gdrivers/zarr_driver.py @@ -5869,3 +5869,16 @@ def test_zarr_read_zarr_with_stac_proj_wkt2(): def test_zarr_identify_file_extensions(file_path): ds = gdal.Open(file_path) ds.GetRasterBand(1).Checksum() + + +############################################################################### +# + + +@gdaltest.enable_exceptions() +def test_zarr_read_zarr_geo_proj_prototype(): + + ds = gdal.Open("data/zarr/byte_geo_proj_prototype.zarr") + assert ds.GetSpatialRef().GetAuthorityCode(None) == "26711" + assert ds.GetSpatialRef().GetDataAxisToSRSAxisMapping() == [1, 2] + assert ds.GetGeoTransform() == (440720.0, 60.0, 0.0, 3751320.0, 0.0, -60.0) diff --git a/frmts/zarr/zarr_array.cpp b/frmts/zarr/zarr_array.cpp index 48741d6dfd28..124af21c5c82 100644 --- a/frmts/zarr/zarr_array.cpp +++ b/frmts/zarr/zarr_array.cpp @@ -2816,7 +2816,163 @@ void ZarrArray::ParseSpecialAttributes( // 1-dimensional array with the values. } - if (poSRS) + bool bAxisAssigned = false; + const auto oZarrConventions = oAttributes["zarr_conventions"]; + if (oZarrConventions.GetType() == CPLJSONObject::Type::Object) + { + const auto oGeoProj = + oZarrConventions["f17cb550-5864-4468-aeb7-f3180cfb622f"]; + if (oGeoProj.GetType() == CPLJSONObject::Type::Object) + { + // Cf https://github.com/EOPF-Explorer/data-model/tree/main/attributes/geo/proj + + const auto osVersion = oGeoProj["version"]; + // TODO: implement SemVer + if (osVersion.ToString() != "0.1.0") + CPLDebug( + "ZARR", + "geo/proj version unhandled by this implementation: %s", + osVersion.ToString().c_str()); + + const auto oConfiguration = oGeoProj["configuration"]; + if (oConfiguration.GetType() == CPLJSONObject::Type::Object) + { + const auto oCode = oConfiguration["code"]; + if (oCode.GetType() == CPLJSONObject::Type::String) + { + poSRS = std::make_shared(); + poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); + if (poSRS->SetFromUserInput( + oCode.ToString().c_str(), + OGRSpatialReference:: + SET_FROM_USER_INPUT_LIMITATIONS_get()) != + OGRERR_NONE) + { + poSRS.reset(); + } + } + else + { + const auto oWKT2 = oConfiguration["wkt2"]; + if (oWKT2.GetType() == CPLJSONObject::Type::String) + { + poSRS = std::make_shared(); + poSRS->SetAxisMappingStrategy( + OAMS_TRADITIONAL_GIS_ORDER); + if (poSRS->importFromWkt(oWKT2.ToString().c_str()) != + OGRERR_NONE) + { + poSRS.reset(); + } + } + else + { + const auto oPROJJSON = oConfiguration["projjson"]; + if (oPROJJSON.GetType() == CPLJSONObject::Type::Object) + { + poSRS = std::make_shared(); + poSRS->SetAxisMappingStrategy( + OAMS_TRADITIONAL_GIS_ORDER); + if (poSRS->SetFromUserInput( + oPROJJSON.ToString().c_str(), + OGRSpatialReference:: + SET_FROM_USER_INPUT_LIMITATIONS_get()) != + OGRERR_NONE) + { + poSRS.reset(); + } + } + } + } + + if (poSRS) + { + const auto oSpatialDimensions = + oConfiguration["spatial_dimensions"]; + int iDimName1 = 0; + int iDimName2 = 0; + if (oSpatialDimensions.GetType() == + CPLJSONObject::Type::Array && + oSpatialDimensions.ToArray().Size() == 2) + { + const auto osName1 = + oSpatialDimensions.ToArray()[0].ToString(); + const auto osName2 = + oSpatialDimensions.ToArray()[1].ToString(); + int iDim = 1; + for (const auto &poDim : GetDimensions()) + { + if (poDim->GetName() == osName1) + iDimName1 = iDim; + else if (poDim->GetName() == osName2) + iDimName2 = iDim; + ++iDim; + } + if (iDimName1 == 0) + { + CPLError(CE_Warning, CPLE_AppDefined, + "spatial_dimensions[0] = %s is a unknown " + "Zarr dimension", + osName1.c_str()); + } + if (iDimName2 == 0) + { + CPLError(CE_Warning, CPLE_AppDefined, + "spatial_dimensions[1] = %s is a unknown " + "Zarr dimension", + osName2.c_str()); + } + } + else + { + int iDim = 1; + for (const auto &poDim : GetDimensions()) + { + if (poDim->GetName() == "y" || + poDim->GetName() == "Y" || + poDim->GetName() == "lat" || + poDim->GetName() == "latitude" || + poDim->GetName() == "northing" || + poDim->GetName() == "row") + { + iDimName1 = iDim; + } + else if (poDim->GetName() == "x" || + poDim->GetName() == "X" || + poDim->GetName() == "lon" || + poDim->GetName() == "longitude" || + poDim->GetName() == "easting" || + poDim->GetName() == "col") + { + iDimName2 = iDim; + } + ++iDim; + } + } + if (iDimName1 > 0 && iDimName2 > 0) + { + bAxisAssigned = true; + const int iDimY = std::min(iDimName1, iDimName2); + const int iDimX = std::max(iDimName1, iDimName2); + const auto &oMapping = + poSRS->GetDataAxisToSRSAxisMapping(); + if (oMapping == std::vector{2, 1} || + oMapping == std::vector{2, 1, 3}) + poSRS->SetDataAxisToSRSAxisMapping({iDimY, iDimX}); + else if (oMapping == std::vector{1, 2} || + oMapping == std::vector{1, 2, 3}) + poSRS->SetDataAxisToSRSAxisMapping({iDimX, iDimY}); + + SetSRS(poSRS); + } + } + + // TODO deal with "bbox"? and "transform" + } + } + } + + if (poSRS && !bAxisAssigned) { int iDimX = 0; int iDimY = 0;