From 78c7886935b017b70318e91a8f12bb6ea5727b3e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 20 May 2024 01:46:37 +0200 Subject: [PATCH 0001/1119] OpenFileGDB: add partial read-only support for tables with 64-bit ObjectIDs Tables with non-sparse pages in .gdbtablx are fully supported. A subset of tables with sparse pages are properly supported (cf https://github.com/rouault/dump_gdbtable/wiki/FGDB-Spec#trailing-section-16-bytes--variable-number-) When we detect that we can't properly decode ObjectID in the unsupported subset of sparse tables, we emit a warning, and turn off use of attribute/spatial indices. Read-only support for now. --- .../3features.gdb/a00000001.TablesByName.atx | Bin 0 -> 4118 bytes .../3features.gdb/a00000001.gdbindexes | Bin 0 -> 110 bytes .../3features.gdb/a00000001.gdbtable | Bin 0 -> 322 bytes .../3features.gdb/a00000001.gdbtablx | Bin 0 -> 5152 bytes .../3features.gdb/a00000002.gdbtable | Bin 0 -> 2055 bytes .../3features.gdb/a00000002.gdbtablx | Bin 0 -> 5152 bytes .../3features.gdb/a00000003.gdbindexes | Bin 0 -> 42 bytes .../3features.gdb/a00000003.gdbtable | Bin 0 -> 1016 bytes .../3features.gdb/a00000003.gdbtablx | Bin 0 -> 5152 bytes .../a00000004.CatItemsByPhysicalName.atx | Bin 0 -> 4118 bytes .../a00000004.CatItemsByType.atx | Bin 0 -> 4118 bytes .../3features.gdb/a00000004.FDO_UUID.atx | Bin 0 -> 4118 bytes .../3features.gdb/a00000004.gdbindexes | Bin 0 -> 310 bytes .../3features.gdb/a00000004.gdbtable | Bin 0 -> 10304 bytes .../3features.gdb/a00000004.gdbtablx | Bin 0 -> 5152 bytes .../3features.gdb/a00000004.horizon | Bin 0 -> 32 bytes .../objectid64/3features.gdb/a00000004.spx | Bin 0 -> 4118 bytes .../a00000005.CatItemTypesByName.atx | Bin 0 -> 12310 bytes .../a00000005.CatItemTypesByParentTypeID.atx | Bin 0 -> 4118 bytes .../a00000005.CatItemTypesByUUID.atx | Bin 0 -> 4118 bytes .../3features.gdb/a00000005.gdbindexes | Bin 0 -> 296 bytes .../3features.gdb/a00000005.gdbtable | Bin 0 -> 2071 bytes .../3features.gdb/a00000005.gdbtablx | Bin 0 -> 5152 bytes .../a00000006.CatRelsByDestinationID.atx | Bin 0 -> 4118 bytes .../a00000006.CatRelsByOriginID.atx | Bin 0 -> 4118 bytes .../3features.gdb/a00000006.CatRelsByType.atx | Bin 0 -> 4118 bytes .../3features.gdb/a00000006.FDO_UUID.atx | Bin 0 -> 4118 bytes .../3features.gdb/a00000006.gdbindexes | Bin 0 -> 318 bytes .../3features.gdb/a00000006.gdbtable | Bin 0 -> 263 bytes .../3features.gdb/a00000006.gdbtablx | Bin 0 -> 5152 bytes .../a00000007.CatRelTypesByBackwardLabel.atx | Bin 0 -> 12310 bytes .../a00000007.CatRelTypesByDestItemTypeID.atx | Bin 0 -> 4118 bytes .../a00000007.CatRelTypesByForwardLabel.atx | Bin 0 -> 12310 bytes .../a00000007.CatRelTypesByName.atx | Bin 0 -> 12310 bytes ...00000007.CatRelTypesByOriginItemTypeID.atx | Bin 0 -> 4118 bytes .../a00000007.CatRelTypesByUUID.atx | Bin 0 -> 4118 bytes .../3features.gdb/a00000007.gdbindexes | Bin 0 -> 602 bytes .../3features.gdb/a00000007.gdbtable | Bin 0 -> 3626 bytes .../3features.gdb/a00000007.gdbtablx | Bin 0 -> 5152 bytes .../3features.gdb/a00000009.gdbindexes | Bin 0 -> 116 bytes .../3features.gdb/a00000009.gdbtable | Bin 0 -> 1384 bytes .../3features.gdb/a00000009.gdbtablx | Bin 0 -> 5156 bytes .../3features.gdb/a00000009.horizon | 1 + .../objectid64/3features.gdb/a00000009.spx | Bin 0 -> 65566 bytes .../data/filegdb/objectid64/3features.gdb/gdb | Bin 0 -> 4 bytes .../objectid64/3features.gdb/timestamps | Bin 0 -> 400 bytes .../with_holes_8.gdb/a00000001.gdbtable | Bin 0 -> 505 bytes .../with_holes_8.gdb/a00000001.gdbtablx | Bin 0 -> 4128 bytes .../with_holes_8.gdb/a00000002.gdbtable | Bin 0 -> 2094 bytes .../with_holes_8.gdb/a00000002.gdbtablx | Bin 0 -> 4128 bytes .../with_holes_8.gdb/a00000003.gdbtable | Bin 0 -> 797 bytes .../with_holes_8.gdb/a00000003.gdbtablx | Bin 0 -> 4128 bytes .../with_holes_8.gdb/a00000004.freelist | Bin 0 -> 4440 bytes .../with_holes_8.gdb/a00000004.gdbtable | Bin 0 -> 47714 bytes .../with_holes_8.gdb/a00000004.gdbtablx | Bin 0 -> 4128 bytes .../with_holes_8.gdb/a00000005.gdbtable | Bin 0 -> 2060 bytes .../with_holes_8.gdb/a00000005.gdbtablx | Bin 0 -> 4128 bytes .../a00000006.FDO_OriginID.atx | Bin 0 -> 4118 bytes .../with_holes_8.gdb/a00000006.gdbindexes | Bin 0 -> 66 bytes .../with_holes_8.gdb/a00000006.gdbtable | Bin 0 -> 740 bytes .../with_holes_8.gdb/a00000006.gdbtablx | Bin 0 -> 4128 bytes .../with_holes_8.gdb/a00000007.gdbtable | Bin 0 -> 3665 bytes .../with_holes_8.gdb/a00000007.gdbtablx | Bin 0 -> 4128 bytes .../with_holes_8.gdb/a00000009.gdbindexes | Bin 0 -> 116 bytes .../with_holes_8.gdb/a00000009.gdbtable | Bin 0 -> 566 bytes .../with_holes_8.gdb/a00000009.gdbtablx | Bin 0 -> 37990 bytes .../with_holes_8.gdb/a00000009.horizon | Bin 0 -> 32 bytes .../objectid64/with_holes_8.gdb/a00000009.spx | Bin 0 -> 65566 bytes .../with_holes_8.gdb/a0000000a.gdbindexes | Bin 0 -> 116 bytes .../with_holes_8.gdb/a0000000a.gdbtable | Bin 0 -> 566 bytes .../with_holes_8.gdb/a0000000a.gdbtablx | Bin 0 -> 37990 bytes .../with_holes_8.gdb/a0000000a.horizon | Bin 0 -> 32 bytes .../objectid64/with_holes_8.gdb/a0000000a.spx | Bin 0 -> 65566 bytes .../with_holes_8.gdb/a0000000b.gdbindexes | Bin 0 -> 116 bytes .../with_holes_8.gdb/a0000000b.gdbtable | Bin 0 -> 566 bytes .../with_holes_8.gdb/a0000000b.gdbtablx | Bin 0 -> 37990 bytes .../with_holes_8.gdb/a0000000b.horizon | Bin 0 -> 32 bytes .../objectid64/with_holes_8.gdb/a0000000b.spx | Bin 0 -> 65566 bytes .../with_holes_8.gdb/a0000000c.gdbindexes | Bin 0 -> 116 bytes .../with_holes_8.gdb/a0000000c.gdbtable | Bin 0 -> 566 bytes .../with_holes_8.gdb/a0000000c.gdbtablx | Bin 0 -> 37990 bytes .../with_holes_8.gdb/a0000000c.horizon | Bin 0 -> 32 bytes .../objectid64/with_holes_8.gdb/a0000000c.spx | Bin 0 -> 65566 bytes .../with_holes_8.gdb/a0000000d.gdbindexes | Bin 0 -> 116 bytes .../with_holes_8.gdb/a0000000d.gdbtable | Bin 0 -> 566 bytes .../with_holes_8.gdb/a0000000d.gdbtablx | Bin 0 -> 37990 bytes .../with_holes_8.gdb/a0000000d.horizon | Bin 0 -> 32 bytes .../objectid64/with_holes_8.gdb/a0000000d.spx | Bin 0 -> 65566 bytes .../with_holes_8.gdb/a0000000e.gdbindexes | Bin 0 -> 116 bytes .../with_holes_8.gdb/a0000000e.gdbtable | Bin 0 -> 566 bytes .../with_holes_8.gdb/a0000000e.gdbtablx | Bin 0 -> 155936 bytes .../with_holes_8.gdb/a0000000e.horizon | Bin 0 -> 32 bytes .../objectid64/with_holes_8.gdb/a0000000e.spx | Bin 0 -> 65566 bytes .../with_holes_8.gdb/a0000000f.gdbindexes | Bin 0 -> 116 bytes .../with_holes_8.gdb/a0000000f.gdbtable | Bin 0 -> 642 bytes .../with_holes_8.gdb/a0000000f.gdbtablx | Bin 0 -> 176416 bytes .../with_holes_8.gdb/a0000000f.horizon | Bin 0 -> 32 bytes .../objectid64/with_holes_8.gdb/a0000000f.spx | Bin 0 -> 65566 bytes .../filegdb/objectid64/with_holes_8.gdb/gdb | Bin 0 -> 8 bytes .../objectid64/with_holes_8.gdb/timestamps | Bin 0 -> 400 bytes autotest/ogr/ogr_openfilegdb.py | 157 ++++- ogr/ogrsf_frmts/openfilegdb/filegdbindex.cpp | 615 +++++++++++------- .../openfilegdb/filegdbindex_write.cpp | 42 +- ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp | 285 ++++++-- ogr/ogrsf_frmts/openfilegdb/filegdbtable.h | 60 +- .../openfilegdb/filegdbtable_write.cpp | 67 +- .../openfilegdb/filegdbtable_write_fields.cpp | 2 +- .../openfilegdb/gdalopenfilegdbrasterband.cpp | 8 +- ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h | 4 +- .../openfilegdb/ogropenfilegdbdatasource.cpp | 2 +- .../ogropenfilegdbdatasource_write.cpp | 51 +- .../openfilegdb/ogropenfilegdblayer.cpp | 104 +-- .../openfilegdb/ogropenfilegdblayer_write.cpp | 13 +- 113 files changed, 959 insertions(+), 452 deletions(-) create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000001.TablesByName.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000001.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000001.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000001.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000002.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000002.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000003.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000003.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000003.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.CatItemsByPhysicalName.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.CatItemsByType.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.FDO_UUID.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.horizon create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.spx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.CatItemTypesByName.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.CatItemTypesByParentTypeID.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.CatItemTypesByUUID.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.CatRelsByDestinationID.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.CatRelsByOriginID.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.CatRelsByType.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.FDO_UUID.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByBackwardLabel.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByDestItemTypeID.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByForwardLabel.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByName.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByOriginItemTypeID.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByUUID.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000009.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000009.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000009.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000009.horizon create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000009.spx create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/gdb create mode 100644 autotest/ogr/data/filegdb/objectid64/3features.gdb/timestamps create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000001.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000001.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000002.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000002.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000003.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000003.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000004.freelist create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000004.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000004.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000005.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000005.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000006.FDO_OriginID.atx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000006.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000006.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000006.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000007.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000007.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.horizon create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.spx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.horizon create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.spx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.horizon create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.spx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.horizon create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.spx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.horizon create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.spx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000e.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000e.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000e.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000e.horizon create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000e.spx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000f.gdbindexes create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000f.gdbtable create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000f.gdbtablx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000f.horizon create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000f.spx create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/gdb create mode 100644 autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/timestamps diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000001.TablesByName.atx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000001.TablesByName.atx new file mode 100644 index 0000000000000000000000000000000000000000..f4eaf02531704846ef18b407c3976fa7ac1ec45f GIT binary patch literal 4118 zcmeH~?Fxc06oy~>?k>Eu=)=}v)yVb3yXjJUk1|3j3V%3*GkNC}g~w-S0|8*oNSiPV zX35N$IkRG>Ovc3LIPBF74%lH+_W^fYP#_=C5&0|v$~#53dHTjDb>~@m7JF8oW9^wl71t(>{z}p{t3v36_BeR0waJ>R z7hgr5W(|wMFcOPGBwU^|>bOQty{o+Yk~H^vZR+}x8c+jjKn6orp9BUJH!M$m<8pTb2giXbkk`ymXm7Ge`@X3@8qN1c;sT^TrV^L^*u zHh`E}GapRzlGbBdJ%RVa&*W`Hd!#@|MB)gE8x-YVXmqzIpb54xxKl|uE(w7d{dkuq z->15A>b8+iYLgRU$aK9)lk;!MTfnEsp=_lqq|Vf(@_fL=p6_`gkO@h@A?MnvKo_K6 u4b7D15mQ#z!UnsfoxiiM{V5v*-^i5J#g9CgJR%w~J*O&XjjnT3@{51d`8)go literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000001.gdbtablx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000001.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..3d6f920bbf82725a7a359819071aa1ea475639f6 GIT binary patch literal 5152 zcmeI$F%5uF5JbTvgaQS)Ko~mkfDAOC0wwg^7Kk93pX{rCHRX*}HT34*)Z2QsCr5H7 pS8^v$vgH4ocmxO#AV7cs0RjXF5FkK+009C72>ebUr@imI_W~9`1swnY literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000002.gdbtable b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000002.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..a0af90eaae1fb1738ddead12a36441ef8292c88d GIT binary patch literal 2055 zcma)7-*3|}5H{*c$JBM>=L-l0#>7CQsAyD!Hz!THh%}iJM;g3DrcFkqCP7ImHvV{o z?KWwh#Dz$f9Q*G3zB`}KEdam~89o_pGK#OWyPTb@7U=#Co!TX$U4{6>AOIn_1`aVw$t)&wf=(#gU`UYu?2c3ox(0GO zuI?fA3LT*zzBKa#yqM?G4Cgoq=e{&e;vl?Uq&N?gxKA~#Jr9)&XhQ|5wx?P41fm@m zY3IkB&B0i;CMvSOu1xlnB>I{9S?14Pa)yVcp@S?Nj@7$*Y=p7@oU`i6h9!Hv#WT)g ze*&*OO*d4}(Z8v!0g@$dzh^^-D&{dhQJX!Q29Lxr7#2x+MU%o-q};#Br65TooL90! z4|mhKmX|g|xS(eDIeFgMST3keDHZvbB774@i)eiYS@^qp5G=`ctV-|%&7hXcT4c>w62nN@a;TA-8V{K>>}pstJ{f{uzx zwLPMCxx@M@yVvWi+c+c2B|**yRJ90~?_BEwV8tnL66iN6&m&&HZqj>EQP1vYlw>%Z zvWArTxfI6is_^+f6(ZK!A$ulRq>T!y*LmZ~=!;3p`sW+_TcHP+nbl)MpW^1U@a>I0 KE8>8gpP0Wjd{iF* literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000002.gdbtablx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000002.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..7c12c568195050346285f43f1164067d9ce86e7c GIT binary patch literal 5152 zcmeI$uMNUL97pkkmOm2^Jhm2##9$eqEP*Rw1rkOeL9hS=;0Ue>8dF=-gapO!jZptC z`F_laOP;(er8I~wVx!n8mY&eUoVyj2aD)qdU^)!8P{9j+u+D-5Jm3qX*gyq0c!S3R7O;Q?EMNf(Sik}nuz&?DU;ztQzycQNS%9xS#`yy^ CN)wa- literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000003.gdbindexes b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000003.gdbindexes new file mode 100644 index 0000000000000000000000000000000000000000..58df68d525b47a895d5876a8bf5f437a90afc3f0 GIT binary patch literal 42 mcmZQ%U|?VaVmAgC27iWl22UW(z#ss`|Nk=rNhTnMhyegseFSR& literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000003.gdbtable b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000003.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..f8006ccdaad9fa9e214675facdf868ce17284898 GIT binary patch literal 1016 zcmbtSy^hmB5S~l!!U<@2g{>&8$UkwCZGb3oj88a@YzOWVMb_fm%Na$EodA+*5 z0hSnd#Tt1xp=3b^pt4)4c;JE$1{~sygncnpy1-xxbKnR=d4DgD9IcUShj0V~h#`U$ znMKxcgAD({u{RvM$hjeE`!K^ha7gejF@(6vdR!k~nX#|Rd=>9rz1lGi$838ef>ZFw z^a6!8Yeed_{DD0pogC!#?;EaTbw=cZR@WL$MQc=LNu|xIsy9?s)9c!JEcIQ>Hn4)3 za2`L8!c!v2Re3B8dX|rM`2_I{QW`Z~Q>m&el-4vnD((e~lXDSZ@i2>v7u8m_f3nQ> zaQ5L|asTrByEi{Sp5Ix${qf0u+`qex;wOOB>vG?94#YzZ-_2f0gMn`c!&%G&P3;jW zFT`(7PKg|LA%(fFB@^iqDO5#P{!0o}(K#^MzUA~r#7|f}f5wwJ4{V-IfKp9D4q~Wm&Fj8|HbLelJqnq74vN#rwN;i4$*PMVj|H(1utY%9&}kK z`lF|m()u4Q9#6(EadQ7LeooleDqR)!OB-><$dp$1}hPJ zS7r*8%)!RZV&@(x^JRHXR?CSe z;LLFzz8#57L@76BYEg~+4BVwoj>TW6c0VHbLY)1Y8a0tlHVQ}5p{bdqH8r&jGjAkO zGUcvJwUrnmoN4hP&=SoH)4+8~(OVoEK6eWDtb^RSWo63ES7toeSo8PJF)92>_q`Xu-zKjB literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.gdbtable b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..0cbf02d63282a4455bcb2f2593fd2becef0b3594 GIT binary patch literal 10304 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 CpD$kk literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.gdbtablx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..93fec31b61e3d8dce2c753b4624c1f43c4d16239 GIT binary patch literal 5152 zcmeI$!3_W*3vQKmY&$ literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.horizon b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.horizon new file mode 100644 index 0000000000000000000000000000000000000000..b64b92356a70378da21d5ffdecaaca5124025076 GIT binary patch literal 32 YcmZQ#0D;N_OdzTu3_>$hIzagj07ZNRrvLx| literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.spx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000004.spx new file mode 100644 index 0000000000000000000000000000000000000000..15bba5bf1c63e26f4de207a8e38a22b89fc95ae0 GIT binary patch literal 4118 zcmeI$u?@f=5JOQT8cIfB1vbd2lw`}CK`ek$ek5ar#1*eZl5)h$eX=gt5I_I{1g->n m)tE^&y1l!H`)U}E5I_I{1Q0*~0R#~EL7+S-ra0#pz~>L^aRjRX literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.CatItemTypesByName.atx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.CatItemTypesByName.atx new file mode 100644 index 0000000000000000000000000000000000000000..5f5004620ba9c73658f069eeace1557d8df8cebe GIT binary patch literal 12310 zcmeI2S#Q%o6oqft_kCZ@{yR`Y0Er^h26#@=6e`lBvRx?uhM&rJ#tBh|JXAcy zvI1FwtUy*EE09tFt@&@w2;AX*44egzz!mTmJOBl_4W5IW;2d}XZUDW1`8?mDoOTB4KiNe)23tWnWt}0< zCkbcfHTYHVlWqM~2Y<(cxBL>vYCIpO_R0?-Qiu4P;?|pbg)Q^z!gesrG^v$qK7SoX zicihG9%~u#Q+{qTaa`@E*KnDiKmSuURz)?>&y{^87w%$hAC*?#UuKd04~N$4Ixc`S z;0d?}?tzEkG&lk71N927gJ(d$8Mp+lf`c5feY|;i!>`24wc?-P=L=u=;N#*~QG;lG zqgg6_##&~u5tx04*-w}A=LWn~MS7gurR0KN94X5>ry6r+^kb;p^!vLysOOF2 zXY8s@NrjcQL*Q+OTg7ixPc)US4^o5W|H%qu1+oHJfvkWO_*JyHY9+7Td>tD925`t0 An*aa+ literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.CatItemTypesByParentTypeID.atx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.CatItemTypesByParentTypeID.atx new file mode 100644 index 0000000000000000000000000000000000000000..269f1f31a465213f69d5cad44ad6d2744b796761 GIT binary patch literal 4118 zcmeI$$%@1v5C-6$ec$(e?>QA1HCi5xS}zsy?(=7uzRamFGtjgJUks!O$&VP!WHOn2 z!dTXLyAdSeEM5PtiF3I?S#Oz2+Q4;Xop+$Yz&`gLY&~(Ou&j=Y6hSHHpQU6W(h) ztTGgg60*L{6pIz=ICv#iiwSnn<^au1d^ab=O^a}a_`WyNU`VRU5dLQ}*`W1Qsy$fBr{(?S+K7u}neuCbD zUP9kOA3{GuKSQ5E-$S23UqN3(??JDjcc5ROKcP3}xiw!gwhubT3(3+&KAMv3-DcIQ zX6t@ytIV_eBGp7xXC?0}G)MMgRwK6DZ``MrdJF-%N#Y!eHI2+lTkJe$ZHO|pHdn)k z50iLSoH#pq3Qs(`y4`Owq(`Zpmexihz9_agYLa{U=*43rYA=cIo3zzKQiN&rjTU-2 zGo+uKdB62JRJ$6T=_qckO@n>Xl*uNII}h}TMwtib|T&0Ij$Fn7%N5V%`8(hd(y+c5W&hW9R(a*)*<5-Mm` z+aoGhM7hl!jKafl;<(aGaq6XDDpmUI;N?2PQAvP z=wJLIhdEKr>CJs|DkCac9ZsXD4K7T>w3VH5q{`N@hoAc`xZr`S3I>AeHE67ZR~pCL z2wn$XMQa5>2f;~jA@b0SU8wRtC(BsNa3Wlaczg&B1 zXV9>Y3H4M&H6~rFVo!@rfpgYyX?0Ip9lI)qghL!u~O;Sw1ds&t%tN< z?0Xe*#zmEz2kMH&6S5LL}D5F!FlknsCrsfDqUmDbI7PK|7OI ztyu7j>N%^xNls@QJh2fviY2ZM-yVG@Qr_Q`zgUkx9|Rr*9t0i)9t0i){%-_cKYug7 MzMcQ~Ux4fNzfc=rO#lD@ literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.gdbindexes b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.gdbindexes new file mode 100644 index 0000000000000000000000000000000000000000..bc887093f340449a4e4f40224ff3a487814c09a8 GIT binary patch literal 296 zcmZQ!U|`?@VmAgC27iWlAWmY)Vn}63W+-9s1d1^*2mtZ_|BOJA1Bfx@g@B^Y42fX% zB|x3I3?U4a3dhbTnfm;XF>qb^dg{nc|bd0W}}-3 X@&j&lgxu%{v>_L00|Nu98xi6F*S08% literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.gdbtable b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000005.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..bf93ec49a465f6bf1205fb164e92bee5b3303828 GIT binary patch literal 2071 zcmZXV4@?tR9LGja+h|ws-Oi@JHbdkjWI+UoZW*Rpq%G@ARR|)Jr+GE+X$+g#e-_P&;-tXVJ z5(F_1UZ22gGQ5P(40p#}kn;gr7oqz}=+`}!&=KniKYXQqTPhL8bUsT)Dms%dEx6XL7ZwaoII8Q9t zM=rG(`qvz6+OH@JzLPZ+DeBVAuD|IQPA6Df=Rfa0OGSBWNR9`rfXE=S?6lhzt7TR4 zA$zN#enB zzO_s17Vq1b-0=Q|UN!Y(JDzl3IgjEKG3*ax8aQ6 zgjZ6fg$FbTQ;JFq$U=V0i?u6y!qootdHW{eoOgNwTL_AUt%4!ar^zqO9SoFj=HtBL z4QertxN8|R1DmTj8j7`-(=T64J|S=1lVkh%!nbGe_|8gZ^p5({fLy==T2IjvZHdYz zwe#ZfZPhjVlm{{?zMD@1U`&9A$q{A-Dx1%#w}0+jwm$Bg{q*D3-u6D65^EX8 zsADWB+^T$zKW@v6s}^;!>d>|jTq}tOBwxV7T6PQ=R5o~9;fzY4`! z+&Z)DP4bE+x+&nx^b(x#)d1soAh>mu+0krh!v5m|Zm-_mtF+EF-DsXxl7M5L>BaQK z=nH^Af9bu~fk`J~8fQNH@y3jRHvm^HMWeXliNFo}Oq7XmZsd*uG-PdleLrPK?XmCp zx#?D%a=7E{GZZKf3uj(f>6)~6&cbH{S$phw;Jk$3j*l0VI^1MX)Acd)XrTJ54ISHR z)|?*}JJW!w_vNI{h^xx6q$4MaT>3Wc@x`qR1JXI}QT5{HZXKCi@oMY5xb25Oym((6 zR^_e%98&-%J$l39qYd}X&GBYK2fY?E@R>pOP@F B2><{9 literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.CatRelsByOriginID.atx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.CatRelsByOriginID.atx new file mode 100644 index 0000000000000000000000000000000000000000..1664d21885e8448309fe4bc31b7f2754ba2770b8 GIT binary patch literal 4118 zcmeI#OA3H63`Nnn8D{~jwVz=ux+!t*TBU0!!Y!mBql7biA|k_6^|k&@{5=HDBW=6c zWE$t_)t9ttE+J@%mRz>QLULD+%h-tk0tg_000IagfB*srAn<2_@lE|T`M306`wgwf B2_gUh literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.CatRelsByType.atx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.CatRelsByType.atx new file mode 100644 index 0000000000000000000000000000000000000000..0413692ccd84d77f05a97571224962a84f049876 GIT binary patch literal 4118 zcmeI#Q3`-C2nJxgOm9MCg1tItmx}IQ7CXZT{(%#pl<(~)B3hoxH~Kf>pCNE|*ZnBg zxJn31HRoocTwKCh-$Grl8JA%v1Rwwb2tWV=5P$##AOHaf{8?aqQ-3D^k^Zya@mvX9 literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.FDO_UUID.atx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.FDO_UUID.atx new file mode 100644 index 0000000000000000000000000000000000000000..282a6a2783509cad39c2bb11f6f1e7b6c72073d0 GIT binary patch literal 4118 zcmeI#O$vY@5QgFDGVKNw%%7#1UMjkKS$GBsUSS+&HN&&}L`0US`WyY5_-6>5-q@PA z;`K;%R~JguSWVVaQVKRUo1434T*giW5I_I{1Q0*~0R#|00D(UXtZ(Y?$$zB(-fy!1 B2}S?_ literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.gdbindexes b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.gdbindexes new file mode 100644 index 0000000000000000000000000000000000000000..c608a88be082bd48fb5821f6bacf48df11c2495d GIT binary patch literal 318 zcmZvXu?oUK6a+_6v526JAEZG@C5Wj-MI<6B#>TIYbHordSM0&Zv3dAtL4iW?c4G0C6X92N+;_M6@{tQVBSq!NR$qXe7o(wJw3~Ve+ zETIgc5DvE*6HrAFLncEy5a&Szxzt$LT!31NVOoF!EFlb)3bd^TFzhfWny&2}TxH23Mei{vKN@m+<=L3HLs(GcybOrM_^j+V`WR zsMTjq+VK^ViW>w7o|vq0-sLXixB9~K;~<{`0C68T Axc~qF literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.gdbtablx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000006.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..431307d13c60381f56be9a7c0c765046b36cb27f GIT binary patch literal 5152 zcmeI$!3h8$3-MA9039Z2oNAZfB*pk1PBlyK!5-N L0{;rUe_$^>!R-Lc literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByBackwardLabel.atx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByBackwardLabel.atx new file mode 100644 index 0000000000000000000000000000000000000000..8797338e7a9c750c8dff7448fb1f9ba3da067822 GIT binary patch literal 12310 zcmeI1=}y8x6h<%Z`@a9`yFf4|Mvcn@Ac7K+62KVW#z*zs)_I{>NWyKzz7%tBVYuKfDtePM!*Od0V9wn0bTQ-{20*hJ#>ur(E(aT+vpUnqXIfY zJLnK?pk1_vHqkO#L}zFVC3%Gfj*Ifb+P*1ItmMnA>h-9`+CGqLse51DyR!#HJhjL; zRB+kCnfi<~6J<)8t$4?6O-j*mh0KNKC*1X>b;T%ue`%)mf0NcT%%74BawFD%2h;9v zuwTVdaYrt!MaC?Dii7^bC~9wsf;O?NS-m);r*SiQZGTJnDV1>2B_g%Ysgp(&&(kux z1mennp5s51w`f%9L_D+2fx7%%;x2CEgsirG9qFj3eN@Rmdu#n!_cxf&&N@b1{HEPs z?7vE1YkKQrqedndzuEFvW{i9!a&_Dub=T>gn~dM|`RjkurN?F5nq2&TXA1c^b@{uG zo)jJWT|6rs_rBA*&h0N`CZjXykf)fwfy{Py`%`v0{$)EaNGQy@KYPE-B7DfEYjoX~ z&`NaV=fl;1@@}7a)YQ4GuIx1v5a$5;SN{Qdj3#l|Wltg_n{Pj@Vzn9pBVYuKfDteP dM!*Od0V7}pjKF^&@D&ueP0ax48++Zf{0+VJ$xQ$N literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByDestItemTypeID.atx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByDestItemTypeID.atx new file mode 100644 index 0000000000000000000000000000000000000000..47d2132ee8b7ee9a518ea5306182b61d0b93f04a GIT binary patch literal 4118 zcmeI$xsKg1429vFWU@{6WhTo5q$8k(qNtJ*rI#}N?m0I=$|=$W2_OLjY?Bb*?3Gtk%M~PCapa1Ms4K-H{jkKgeY#~#dDN!18Oe?+hW#_S{{;Y&@ zDCS;BeJOEDm2}ylyoUKi@~mDd0c7a`B7uW^*0!N_*mv1pEVm2FJ@jCOJ#lYKm8GZL$riEF0g>s(Kgyg2k032 zeT!%rZK4c$dvj<7t)exwfwoY?N__nx{(OhrJ^Iti*LVNE;CyWVep36t(Z13i$2=#! zUHtlw{Lbms0t@3mdzvSF7F)W)&VChh^07D?r5>sB5j#!W-*L_RSlBf^dqe!o<6tRl zvhQoXAbx+FUz7QxLnPb3-EwQ%eG;GA!2FW=#G~pyyLqI&WNx|Rc#w#-qdB+>zuRhE zP+(n3YXsRB#!{V9>c9WUzB;`c?h(JhqE1Qt>QD>!q|Of)ILYKbzNZs3UzOtQQtzJ@ z|48Vu_DE^X5k_|&r&H9b2#A0Ph=2%)fCz|y2#A0Ph`_%f@S115r$+$X`}gF>Z!;8~ Ax&QzG literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByName.atx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByName.atx new file mode 100644 index 0000000000000000000000000000000000000000..70ed36c3fea8bbfec79d541e8f5931e81151e214 GIT binary patch literal 12310 zcmeI1`%c0z6vhwU@Avy(dlw0Y#Hi8v0E&P_WM<45-o{7u`#KCOfd&({F7b?fvTo9S z`?cL^&qPEj&;qmw<++9OB+t^7rqrb;cPPcuK&dM=b0(G!%7M2}p7*YGD*+{-1eAah zPy$Lo2`B+2pahhF67WfYYyPvLfdhOWLc7p9bPgRsn-FsZd(alN0bM{l5P!b}v5r${ z6@6!X;$Zu` zm6qJY*#{glZ}L@<&eWA?vrG?uqQc)I*u!w1Jm0=f~{ za^$oiK6ltd$-JF2ahE@vmYeV`N}4{{Dr2k#lz7(ZG`1} z-0d$yBw@6RFb)QVjMwqIb^9YbzAdmv%6J`lmRNt4;6D)f4l1}!uKf8%pBu|R D1zNXT literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByOriginItemTypeID.atx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByOriginItemTypeID.atx new file mode 100644 index 0000000000000000000000000000000000000000..139b478cc0a4b689389507d63d6ff6ec57496bfa GIT binary patch literal 4118 zcmeI0$*LPM5Jg|+d7kqD*%e5tNm^-NxLZfXasWanRL#a;Tfn`hmdbuqnv7~Da zvXVDR5~iksa+>z&qI-^cz36CWYEFApNBb4jxDFaJ*rl*?LwifnCCyF65ETtHTzivm z!(Nr^j(Tg#le5>U-HiGkjbzkHT{Y9(yQdzxSV>69 zk&{Mi!jcqq_(}qOUL?45a zX$iJ1k>K9l^Paw_AC_bzUkjoJPuZjO%=y+7E|E~F5oS?HnlX(Yi89CDGqtsxub10W zb1DPWowK9;aB95`GFemGY^@E<@wU{erm5^k&9!3M)2OY&WNYP~a`GWCv09aqW6;uy z_MS0EsVKmlWTl$Zw(BKE;69kRJG$dU4;;XLuP?%vVHC4sLbs(h&JBlcGClUFS*UVW zn!&B=>Wc^5|HIVZ@A~tb)4*xqG;kU?4V(r}1E+!0!2dDu>;8%TcxwOi7U1~&8=s}> AV*mgE literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByUUID.atx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.CatRelTypesByUUID.atx new file mode 100644 index 0000000000000000000000000000000000000000..dea48d3ebdbbb51845e4bdaaec08af989846aba8 GIT binary patch literal 4118 zcmeH~Nv`EK3`Kt>skD{8sb&G05fCLxqK0gwO+CE5*YyW7tZ7CzZ~@uACaFVe;Fp(| zmru}jeSrRg-ayy0AEEcq&(J^6N9Y~&1N1xe9rQQ!HS`Vi2lN~C7Wx+Y75WAG3HlTI z9{LKpuP+mLl;q;gCe<^9r0+pKEpQuk`l;MnJRdxFQA?-ljY%?+^kh<=Ue{JVD((5G z2zEvv#NtLG;k0ZN>Y6o0&NPLq_pApYD4ZwBZk0>}tJxQCtT;DZ+N`@7wce4DZIhZQ zF>9Z^PE%cQWtLz&h}DwhY$FZ$aqFfzXQab*R?)r-O;k$Kx{&P_)FhB)ZksC4$JjeF zW7CkcKtdKFf6^)4+K5{E&gWxRRF&rVxL1@Zp~0~s%>#)Vphh?f|Pu}5$)8M6Q*gQ~_Ltq9Fc(9U-D;0c#R%E7x<<_52VJ*qb9 zc&~AI*;crE)M_V@rwzC`$r&Vg&SkZa7+#+b4zpKv5~6CFAf&P=!kmzfR7^NozP{ZZe5OHdS6!@ literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.gdbindexes b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.gdbindexes new file mode 100644 index 0000000000000000000000000000000000000000..2a98c93adab6374c865fccc1045125683eb4a05b GIT binary patch literal 602 zcmaixVGF@f7=<5Fd_a6e{>-eEl9l)@8wnelmHhfSvslCKT4%fW-MP=X_q|P^Ejie@ ztR;^lJA7iMszFamlA7Kv>GywM)d?H<7g`sv6;HpM-C2SERT9zjtKJvR}JS&t08k_L_S#SGPiCL9r)!ePG?#u zD|Q4ysahx?6s@(Qww0my02RfzfQlWpSlXhFMbxo-$p(^zHa0Uicf0%l`~8pa|98_L z05A$Z58z`5AJcD=lkv+NO)%@W%(o2y_P|~YV!;YP02!cx0Pp}9VB60&8blihgD2QJ ziGdc7KnW*kC>IYTAQ>dUFM~8Az=H#wLA`GQTM>{#Jp$tHGk`nrff{bQ8cu709E8Jn zEL>JWfwxe=4mbwGDH&M#Um^mC$%Xq+5LpThqzn+u2!MWe;2ZerT=e2Hr;1g_%U!3&$sD1Sk}3%u2dQ!lx|2VWYH3nNLMgZ; zpg5+jIA(is&}IO70*%$LaC^1qXV0L$MRmGBY9jAaVB2!#gw~K#Z$FLO5w-pCoRb4C zv{D%%BIwm>?Mg?JHHNj(xD^Ib4w3~x&%oA-FYH`5 zbqKe3&97R7Uu^mA2<;V>|FR!(bIF&rA^BeMm-o4>4IL4cOvzAo1r#%gBob7lOzOaV zoAT~~gBuo+qz&XgL_T-_di|^&R|C`L|8o1yT%Q&^8aOG2BT`s~!IBp5=yb=ZBU@AM z`tnO$0%{gIkgRP7uZqUTaszYP)19kAUY~s8nRaW#HP_IcY22wodzTk%WO=vWXgmzP zX+m!DoQ-?l?eu2G=?A>LM}D2d7G@XD6xB4>%)?W@6@dM14lBg@UHpm-xpSX-Ky@vl zBPiNXu408bB~1J<7ACAebJ3CALui~u1*4)otEo@Ebu1_-oAtf_Jg*fe@JweG-1c7o zHeQ%HLDX6)Ri?6#_hN+^1^S8rlH-UK9CJ>fqAZD-y(D5rc~DmI&~(k-S(&8Y`u)|* zWP+-Du7AEB)U3lJf)ynUuZ&RfrLkI4W~~Hz6{S&`W7Gy`XcG8P_;xYRZH{sazg&lvLz2&hm(9Z%5En0G8oPn;j!8}tIAh)txUESgAsTPOo{-cFlP9*jB7X*5+0oKQ;hq{ zoVAU3kHE`?-CZuI1E6sq6*C$wanuT?h?#C`)=>a393HZCpCh}5_zHK_*vq}jcgLo_ z)~w#LzfrtlyeyynTj4Z345sFQ4tzvUgk&ukz4|CpH+4Ax5}BBgYbK@MEG=44W?vw@ zv&p*v=P1qy7HZ)}Mj%@(DPfh0NeuAP*|Neaei(1_fSaYQhrj(mpwZDsn-&LW+8)=6 zd=F0jw`2$?L=J@Q!d1i0N>VzLWA11I_Dp!Su@ts}S22iP+L6@s>+~{iRj7xkwBgYi z^}M8K=LuHG#^Dz~OuyY&iDNq>EQqaE$q8*QOjZvjNtq0>0dWl2oveH8X+9T(+neu3 z7G!jC@y**i{5RR^6=4S!c>LhKB|zrPQ^!Mp1QY^w?s3qt&?go!9>@v&tn8sfYr{5> zuHxqT57{`w@NAk6WJ_vUrqeo+4CKDp+)lle3qq<6{q^z8YP)v)Hj6`hA|jdYo@Q6i zK3VlYFRigm=qpY)d@{GLO{|Zon^Sxvc8=T_mAmnvo<tL4H9cf zB_yd>;lv|!G)Wn5C2$D9XN)&Z&#y$Y>wHDUXF`{b(Q8`#6KAZF`&EP;_%SIU#rtR- Z-lT?Oy_(Q!A$zTLr(Sm~iBS)Le*nHMfzbc} literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.gdbtablx b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000007.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..bf096e13d28fc61c7dc794d7b965ba6083f1f807 GIT binary patch literal 5152 zcmeI$tqlS}00rTN;~y?~+Q3u685C$bpaX)#(*j-KNC*~%q6ZYoIo=e3n@!%AY_e+P zR;83qw#fR~oXZuK-Ef0V6`s)a!X4H;e!zARK5!g{77n9O#@QL_NjSk5cGK{N{Vcp- nRfh|BEMNf(Sik}nuz&?DU;ztQzycPqfCVgIfqxd@_m6RY0`v~Q literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000009.gdbindexes b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000009.gdbindexes new file mode 100644 index 0000000000000000000000000000000000000000..c9d0caa2233c4a1fcbaf3847dab2f14df879a2b9 GIT binary patch literal 116 zcmZQ#U|`?@VmAgC27iWlAa-K#VsK?}W(Z;M1d1^*2mtZ_|BOJA1Bfx@If0@u-N6hQ X42cW{45>gd79eI|0O?}|Vq_Tr4EhZd literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000009.gdbtable b/autotest/ogr/data/filegdb/objectid64/3features.gdb/a00000009.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..451b4e263cc6b611ab030b0bcb1985aa6e4e0b3a GIT binary patch literal 1384 zcmaJ=Ye-XJ7=DlEoH@;Yg#8Rl1BnoEURL`N?s!d8C(aRNvdl~^saUJ1KM4a{&Lk^; zX!WNHxuSw@ts>3raWT4IXS;>OET*RGphz_FCN(mHCdB~2TOjMZg_SLI1>oPbjJB<`z zS%AiRr2*;Rek{*S`Lq>hj2olQvNQkU700{l!DJIy>UfN$IMl{--m!>DIE&{)hZ7wY zW5fWt0GPQzS6O6|4VucJ>^N0YDg0p!;)tQKd<&FjfwjuGjLH}D16exsNklj1ih=iK z-Wjl$fD!1*eD+<338)OQ5NIXneT0sIgAI}So6F~=II+WX8}d}<3b-qrdm(p+A!Hz{ zYOyXI^CPIK1Y$YYkpvaK;Zu|kjQ`{0@ve4w5kLw!vV46i_t4JuRQWYj(q3RNh0HI5 z-__+Y%hV>jED|}pjXdt3ea7Q5>pA!IK<=LW za&47MuqfjvSKk%oD5ZL2iyWb`C;|tZJ>`p}PO$r-(HIu^h^>?Zy6m#qWs#^ADfG`% z1yagj2|w9|^4aWgS!j6L@OnsSum3#ekv@0a)I4bz6YqB2YH^DXx@ulHLY$8?f86g8 zzP|dkqGxJG_TDj_`Ep*k(tSGYUYlD}-7?(D4cR_ho*vUTKI(VxI^7<> zZN_4)b5r3OJEApm#^e+2xsLlpjF!Y9Zmpv}Bta5jNQ%~w7-@uDmpqhat>1KsKqEmydIb~Wf8Sr^#cmuO<_!t$I9D^JbWSXbk(o4{@ zf(BfoM6I$!q*lFxdQqkR literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000001.gdbtablx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000001.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..52fde329b77d5952c4cc4ba8ed62037af103eb28 GIT binary patch literal 4128 zcmeI#F%5t~5Cze_a6Jalf(u-rgB}4WpryvZ^$4Is#pQ+q5hSz8>$aNicM*}$BV)ym u6Biy-3_OjPFk``n0|hrc3s}Gc7O;Q?EMNf(Sik}nuz&^rQ{bKQ-<)sz6$y9% literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000002.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000002.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..0b29eb53e07d77c606e25ebcbd77f7e494d1f585 GIT binary patch literal 2094 zcma)7?QYXB6gAq)#`H7hfha^j_g-ky`G&;7oO4oeiv!*s8&Cy z9k}G{{_&`F*sLEOk;cC3$LSS=SZ3z809XPmr+|Pf@C`&D1}@!!e0$>zICQ}Q0o_jN zh=D%+%1qWf)F%WU)pP+YY82C1Naqxts;YorKYsphQnWv-U8RSH8n#x3s*RO%yp2B- z=n~JFj8o`3spI)$0!LBk`4^MeN&P6SF{tuE7{$qo=95k_p&h0*px-d1G2_GWNF;l!dCjL!k_h74CcHQswo!K>A!HZI7c* zg>YY$cnifpBf`w|aULPkyLLG%KkAZDozR6P5?cSly&-shS~qEu#L{D&Tl9XKNY(AE zMQ!pHb-k9is+7dBPi7>3~w5w)?kAu%Bgzy!o0Xl*TRY#9KTpo6f2i6fX;xPSxL5jqp%i#yPL zmpsM&#a;4tccG*JDVDgQ$By9vCsgS0z-A$3DA8a*5=v*Z_+ZaLM^tztj-&!Lp7>7t wm2|}&+qIPAf(9>)@NxkcZ~+%^0T*xq7jOX=Z~+%^0T-CB0MGpYpTKm(563+c?f?J) literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000003.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000003.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..5300ca64943170bf47c6e0c42c14ffa4c47a1995 GIT binary patch literal 797 zcmZQ(U|?VZ;y*yl3dArV%M4+8Lj~l4w7ZL=kAktDp@E)3N@|&|WwM2lnW2$!s)=q& zW>HBc$OsmYaz+LQ9tI{)1{VefHWntfV1^)u5C&I<2p|bmzyTuI+!zuWau|vkQh~w@ z%p9!9B9Rag_6UYZhERq)hD?SMph|RAQBaX6JZgNQYJAbvaD&VaVaNx%A(f#B=$1T& zWFQM{2UiqM8DCs7_tx%v{}kl+KNA_SZO-%=igM|#uO-zlAER9SJEiFxqfm%WCVrWP^0HPQg!oYMx zi=HExpY;RGXXrTWfX?rIyLG|$V+ZX1to(S%Hz~j#Mh|X}GE_o51f^5Yo+aqf&wtob Ky1bk*JPH7ebh7#Y literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000003.gdbtablx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000003.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..730418420e468d11c7758cf599bd0f363c50b722 GIT binary patch literal 4128 zcmeI$xeWjy3*ltdTw-w)?D(44e^(+Rz*YNVNVx;aiCMt}eT0t5&UAV7cs0RjXF Ld@a!bfUfrd*zEw1 literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000004.freelist b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000004.freelist new file mode 100644 index 0000000000000000000000000000000000000000..040b0de5c092ac282111f17f566bc3fa2dde68f5 GIT binary patch literal 4440 zcmZQ)U|{$U1eZA(80@5g91!D)E|k&b2xV+HhcX0Jpo~s7Amb5Gaf|?vF$zXQU^E0q zLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1O{;kFaq5M>i>fP$eGlpL1wbkz+7s( IliKD10Ap{p$p8QV literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000004.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000004.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..56afc1dd9910f94ae71d40779de43326c9e39027 GIT binary patch literal 47714 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 literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000004.gdbtablx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000004.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..0a43ae9a20008fb30e3e2b4c91943ddc39eb1dba GIT binary patch literal 4128 zcmeI$F%5t~5Czd8M}rGoAOZ;l1vo$fK>|Gu1!_=3#XlJ&v&l4@tzNkWXi}HjbQXBh sJi;(Rzred@x5ItFcIC)XKmi35P(T3%6i`3`1r$&~f!_<%e?YCy2M=oqqyPW_ literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000005.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000005.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..9a0fe221dc4aa99430abf335eefce0eb0d7f5564 GIT binary patch literal 2060 zcmZ{k4@?tR9LEnRe{?|Qk8yEkR)GpCtWXh|qChDHgd%O3E==6@`ltu(UAa4;3~{KS z^2a|6ryEeBf-x>CF;f%~7F|XH{vS%zRWivo2FlnZQ&)-X9kjZ=WtY6%yLaEu-TVE1 z-}iIDFiZ?bHXN>SI4)0*>DgNQ!hCpFB*dkPq+#KbFo_NnhDH-BB9@0oNWrpD9m#OT z{3isw+=;(?q+?nv7Xui9aab~zfMIR|XF(>GIsGtGEbv^1;TQuI6jW%iV$1~RQ%d+! zpoA}tZ~+q>p8D>q}ACJpt!%7I1-3+h6+dWl+lb*!sCozP&vsP=T^88j=cwI_-kmJATV3 zg{5WjSjS~D8r6BlX<3fJ2~LA+4L}NsN5|dTvTTjA@`%ubVL(>K=ZC(tu!dGl}8IvAc5g{trcyGcD)y`NCLuE}k0hU2^ z(X%FN^W5X|s_T;XRnE7psM_zfS|m4ffMQ{7`0TofLyClT8x^P1FB}raj~_repVw+f z9K5+l|A^WtsL4rcNtt&cxOx?VQf>*f5hp1=F)6)+h{ z0_W}VctQF7?RWhY2g+rVG>=)&P_59eb!>$X;HNg7RcP&$o&ED^K!x@9OqB6SB3X`x z?!XPAG@Qu;4Bu~7UUIu`F=wQHWoyybE~y0Q32>cqI)651m|4@(3YV0og-f4|WHs1O zrI*99Qzqx#)U5B%KGAqIp)BNn*3&3KJ4$$~aFzp%BM2UO?$j1}Lq3^u>U@!N-;M^~ z|3hT(Y(vxXxhkE=FRE_tOWz(uSmq2L2}cVlv>yh?Z}?h76+{;5q^(IUUFKC=g3nz= z8Fvj}7+5zxZc7dAWFowFu9SC*aJf!@yt!P*b-Z} z*T+0X34d5U2C#tQraHr$t`Ip=V&0jOp4^eN?yZCVSsPGLXb;;F3ZJp7F>x=F7Dgt2 zGaSAtdTYikl<`iZS^Ul63x&w;6P7EhYo5!iKYIJ;cZa&pq9*oK;glY>o+<0{P0qJ^ zc}>`nt*HlB^mHr9;e9CJE2DKlC;9+46gV*cKYDO5YuC$xw7TlEUvmL!3+hNubumSV z>{jN1znsCn4TrkrmH^}J!?Q~M)262ZdrRbN|NRnu)Oh>lq{?$OvW^bpdNg=pCPx}b zuGrBK`Fh@r9r;)<9+Y=Ze!BTH^}b(G#!CYmJa7Dk#NI>E`0)_zysjjW*5CjYxR8e+lf~l%xfiA&MXF#5p zeCCQPes^D#6tG5u1_J`a4Nj=hV#I1D9Z}oYyVL-7s-56uchUyv6h$b|n1+A#v zW8~>9aJx{7YII_gjpF^4CBKfLM;&F?8d0SZun0u-PC1t>rP3j9o9 N|F-_R`L`{<&M#+pK3@O; literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000006.gdbindexes b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000006.gdbindexes new file mode 100644 index 0000000000000000000000000000000000000000..9e15d35d613629194e6fd897de84c6d110e2bfa8 GIT binary patch literal 66 vcmZQ%U|`?@VmAgC27iWlATDCaWJm|%JO)po7y|n=;#MK literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000006.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000006.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..1da41b719ca6c0ab6209542a3d8617aeaf4cbc11 GIT binary patch literal 740 zcmZQ(U|?Ve;%Pw43dAt*gbBj(h6>07X?GV#9|dDQLjyg7l+-d^%VY~9GeaZeR1@8l z%%YM?kP$2(<%}RBIs6%t7_t~r8Il=F7(5wV7#P@Cm{>v?LLnS(H5Q z$Gz|ON{ze@M=5lJWPA6W{_POuus^iz!=#8%cbo=Iy~HoX`Ip6QwwCDKO>XO-q8qek z5tqoiPjU`z_uBp+JNZrxr$K>-85KGf*7!Ate7JY9yki!+K~FYhtlp9z<7>3Mim}0tzUgfC38qN1*-zRqX?%$^%;f literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000007.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000007.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..231ae0568629460f3704635485ad82e2e607d757 GIT binary patch literal 3665 zcmb7G3s4hR6n!WtDhLXK3QEw?qWIyXh>8j%1R^9UKR;zUx+I&h7&fq3lqg!uAB;k+ z*bxM^YN3i!q}GaJD?{-EDvEyr(RRdQso$}RIQDI_0rFrPo0-h+eS7ac=bm@p-5m@7 z7!UvL@NWkH`d{cs-CZ!d-~_LMJiic*=PY-(S#DB%?acWSZx1hb4^Mo~OesOpY9@s# z<8A_gIWQN4Xb=lqK2XA>>)I857jfHw)G?Rm zr{8U=K<$xyr9z5R0#bLi{uHBdlIG|vj#y^JjUg1$UfTq6hcM-58$VxE-yzn7)-No* zm3$9Pd*g^%+MBJP_~(`_x!5|?Gs8ka<8gWrORkcnF@jWam{l9r!lfJm$pV>xBnD;W z0?EEAeh@En$nC=qkNq%9kf5TEH!t_kHa$s+JPx{L7vFW+{2@-5JyrCu0+VmF=V3Ia z!f6PEV{{^gv$k~)AiLmTh-u(#0@a1sho2`k|1qbGTNUUiI^5WP4y_Qyw(7;!D%uaz zwilBiIjpw{h+)9)<+RzmyIvCJw%iXZ&U(W|lXpI!mtm@r1sz!G__NCvABh!D83zRv zkZc;O-U77`2E+oe$C$#7L{VXL90Vo#kuPV;C+$YDP}`2hP=(S}t7wA6XhO-D0HiW7))iTeBgRehul^_deT(hYj;64LwX)7B+)Ucvk}nqx z9hRhupOHRzY4M~Pe|1C^cGV3N=TFk(eA;aac!= z)^jXuSelR!&$dD~5~oa&6xsi|&mHCEXvm9A#%_%!il&fDr$zU!ok=C}oTu4BtMy*vgZ=T^gKt$->e>e*0Ftf6*q#cSdEtYZZRI<;O~Ta(L0(LHO+z z-_!*ZO^&;|*Lp+X*l;x|=_#8r*whxxy}oEiwC%JHdn(s<-T8zD^_>%betEV(&09P< z_7sYQR&L9pie-H$3{bXSA^3{m^yS13#P`!{^1TjC;8x_@tG``VckG(0eBR5GXM?hj zYq+OQ#Vtp>P~TYW`y!Gi6a=kiN0G7P*KaDZ;k`pzI}k5CxY^*n{km`3lHcyWU*y_~ zazYQFejpl>F+76Cp|!K9PJC&{CbtpX($rOtG|vkjU-9i&EuZ=@p!&lXG26p)AER<0 zvnYbXB=|>)(QhbdgL|dWc7-O^?%nQRo&ENd)T?~SX*`qOz~3{#%U)mQRN2<^3RbVU zc3r$aHKHNB;bO=MZb6~6dF6#&)udDoslJ@iFa!V)qRw@XnI`Zr@uNt#Xlq{-}Gpmw5b)U=wO7=fF{R_fzjWYlM literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000007.gdbtablx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000007.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..5ed1ca9acb224f26cbb87a4b3dbafe1271471552 GIT binary patch literal 4128 zcmeI$s|^BC5Jus{-d)}#=q-d2sDV0GfdPTXq9}#X2^B!-fN*XVn17N_Ofu@oDZog* zT5VizDh-_U(yqWmTMwQZI(TasLo}f%*@q>iht#xDERN literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.gdbindexes b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.gdbindexes new file mode 100644 index 0000000000000000000000000000000000000000..cc24e2a06b9b9cd5f6fe51035fb99fb73263853b GIT binary patch literal 116 zcmZY0I|_h600Yrc6cjAHl3&(sR?y!2pJE*gL9lo!5>f)4R%Q~HMj{InCz~Dg%DFHb V{rA@PeIEn{cK=Wbo?or+as&A53=;qV literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..c99336adb5db16106db7d62144923ba71a1b38b3 GIT binary patch literal 566 zcmaJ;yGjE=6g?Zw14R4;C3UjwBd$rKu8C}n#)NEwg-F2cqOq_DqNKDF3;)ByUyy)x zK!UZOpoon{P|wWlHWHi*bMDMNbI#nEd8qNC0ZFC=Ih=&T-Dtf`^eXP&imjW5ZUo_F zzSJvPj%nFpAs_Sy7sF?sO_8R}0SP31RN za!sqZ9*$!C;RwcKSFX>#)c(!W=HK<_G!@*|%Inknk4gQwxe=q}{WrzGEjXD5woqfk cWws%n6RAw%_2Kb-iN)_l?RxZfH(5~gFLv8qJ^%m! literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.gdbtablx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..a3af82aa62a81940b5bef5675e1bb499990740a4 GIT binary patch literal 37990 zcmeI$%L#-q5CG6oRy$hIzagj07ZNRrvLx| literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.spx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a00000009.spx new file mode 100644 index 0000000000000000000000000000000000000000..5fa796d466b1859b760d8a500afd3d9fc8111303 GIT binary patch literal 65566 zcmeIup%DNu3f)4R%Q~HMj{InCz~Dg%DFHb V{rA@PeIEn{cK=Wbo?or+as&A53=;qV literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..c99336adb5db16106db7d62144923ba71a1b38b3 GIT binary patch literal 566 zcmaJ;yGjE=6g?Zw14R4;C3UjwBd$rKu8C}n#)NEwg-F2cqOq_DqNKDF3;)ByUyy)x zK!UZOpoon{P|wWlHWHi*bMDMNbI#nEd8qNC0ZFC=Ih=&T-Dtf`^eXP&imjW5ZUo_F zzSJvPj%nFpAs_Sy7sF?sO_8R}0SP31RN za!sqZ9*$!C;RwcKSFX>#)c(!W=HK<_G!@*|%Inknk4gQwxe=q}{WrzGEjXD5woqfk cWws%n6RAw%_2Kb-iN)_l?RxZfH(5~gFLv8qJ^%m! literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.gdbtablx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..d4842ffc23d1d41f9374b4e50a0e78b172693d57 GIT binary patch literal 37990 zcmeI$yA6Xd5CG5vg^n$<2z91l6Bvbp$ub3UlW+ngBnsLndMDXFe+v6m);%JkT3bf5 z4SE6u2oNAZfB*pk1nvc%_5B$q2oNAZfB*pkKPS+=w`$;P%(`Ia)7+PK{n}){43`TZ z#ly>n1p)*J5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAn?xu)#-85+?TeF MIIrdQG5_rG1&m)1>i_@% literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.horizon b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.horizon new file mode 100644 index 0000000000000000000000000000000000000000..b64b92356a70378da21d5ffdecaaca5124025076 GIT binary patch literal 32 YcmZQ#0D;N_OdzTu3_>$hIzagj07ZNRrvLx| literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.spx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000a.spx new file mode 100644 index 0000000000000000000000000000000000000000..5fa796d466b1859b760d8a500afd3d9fc8111303 GIT binary patch literal 65566 zcmeIup%DNu3f)4R%Q~HMj{InCz~Dg%DFHb V{rA@PeIEn{cK=Wbo?or+as&A53=;qV literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..c99336adb5db16106db7d62144923ba71a1b38b3 GIT binary patch literal 566 zcmaJ;yGjE=6g?Zw14R4;C3UjwBd$rKu8C}n#)NEwg-F2cqOq_DqNKDF3;)ByUyy)x zK!UZOpoon{P|wWlHWHi*bMDMNbI#nEd8qNC0ZFC=Ih=&T-Dtf`^eXP&imjW5ZUo_F zzSJvPj%nFpAs_Sy7sF?sO_8R}0SP31RN za!sqZ9*$!C;RwcKSFX>#)c(!W=HK<_G!@*|%Inknk4gQwxe=q}{WrzGEjXD5woqfk cWws%n6RAw%_2Kb-iN)_l?RxZfH(5~gFLv8qJ^%m! literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.gdbtablx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..eee05bd71076531d442af3f15f43fb65db34b54f GIT binary patch literal 37990 zcmeI$!3l&g5CFhc&Yv$U2wXE_RVN5`Xgivy2bK_I7Lt(7XLmk<{X|5QSBdUA>;wo9 zAV7cs0RjXF5U4=l+g4D75(p3=K!CvO1lDahdzyY_E?6dmX`Gin|GW2;Fz0KGRAW(U zB0zuu0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5;&7Yj6x-qJWPeH-!2mFNF` GzvBQTPYwM5 literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.horizon b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.horizon new file mode 100644 index 0000000000000000000000000000000000000000..b64b92356a70378da21d5ffdecaaca5124025076 GIT binary patch literal 32 YcmZQ#0D;N_OdzTu3_>$hIzagj07ZNRrvLx| literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.spx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000b.spx new file mode 100644 index 0000000000000000000000000000000000000000..5fa796d466b1859b760d8a500afd3d9fc8111303 GIT binary patch literal 65566 zcmeIup%DNu3f)4R%Q~HMj{InCz~Dg%DFHb V{rA@PeIEn{cK=Wbo?or+as&A53=;qV literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..c99336adb5db16106db7d62144923ba71a1b38b3 GIT binary patch literal 566 zcmaJ;yGjE=6g?Zw14R4;C3UjwBd$rKu8C}n#)NEwg-F2cqOq_DqNKDF3;)ByUyy)x zK!UZOpoon{P|wWlHWHi*bMDMNbI#nEd8qNC0ZFC=Ih=&T-Dtf`^eXP&imjW5ZUo_F zzSJvPj%nFpAs_Sy7sF?sO_8R}0SP31RN za!sqZ9*$!C;RwcKSFX>#)c(!W=HK<_G!@*|%Inknk4gQwxe=q}{WrzGEjXD5woqfk cWws%n6RAw%_2Kb-iN)_l?RxZfH(5~gFLv8qJ^%m! literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.gdbtablx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..b83ce08a274d3517fcfd1c066e081bf9228a80b6 GIT binary patch literal 37990 zcmeI*u?>VE6aY}pc%`+iiQ8DXgBv)4gEi%Qd=THo OxoxU*UQRCiza#z|+YS)` literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.horizon b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.horizon new file mode 100644 index 0000000000000000000000000000000000000000..b64b92356a70378da21d5ffdecaaca5124025076 GIT binary patch literal 32 YcmZQ#0D;N_OdzTu3_>$hIzagj07ZNRrvLx| literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.spx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000c.spx new file mode 100644 index 0000000000000000000000000000000000000000..5fa796d466b1859b760d8a500afd3d9fc8111303 GIT binary patch literal 65566 zcmeIup%DNu3f)4R%Q~HMj{InCz~Dg%DFHb V{rA@PeIEn{cK=Wbo?or+as&A53=;qV literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..c99336adb5db16106db7d62144923ba71a1b38b3 GIT binary patch literal 566 zcmaJ;yGjE=6g?Zw14R4;C3UjwBd$rKu8C}n#)NEwg-F2cqOq_DqNKDF3;)ByUyy)x zK!UZOpoon{P|wWlHWHi*bMDMNbI#nEd8qNC0ZFC=Ih=&T-Dtf`^eXP&imjW5ZUo_F zzSJvPj%nFpAs_Sy7sF?sO_8R}0SP31RN za!sqZ9*$!C;RwcKSFX>#)c(!W=HK<_G!@*|%Inknk4gQwxe=q}{WrzGEjXD5woqfk cWws%n6RAw%_2Kb-iN)_l?RxZfH(5~gFLv8qJ^%m! literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.gdbtablx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..f343e6d9eb39e33e7edb1a3c4dae26d688aa3d6f GIT binary patch literal 37990 zcmeI$I|{-;5CG6gh-j0LMsk3?ND4vl3SPzwdM4e$2$nY6gg3BXmVtTQ&JYn@9T|tu zVkSU<0D(pVrEip2S^@+J5FkK+009C72oNCfr@;AnKjzchBm08c8JyR$jP=Kw`)ge4 zEC}pX;I!Am)J1>*0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!894fv)D9w!HIN Pma&h>yWjYpUt7EZx&#al literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.horizon b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.horizon new file mode 100644 index 0000000000000000000000000000000000000000..b64b92356a70378da21d5ffdecaaca5124025076 GIT binary patch literal 32 YcmZQ#0D;N_OdzTu3_>$hIzagj07ZNRrvLx| literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.spx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000d.spx new file mode 100644 index 0000000000000000000000000000000000000000..5fa796d466b1859b760d8a500afd3d9fc8111303 GIT binary patch literal 65566 zcmeIup%DNu3f)4R%Q~HMj{InCz~Dg%DFHb V{rA@PeIEn{cK=Wbo?or+as&A53=;qV literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000e.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000e.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..c99336adb5db16106db7d62144923ba71a1b38b3 GIT binary patch literal 566 zcmaJ;yGjE=6g?Zw14R4;C3UjwBd$rKu8C}n#)NEwg-F2cqOq_DqNKDF3;)ByUyy)x zK!UZOpoon{P|wWlHWHi*bMDMNbI#nEd8qNC0ZFC=Ih=&T-Dtf`^eXP&imjW5ZUo_F zzSJvPj%nFpAs_Sy7sF?sO_8R}0SP31RN za!sqZ9*$!C;RwcKSFX>#)c(!W=HK<_G!@*|%Inknk4gQwxe=q}{WrzGEjXD5woqfk cWws%n6RAw%_2Kb-iN)_l?RxZfH(5~gFLv8qJ^%m! literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000e.gdbtablx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000e.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..3a77f69dfcb1325bbc278c475b3bd6f26a01ef60 GIT binary patch literal 155936 zcmeI$%L&3j5CG8GMZrr>DZxg(Y5}Ro5(0u%q#7I2U6Mcw@L=3Gu>Z@me^^<9prciULc?$hIzagj07ZNRrvLx| literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000e.spx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000e.spx new file mode 100644 index 0000000000000000000000000000000000000000..5fa796d466b1859b760d8a500afd3d9fc8111303 GIT binary patch literal 65566 zcmeIup%DNu3f)4R%Q~HMj{InCz~Dg%DFHb V{rA@PeIEn{cK=Wbo?or+as&A53=;qV literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000f.gdbtable b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000f.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..e035332fb4d83d3d25f7581e1cf0deff6deab17e GIT binary patch literal 642 zcmb_ZyG{Z@6g`XbNR06lG}N%!2O=~oE@lx$2n(?=hD3IwSXdCEq14J)_#YPjf+4Xc zF~r(W&=?yFqMn&qEDW*o-t3$^GiT13J39*%PZ|(qDLxUG5rx|dut;=^g`Fi^Hx1qJ zgR^wLowKs0We1tG-|6=HkCcd!rp*8mL_L&HKng12`N@Y`zj|2zJ%1m^{Ohu_I=+1yRSs)w6O_Ea2L7?1NK~vVJN326Uas&tvAV7cs0RjXF5U5FD=@zf1k4vot2oNAZfB*pk1PBly zK!5-N0_OrNkGRfJ93en}009C72oNAZfB*pk1PBlyK!5-N0t5&UAVAZcuM>4@s_r|=!T3So<~rF5(p3=P`$uNzSFCB z^acnJs7m0ms%jN`S-{2YvXK!42oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0@o9mo}8-P?ho@O zA}zfr|GC-jj``9r?IMqEzxw%dp3jp1>E|Ev@mX&Fa^$Xt009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly mK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNA}2Z70z=llo3t{Lh8 literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000f.horizon b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000f.horizon new file mode 100644 index 0000000000000000000000000000000000000000..b64b92356a70378da21d5ffdecaaca5124025076 GIT binary patch literal 32 YcmZQ#0D;N_OdzTu3_>$hIzagj07ZNRrvLx| literal 0 HcmV?d00001 diff --git a/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000f.spx b/autotest/ogr/data/filegdb/objectid64/with_holes_8.gdb/a0000000f.spx new file mode 100644 index 0000000000000000000000000000000000000000..5fa796d466b1859b760d8a500afd3d9fc8111303 GIT binary patch literal 65566 zcmeIup%DNu3Reset(); } - virtual int GetNextRowSortedByFID() override; + virtual int64_t GetNextRowSortedByFID() override; - virtual int GetRowCount() override + virtual int64_t GetRowCount() override { return poTable->GetTotalRecordCount(); } - virtual int GetNextRowSortedByValue() override + virtual int64_t GetNextRowSortedByValue() override { return poParentIter->GetNextRowSortedByValue(); } @@ -129,8 +129,8 @@ class FileGDBTrivialIterator final : public FileGDBIterator return poParentIter->GetMaxValue(eOutType); } - virtual int GetMinMaxSumCount(double &dfMin, double &dfMax, double &dfSum, - int &nCount) override + virtual bool GetMinMaxSumCount(double &dfMin, double &dfMax, double &dfSum, + int &nCount) override { return poParentIter->GetMinMaxSumCount(dfMin, dfMax, dfSum, nCount); } @@ -144,8 +144,8 @@ class FileGDBNotIterator final : public FileGDBIterator { FileGDBIterator *poIterBase = nullptr; FileGDBTable *poTable = nullptr; - int iRow = 0; - int iNextRowBase = -1; + int64_t iRow = 0; + int64_t iNextRowBase = -1; int bNoHoles = 0; FileGDBNotIterator(const FileGDBNotIterator &) = delete; @@ -161,8 +161,8 @@ class FileGDBNotIterator final : public FileGDBIterator } virtual void Reset() override; - virtual int GetNextRowSortedByFID() override; - virtual int GetRowCount() override; + virtual int64_t GetNextRowSortedByFID() override; + virtual int64_t GetRowCount() override; }; /************************************************************************/ @@ -173,8 +173,8 @@ class FileGDBAndIterator final : public FileGDBIterator { FileGDBIterator *poIter1 = nullptr; FileGDBIterator *poIter2 = nullptr; - int iNextRow1 = -1; - int iNextRow2 = -1; + int64_t iNextRow1 = -1; + int64_t iNextRow2 = -1; bool m_bTakeOwnershipOfIterators = false; FileGDBAndIterator(const FileGDBAndIterator &) = delete; @@ -191,7 +191,7 @@ class FileGDBAndIterator final : public FileGDBIterator } virtual void Reset() override; - virtual int GetNextRowSortedByFID() override; + virtual int64_t GetNextRowSortedByFID() override; }; /************************************************************************/ @@ -203,8 +203,8 @@ class FileGDBOrIterator final : public FileGDBIterator FileGDBIterator *poIter1 = nullptr; FileGDBIterator *poIter2 = nullptr; int bIteratorAreExclusive = false; - int iNextRow1 = -1; - int iNextRow2 = -1; + int64_t iNextRow1 = -1; + int64_t iNextRow2 = -1; bool bHasJustReset = true; FileGDBOrIterator(const FileGDBOrIterator &) = delete; @@ -221,8 +221,8 @@ class FileGDBOrIterator final : public FileGDBIterator } virtual void Reset() override; - virtual int GetNextRowSortedByFID() override; - virtual int GetRowCount() override; + virtual int64_t GetNextRowSortedByFID() override; + virtual int64_t GetRowCount() override; }; /************************************************************************/ @@ -230,7 +230,9 @@ class FileGDBOrIterator final : public FileGDBIterator /************************************************************************/ constexpr int MAX_DEPTH = 3; -constexpr int FGDB_PAGE_SIZE = 4096; +constexpr int FGDB_PAGE_SIZE_V1 = 4096; +constexpr int FGDB_PAGE_SIZE_V2 = 65536; +constexpr int MAX_FGDB_PAGE_SIZE = FGDB_PAGE_SIZE_V2; class FileGDBIndexIteratorBase : virtual public FileGDBIterator { @@ -238,40 +240,67 @@ class FileGDBIndexIteratorBase : virtual public FileGDBIterator FileGDBTable *poParent = nullptr; bool bAscending = false; VSILFILE *fpCurIdx = nullptr; + + //! Version of .atx/.spx: 1 or 2 + GUInt32 m_nVersion = 0; + + // Number of pages of size m_nPageSize GUInt32 m_nPageCount = 0; + + //! Page size in bytes: 4096 for v1 format, 65536 for v2 + int m_nPageSize = 0; + + //! Maximum number of features or sub-pages referenced by a page. GUInt32 nMaxPerPages = 0; + + //! Size of ObjectID referenced in pages, in bytes. + // sizeof(uint32_t) for V1, sizeof(uint64_t) for V2 + GUInt32 m_nObjectIDSize = 0; + + //! Size of the indexed value, in bytes. GUInt32 m_nValueSize = 0; - GUInt32 nOffsetFirstValInPage = 0; - GUInt32 nValueCountInIdx = 0; + + //! Non-leaf page header size in bytes. 8 for V1, 12 for V2 + GUInt32 m_nNonLeafPageHeaderSize = 0; + + //! Leaf page header size in bytes. 12 for V1, 20 for V2 + GUInt32 m_nLeafPageHeaderSize = 0; + + //! Offset within a page at which the first indexed value is found. + GUInt32 m_nOffsetFirstValInPage = 0; + + //! Number of values referenced in the index. + GUInt64 m_nValueCountInIdx = 0; + GUInt32 nIndexDepth = 0; #ifdef DEBUG - int iLoadedPage[MAX_DEPTH]; + uint64_t iLoadedPage[MAX_DEPTH]; #endif int iFirstPageIdx[MAX_DEPTH]; int iLastPageIdx[MAX_DEPTH]; int iCurPageIdx[MAX_DEPTH]; GUInt32 nSubPagesCount[MAX_DEPTH]; - GUInt32 nLastPageAccessed[MAX_DEPTH]; + uint64_t nLastPageAccessed[MAX_DEPTH]; int iCurFeatureInPage = -1; int nFeaturesInPage = 0; bool bEOF = false; - GByte abyPage[MAX_DEPTH][FGDB_PAGE_SIZE]; - GByte abyPageFeature[FGDB_PAGE_SIZE]; + GByte abyPage[MAX_DEPTH][MAX_FGDB_PAGE_SIZE]; + GByte abyPageFeature[MAX_FGDB_PAGE_SIZE]; - typedef lru11::Cache> CacheType; + typedef lru11::Cache> CacheType; std::array m_oCachePage{ {CacheType{2, 0}, CacheType{2, 0}, CacheType{2, 0}}}; CacheType m_oCacheFeaturePage{2, 0}; bool ReadTrailer(const std::string &osFilename); - int ReadPageNumber(int iLevel); - int LoadNextPage(int iLevel); - virtual bool FindPages(int iLevel, int nPage) = 0; - int LoadNextFeaturePage(); + uint64_t ReadPageNumber(int iLevel); + bool LoadNextPage(int iLevel); + virtual bool FindPages(int iLevel, uint64_t nPage) = 0; + bool LoadNextFeaturePage(); FileGDBIndexIteratorBase(FileGDBTable *poParent, int bAscending); @@ -307,7 +336,7 @@ class FileGDBIndexIterator final : public FileGDBIndexIteratorBase int iSorted = 0; int nSortedCount = -1; - int *panSortedRows = nullptr; + int64_t *panSortedRows = nullptr; int SortRows(); GUInt16 asUTF16Str[MAX_CAR_COUNT_INDEXED_STR]; @@ -321,8 +350,8 @@ class FileGDBIndexIterator final : public FileGDBIndexIteratorBase const OGRField *GetMinMaxValue(OGRField *psField, int &eOutType, int bIsMin); - virtual bool FindPages(int iLevel, int nPage) override; - int GetNextRow(); + virtual bool FindPages(int iLevel, uint64_t nPage) override; + int64_t GetNextRow(); FileGDBIndexIterator(FileGDBTable *poParent, int bAscending); int SetConstraint(int nFieldIdx, FileGDBSQLOp op, @@ -343,19 +372,19 @@ class FileGDBIndexIterator final : public FileGDBIndexIteratorBase OGRFieldType eOGRFieldType, const OGRField *psValue); - virtual int GetNextRowSortedByFID() override; - virtual int GetRowCount() override; + virtual int64_t GetNextRowSortedByFID() override; + virtual int64_t GetRowCount() override; virtual void Reset() override; - virtual int GetNextRowSortedByValue() override + virtual int64_t GetNextRowSortedByValue() override { return GetNextRow(); } virtual const OGRField *GetMinValue(int &eOutType) override; virtual const OGRField *GetMaxValue(int &eOutType) override; - virtual int GetMinMaxSumCount(double &dfMin, double &dfMax, double &dfSum, - int &nCount) override; + virtual bool GetMinMaxSumCount(double &dfMin, double &dfMax, double &dfSum, + int &nCount) override; }; /************************************************************************/ @@ -384,7 +413,7 @@ const OGRField *FileGDBIterator::GetMaxValue(int &eOutType) /* GetNextRowSortedByValue() */ /************************************************************************/ -int FileGDBIterator::GetNextRowSortedByValue() +int64_t FileGDBIterator::GetNextRowSortedByValue() { PrintError(); return -1; @@ -394,15 +423,15 @@ int FileGDBIterator::GetNextRowSortedByValue() /* GetMinMaxSumCount() */ /************************************************************************/ -int FileGDBIterator::GetMinMaxSumCount(double &dfMin, double &dfMax, - double &dfSum, int &nCount) +bool FileGDBIterator::GetMinMaxSumCount(double &dfMin, double &dfMax, + double &dfSum, int &nCount) { PrintError(); dfMin = 0.0; dfMax = 0.0; dfSum = 0.0; nCount = 0; - return FALSE; + return false; } /************************************************************************/ @@ -475,10 +504,10 @@ FileGDBIterator *FileGDBIterator::BuildOr(FileGDBIterator *poIter1, /* GetRowCount() */ /************************************************************************/ -int FileGDBIterator::GetRowCount() +int64_t FileGDBIterator::GetRowCount() { Reset(); - int nCount = 0; + int64_t nCount = 0; while (GetNextRowSortedByFID() >= 0) nCount++; Reset(); @@ -498,7 +527,7 @@ FileGDBTrivialIterator::FileGDBTrivialIterator(FileGDBIterator *poParentIterIn) /* GetNextRowSortedByFID() */ /************************************************************************/ -int FileGDBTrivialIterator::GetNextRowSortedByFID() +int64_t FileGDBTrivialIterator::GetNextRowSortedByFID() { if (iRow < poTable->GetTotalRecordCount()) return iRow++; @@ -541,7 +570,7 @@ void FileGDBNotIterator::Reset() /* GetNextRowSortedByFID() */ /************************************************************************/ -int FileGDBNotIterator::GetNextRowSortedByFID() +int64_t FileGDBNotIterator::GetNextRowSortedByFID() { if (iNextRowBase < 0) { @@ -579,7 +608,7 @@ int FileGDBNotIterator::GetNextRowSortedByFID() /* GetRowCount() */ /************************************************************************/ -int FileGDBNotIterator::GetRowCount() +int64_t FileGDBNotIterator::GetRowCount() { return poTable->GetValidRecordCount() - poIterBase->GetRowCount(); } @@ -626,7 +655,7 @@ void FileGDBAndIterator::Reset() /* GetNextRowSortedByFID() */ /************************************************************************/ -int FileGDBAndIterator::GetNextRowSortedByFID() +int64_t FileGDBAndIterator::GetNextRowSortedByFID() { if (iNextRow1 == iNextRow2) { @@ -697,7 +726,7 @@ void FileGDBOrIterator::Reset() /* GetNextRowSortedByFID() */ /************************************************************************/ -int FileGDBOrIterator::GetNextRowSortedByFID() +int64_t FileGDBOrIterator::GetNextRowSortedByFID() { if (bHasJustReset) { @@ -708,19 +737,19 @@ int FileGDBOrIterator::GetNextRowSortedByFID() if (iNextRow1 < 0) { - int iVal = iNextRow2; + auto iVal = iNextRow2; iNextRow2 = poIter2->GetNextRowSortedByFID(); return iVal; } if (iNextRow2 < 0 || iNextRow1 < iNextRow2) { - int iVal = iNextRow1; + auto iVal = iNextRow1; iNextRow1 = poIter1->GetNextRowSortedByFID(); return iVal; } if (iNextRow2 < iNextRow1) { - int iVal = iNextRow2; + auto iVal = iNextRow2; iNextRow2 = poIter2->GetNextRowSortedByFID(); return iVal; } @@ -728,7 +757,7 @@ int FileGDBOrIterator::GetNextRowSortedByFID() if (bIteratorAreExclusive) PrintError(); - int iVal = iNextRow1; + auto iVal = iNextRow1; iNextRow1 = poIter1->GetNextRowSortedByFID(); iNextRow2 = poIter2->GetNextRowSortedByFID(); return iVal; @@ -738,7 +767,7 @@ int FileGDBOrIterator::GetNextRowSortedByFID() /* GetRowCount() */ /************************************************************************/ -int FileGDBOrIterator::GetRowCount() +int64_t FileGDBOrIterator::GetRowCount() { if (bIteratorAreExclusive) return poIter1->GetRowCount() + poIter2->GetRowCount(); @@ -790,61 +819,109 @@ bool FileGDBIndexIteratorBase::ReadTrailer(const std::string &osFilename) VSIFSeekL(fpCurIdx, 0, SEEK_END); vsi_l_offset nFileSize = VSIFTellL(fpCurIdx); - returnErrorIf(nFileSize < FGDB_PAGE_SIZE + 22); - - VSIFSeekL(fpCurIdx, nFileSize - 22, SEEK_SET); - GByte abyTrailer[22]; - returnErrorIf(VSIFReadL(abyTrailer, 22, 1, fpCurIdx) != 1); + constexpr int V1_TRAILER_SIZE = 22; + constexpr int V2_TRAILER_SIZE = 30; + returnErrorIf(nFileSize < V1_TRAILER_SIZE); + + GByte abyTrailer[V2_TRAILER_SIZE]; + VSIFSeekL(fpCurIdx, nFileSize - sizeof(uint32_t), SEEK_SET); + returnErrorIf(VSIFReadL(abyTrailer, sizeof(uint32_t), 1, fpCurIdx) != 1); + m_nVersion = GetUInt32(abyTrailer, 0); + returnErrorIf(m_nVersion != 1 && m_nVersion != 2); + + if (m_nVersion == 1) + { + m_nPageSize = FGDB_PAGE_SIZE_V1; + VSIFSeekL(fpCurIdx, nFileSize - V1_TRAILER_SIZE, SEEK_SET); + returnErrorIf(VSIFReadL(abyTrailer, V1_TRAILER_SIZE, 1, fpCurIdx) != 1); + + m_nPageCount = + static_cast((nFileSize - V1_TRAILER_SIZE) / m_nPageSize); + + m_nValueSize = abyTrailer[0]; + m_nObjectIDSize = static_cast(sizeof(uint32_t)); + m_nNonLeafPageHeaderSize = 8; + m_nLeafPageHeaderSize = 12; + + nMaxPerPages = (m_nPageSize - m_nLeafPageHeaderSize) / + (m_nObjectIDSize + m_nValueSize); + m_nOffsetFirstValInPage = + m_nLeafPageHeaderSize + nMaxPerPages * m_nObjectIDSize; + + GUInt32 nMagic1 = GetUInt32(abyTrailer + 2, 0); + returnErrorIf(nMagic1 != 1); + + nIndexDepth = GetUInt32(abyTrailer + 6, 0); + /* CPLDebug("OpenFileGDB", "nIndexDepth = %u", nIndexDepth); */ + returnErrorIf(!(nIndexDepth >= 1 && nIndexDepth <= MAX_DEPTH + 1)); + + m_nValueCountInIdx = GetUInt32(abyTrailer + 10, 0); + /* CPLDebug("OpenFileGDB", "m_nValueCountInIdx = %u", m_nValueCountInIdx); */ + /* negative like in sample_clcV15_esri_v10.gdb/a00000005.FDO_UUID.atx */ + if ((m_nValueCountInIdx >> (8 * sizeof(m_nValueCountInIdx) - 1)) != 0) + { + CPLDebugOnly("OpenFileGDB", "m_nValueCountInIdx=%u", + static_cast(m_nValueCountInIdx)); + return false; + } - m_nPageCount = static_cast((nFileSize - 22) / FGDB_PAGE_SIZE); + /* QGIS_TEST_101.gdb/a00000006.FDO_UUID.atx */ + /* or .spx file from test dataset https://github.com/OSGeo/gdal/issues/5888 + */ + if (m_nValueCountInIdx == 0 && nIndexDepth == 1) + { + VSIFSeekL(fpCurIdx, 4, SEEK_SET); + GByte abyBuffer[4]; + returnErrorIf(VSIFReadL(abyBuffer, 4, 1, fpCurIdx) != 1); + m_nValueCountInIdx = GetUInt32(abyBuffer, 0); + } + /* PreNIS.gdb/a00000006.FDO_UUID.atx has depth 2 and the value of */ + /* m_nValueCountInIdx is 11 which is not the number of non-null values */ + else if (m_nValueCountInIdx < nMaxPerPages && nIndexDepth > 1) + { + if (m_nValueCountInIdx > 0 && poParent->IsFileGDBV9() && + strstr(osFilename.c_str(), "blk_key_index.atx")) + { + // m_nValueCountInIdx not reliable in FileGDB v9 .blk_key_index.atx + // but index seems to be OK + return true; + } - m_nValueSize = abyTrailer[0]; + CPLDebugOnly( + "OpenFileGDB", + "m_nValueCountInIdx=%u < nMaxPerPages=%u, nIndexDepth=%u", + static_cast(m_nValueCountInIdx), nMaxPerPages, + nIndexDepth); + return false; + } + } + else + { + m_nPageSize = FGDB_PAGE_SIZE_V2; + VSIFSeekL(fpCurIdx, nFileSize - V2_TRAILER_SIZE, SEEK_SET); + returnErrorIf(VSIFReadL(abyTrailer, V2_TRAILER_SIZE, 1, fpCurIdx) != 1); - nMaxPerPages = (FGDB_PAGE_SIZE - 12) / (4 + m_nValueSize); - nOffsetFirstValInPage = 12 + nMaxPerPages * 4; + m_nPageCount = + static_cast((nFileSize - V2_TRAILER_SIZE) / m_nPageSize); - GUInt32 nMagic1 = GetUInt32(abyTrailer + 2, 0); - returnErrorIf(nMagic1 != 1); + m_nValueSize = abyTrailer[0]; + m_nObjectIDSize = static_cast(sizeof(uint64_t)); + m_nNonLeafPageHeaderSize = 12; + m_nLeafPageHeaderSize = 20; - nIndexDepth = GetUInt32(abyTrailer + 6, 0); - /* CPLDebug("OpenFileGDB", "nIndexDepth = %u", nIndexDepth); */ - returnErrorIf(!(nIndexDepth >= 1 && nIndexDepth <= MAX_DEPTH + 1)); + nMaxPerPages = (m_nPageSize - m_nLeafPageHeaderSize) / + (m_nObjectIDSize + m_nValueSize); + m_nOffsetFirstValInPage = + m_nLeafPageHeaderSize + nMaxPerPages * m_nObjectIDSize; - nValueCountInIdx = GetUInt32(abyTrailer + 10, 0); - /* CPLDebug("OpenFileGDB", "nValueCountInIdx = %u", nValueCountInIdx); */ - /* negative like in sample_clcV15_esri_v10.gdb/a00000005.FDO_UUID.atx */ - if ((nValueCountInIdx >> (8 * sizeof(nValueCountInIdx) - 1)) != 0) - { - CPLDebugOnly("OpenFileGDB", "nValueCountInIdx=%u", nValueCountInIdx); - return false; - } + GUInt32 nMagic1 = GetUInt32(abyTrailer + 2, 0); + returnErrorIf(nMagic1 != 1); - /* QGIS_TEST_101.gdb/a00000006.FDO_UUID.atx */ - /* or .spx file from test dataset https://github.com/OSGeo/gdal/issues/5888 - */ - if (nValueCountInIdx == 0 && nIndexDepth == 1) - { - VSIFSeekL(fpCurIdx, 4, SEEK_SET); - GByte abyBuffer[4]; - returnErrorIf(VSIFReadL(abyBuffer, 4, 1, fpCurIdx) != 1); - nValueCountInIdx = GetUInt32(abyBuffer, 0); - } - /* PreNIS.gdb/a00000006.FDO_UUID.atx has depth 2 and the value of */ - /* nValueCountInIdx is 11 which is not the number of non-null values */ - else if (nValueCountInIdx < nMaxPerPages && nIndexDepth > 1) - { - if (nValueCountInIdx > 0 && poParent->IsFileGDBV9() && - strstr(osFilename.c_str(), "blk_key_index.atx")) - { - // nValueCountInIdx not reliable in FileGDB v9 .blk_key_index.atx - // but index seems to be OK - return true; - } + nIndexDepth = GetUInt32(abyTrailer + 6, 0); + /* CPLDebug("OpenFileGDB", "nIndexDepth = %u", nIndexDepth); */ + returnErrorIf(!(nIndexDepth >= 1 && nIndexDepth <= MAX_DEPTH + 1)); - CPLDebugOnly("OpenFileGDB", - "nValueCountInIdx=%u < nMaxPerPages=%u, nIndexDepth=%u", - nValueCountInIdx, nMaxPerPages, nIndexDepth); - return false; + m_nValueCountInIdx = GetUInt64(abyTrailer + 10, 0); } return true; @@ -970,15 +1047,40 @@ int FileGDBIndex::GetMaxWidthInBytes(const FileGDBTable *poTable) const VSIFSeekL(fpCurIdx, 0, SEEK_END); vsi_l_offset nFileSize = VSIFTellL(fpCurIdx); - if (nFileSize < FGDB_PAGE_SIZE + 22) + + constexpr int V1_TRAILER_SIZE = 22; + constexpr int V2_TRAILER_SIZE = 30; + + if (nFileSize < FGDB_PAGE_SIZE_V1 + V1_TRAILER_SIZE) + { + VSIFCloseL(fpCurIdx); + return 0; + } + + GByte abyTrailer[V2_TRAILER_SIZE]; + VSIFSeekL(fpCurIdx, nFileSize - sizeof(uint32_t), SEEK_SET); + if (VSIFReadL(abyTrailer, sizeof(uint32_t), 1, fpCurIdx) != 1) + { + VSIFCloseL(fpCurIdx); + return 0; + } + const auto nVersion = GetUInt32(abyTrailer, 0); + if (nVersion != 1 && nVersion != 2) { VSIFCloseL(fpCurIdx); return 0; } - VSIFSeekL(fpCurIdx, nFileSize - 22, SEEK_SET); - GByte abyTrailer[22]; - if (VSIFReadL(abyTrailer, 22, 1, fpCurIdx) != 1) + const int nTrailerSize = nVersion == 1 ? V1_TRAILER_SIZE : V2_TRAILER_SIZE; + + if (nVersion == 2 && nFileSize < FGDB_PAGE_SIZE_V2 + V2_TRAILER_SIZE) + { + VSIFCloseL(fpCurIdx); + return 0; + } + + VSIFSeekL(fpCurIdx, nFileSize - nTrailerSize, SEEK_SET); + if (VSIFReadL(abyTrailer, nTrailerSize, 1, fpCurIdx) != 1) { VSIFCloseL(fpCurIdx); return 0; @@ -1022,8 +1124,8 @@ int FileGDBIndexIterator::SetConstraint(int nFieldIdx, FileGDBSQLOp op, if (!ReadTrailer(pszAtxName)) return FALSE; - returnErrorIf(nValueCountInIdx > - static_cast(poParent->GetValidRecordCount())); + returnErrorIf(m_nValueCountInIdx > + static_cast(poParent->GetValidRecordCount())); switch (eFieldType) { @@ -1156,7 +1258,7 @@ int FileGDBIndexIterator::SetConstraint(int nFieldIdx, FileGDBSQLOp op, break; } - if (nValueCountInIdx > 0) + if (m_nValueCountInIdx > 0) { if (nIndexDepth == 1) { @@ -1208,21 +1310,21 @@ static int FileGDBUTF16StrCompare(const GUInt16 *pasFirst, /* FindPages() */ /************************************************************************/ -bool FileGDBIndexIterator::FindPages(int iLevel, int nPage) +bool FileGDBIndexIterator::FindPages(int iLevel, uint64_t nPage) { const bool errorRetValue = false; - VSIFSeekL(fpCurIdx, static_cast(nPage - 1) * FGDB_PAGE_SIZE, + VSIFSeekL(fpCurIdx, static_cast(nPage - 1) * m_nPageSize, SEEK_SET); #ifdef DEBUG iLoadedPage[iLevel] = nPage; #endif - returnErrorIf(VSIFReadL(abyPage[iLevel], FGDB_PAGE_SIZE, 1, fpCurIdx) != 1); + returnErrorIf(VSIFReadL(abyPage[iLevel], m_nPageSize, 1, fpCurIdx) != 1); - nSubPagesCount[iLevel] = GetUInt32(abyPage[iLevel] + 4, 0); + nSubPagesCount[iLevel] = GetUInt32(abyPage[iLevel] + m_nObjectIDSize, 0); returnErrorIf(nSubPagesCount[iLevel] == 0 || nSubPagesCount[iLevel] > nMaxPerPages); if (nIndexDepth == 2) - returnErrorIf(nValueCountInIdx > + returnErrorIf(m_nValueCountInIdx > nMaxPerPages * (nSubPagesCount[0] + 1)); if (eOp == FGSO_ISNOTNULL) @@ -1250,7 +1352,7 @@ bool FileGDBIndexIterator::FindPages(int iLevel, int nPage) case FGFT_INT16: { GInt16 nVal = - GetInt16(abyPage[iLevel] + nOffsetFirstValInPage, i); + GetInt16(abyPage[iLevel] + m_nOffsetFirstValInPage, i); #ifdef DEBUG_INDEX_CONSISTENCY returnErrorIf(i > 0 && nVal < nLastMax); nLastMax = nVal; @@ -1262,7 +1364,7 @@ bool FileGDBIndexIterator::FindPages(int iLevel, int nPage) case FGFT_INT32: { GInt32 nVal = - GetInt32(abyPage[iLevel] + nOffsetFirstValInPage, i); + GetInt32(abyPage[iLevel] + m_nOffsetFirstValInPage, i); #ifdef DEBUG_INDEX_CONSISTENCY returnErrorIf(i > 0 && nVal < nLastMax); nLastMax = nVal; @@ -1274,7 +1376,7 @@ bool FileGDBIndexIterator::FindPages(int iLevel, int nPage) case FGFT_INT64: { int64_t nVal = - GetInt64(abyPage[iLevel] + nOffsetFirstValInPage, i); + GetInt64(abyPage[iLevel] + m_nOffsetFirstValInPage, i); #ifdef DEBUG_INDEX_CONSISTENCY returnErrorIf(i > 0 && nVal < nLastMax); nLastMax = nVal; @@ -1286,7 +1388,7 @@ bool FileGDBIndexIterator::FindPages(int iLevel, int nPage) case FGFT_FLOAT32: { float fVal = - GetFloat32(abyPage[iLevel] + nOffsetFirstValInPage, i); + GetFloat32(abyPage[iLevel] + m_nOffsetFirstValInPage, i); #ifdef DEBUG_INDEX_CONSISTENCY returnErrorIf(i > 0 && fVal < dfLastMax); dfLastMax = fVal; @@ -1298,7 +1400,7 @@ bool FileGDBIndexIterator::FindPages(int iLevel, int nPage) case FGFT_FLOAT64: { const double dfVal = - GetFloat64(abyPage[iLevel] + nOffsetFirstValInPage, i); + GetFloat64(abyPage[iLevel] + m_nOffsetFirstValInPage, i); #ifdef DEBUG_INDEX_CONSISTENCY returnErrorIf(i > 0 && dfVal < dfLastMax); dfLastMax = dfVal; @@ -1312,7 +1414,7 @@ bool FileGDBIndexIterator::FindPages(int iLevel, int nPage) case FGFT_TIME: { const double dfVal = - GetFloat64(abyPage[iLevel] + nOffsetFirstValInPage, i); + GetFloat64(abyPage[iLevel] + m_nOffsetFirstValInPage, i); #ifdef DEBUG_INDEX_CONSISTENCY returnErrorIf(i > 0 && dfVal < dfLastMax); dfLastMax = dfVal; @@ -1332,7 +1434,7 @@ bool FileGDBIndexIterator::FindPages(int iLevel, int nPage) GUInt16 asMax[MAX_CAR_COUNT_INDEXED_STR]; pasMax = asMax; memcpy(asMax, - abyPage[iLevel] + nOffsetFirstValInPage + + abyPage[iLevel] + m_nOffsetFirstValInPage + nStrLen * sizeof(GUInt16) * i, nStrLen * sizeof(GUInt16)); for (int j = 0; j < nStrLen; j++) @@ -1350,7 +1452,7 @@ bool FileGDBIndexIterator::FindPages(int iLevel, int nPage) case FGFT_GLOBALID: { const char *psNonzMaxUUID = reinterpret_cast( - abyPage[iLevel] + nOffsetFirstValInPage + + abyPage[iLevel] + m_nOffsetFirstValInPage + UUID_LEN_AS_STRING * i); #ifdef DEBUG_INDEX_CONSISTENCY returnErrorIf(i > 0 && memcmp(psNonzMaxUUID, szLastMaxUUID, @@ -1458,14 +1560,14 @@ bool FileGDBIndexIterator::FindPages(int iLevel, int nPage) void FileGDBIndexIteratorBase::Reset() { iCurPageIdx[0] = (bAscending) ? iFirstPageIdx[0] - 1 : iLastPageIdx[0] + 1; - memset(iFirstPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(int)); - memset(iLastPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(int)); - memset(iCurPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(int)); - memset(nLastPageAccessed, 0, MAX_DEPTH * sizeof(int)); + memset(iFirstPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(iFirstPageIdx[0])); + memset(iLastPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(iLastPageIdx[0])); + memset(iCurPageIdx + 1, 0xFF, (MAX_DEPTH - 1) * sizeof(iCurPageIdx[0])); + memset(nLastPageAccessed, 0, MAX_DEPTH * sizeof(nLastPageAccessed[0])); iCurFeatureInPage = 0; nFeaturesInPage = 0; - bEOF = (nValueCountInIdx == 0); + bEOF = (m_nValueCountInIdx == 0); } /************************************************************************/ @@ -1483,15 +1585,33 @@ void FileGDBIndexIterator::Reset() /* ReadPageNumber() */ /************************************************************************/ -int FileGDBIndexIteratorBase::ReadPageNumber(int iLevel) +uint64_t FileGDBIndexIteratorBase::ReadPageNumber(int iLevel) { const int errorRetValue = 0; - GUInt32 nPage = GetUInt32(abyPage[iLevel] + 8, iCurPageIdx[iLevel]); - if (nPage == nLastPageAccessed[iLevel]) + uint64_t nPage; + if (m_nVersion == 1) { - if (!LoadNextPage(iLevel)) - return 0; - nPage = GetUInt32(abyPage[iLevel] + 8, iCurPageIdx[iLevel]); + nPage = GetUInt32(abyPage[iLevel] + m_nNonLeafPageHeaderSize, + iCurPageIdx[iLevel]); + if (nPage == nLastPageAccessed[iLevel]) + { + if (!LoadNextPage(iLevel)) + return 0; + nPage = GetUInt32(abyPage[iLevel] + m_nNonLeafPageHeaderSize, + iCurPageIdx[iLevel]); + } + } + else + { + nPage = GetUInt64(abyPage[iLevel] + m_nNonLeafPageHeaderSize, + iCurPageIdx[iLevel]); + if (nPage == nLastPageAccessed[iLevel]) + { + if (!LoadNextPage(iLevel)) + return 0; + nPage = GetUInt64(abyPage[iLevel] + m_nNonLeafPageHeaderSize, + iCurPageIdx[iLevel]); + } } nLastPageAccessed[iLevel] = nPage; returnErrorIf(nPage < 2); @@ -1502,16 +1622,16 @@ int FileGDBIndexIteratorBase::ReadPageNumber(int iLevel) /* LoadNextPage() */ /************************************************************************/ -int FileGDBIndexIteratorBase::LoadNextPage(int iLevel) +bool FileGDBIndexIteratorBase::LoadNextPage(int iLevel) { - const int errorRetValue = FALSE; + const bool errorRetValue = false; if ((bAscending && iCurPageIdx[iLevel] == iLastPageIdx[iLevel]) || (!bAscending && iCurPageIdx[iLevel] == iFirstPageIdx[iLevel])) { if (iLevel == 0 || !LoadNextPage(iLevel - 1)) - return FALSE; + return false; - GUInt32 nPage = ReadPageNumber(iLevel - 1); + const auto nPage = ReadPageNumber(iLevel - 1); returnErrorIf(!FindPages(iLevel, nPage)); iCurPageIdx[iLevel] = @@ -1525,23 +1645,23 @@ int FileGDBIndexIteratorBase::LoadNextPage(int iLevel) iCurPageIdx[iLevel]--; } - return TRUE; + return true; } /************************************************************************/ /* LoadNextFeaturePage() */ /************************************************************************/ -int FileGDBIndexIteratorBase::LoadNextFeaturePage() +bool FileGDBIndexIteratorBase::LoadNextFeaturePage() { - const int errorRetValue = FALSE; - GUInt32 nPage; + const bool errorRetValue = false; + GUInt64 nPage; if (nIndexDepth == 1) { if (iCurPageIdx[0] == iLastPageIdx[0]) { - return FALSE; + return false; } if (bAscending) iCurPageIdx[0]++; @@ -1553,7 +1673,7 @@ int FileGDBIndexIteratorBase::LoadNextFeaturePage() { if (!LoadNextPage(nIndexDepth - 2)) { - return FALSE; + return false; } nPage = ReadPageNumber(nIndexDepth - 2); returnErrorIf(nPage < 2); @@ -1563,7 +1683,7 @@ int FileGDBIndexIteratorBase::LoadNextFeaturePage() m_oCacheFeaturePage.getPtr(nPage); if (cachedPagePtr) { - memcpy(abyPageFeature, cachedPagePtr->data(), FGDB_PAGE_SIZE); + memcpy(abyPageFeature, cachedPagePtr->data(), m_nPageSize); } else { @@ -1574,37 +1694,32 @@ int FileGDBIndexIteratorBase::LoadNextFeaturePage() cachedPage.clear(); } - VSIFSeekL(fpCurIdx, - static_cast(nPage - 1) * FGDB_PAGE_SIZE, + VSIFSeekL(fpCurIdx, static_cast(nPage - 1) * m_nPageSize, SEEK_SET); #ifdef DEBUG iLoadedPage[nIndexDepth - 1] = nPage; #endif - returnErrorIf(VSIFReadL(abyPageFeature, FGDB_PAGE_SIZE, 1, fpCurIdx) != - 1); + returnErrorIf(VSIFReadL(abyPageFeature, m_nPageSize, 1, fpCurIdx) != 1); cachedPage.insert(cachedPage.end(), abyPageFeature, - abyPageFeature + FGDB_PAGE_SIZE); + abyPageFeature + m_nPageSize); m_oCacheFeaturePage.insert(nPage, std::move(cachedPage)); } - GUInt32 nFeatures = GetUInt32(abyPageFeature + 4, 0); + const GUInt32 nFeatures = GetUInt32(abyPageFeature + m_nObjectIDSize, 0); returnErrorIf(nFeatures > nMaxPerPages); nFeaturesInPage = static_cast(nFeatures); iCurFeatureInPage = (bAscending) ? 0 : nFeaturesInPage - 1; - if (nFeatures == 0) - return FALSE; - - return TRUE; + return nFeatures != 0; } /************************************************************************/ /* GetNextRow() */ /************************************************************************/ -int FileGDBIndexIterator::GetNextRow() +int64_t FileGDBIndexIterator::GetNextRow() { - const int errorRetValue = -1; + const int64_t errorRetValue = -1; if (bEOF) return -1; @@ -1632,7 +1747,7 @@ int FileGDBIndexIterator::GetNextRow() case FGFT_INT16: { const GInt16 nVal = - GetInt16(abyPageFeature + nOffsetFirstValInPage, + GetInt16(abyPageFeature + m_nOffsetFirstValInPage, iCurFeatureInPage); nComp = COMPARE(sValue.Integer, nVal); break; @@ -1641,7 +1756,7 @@ int FileGDBIndexIterator::GetNextRow() case FGFT_INT32: { const GInt32 nVal = - GetInt32(abyPageFeature + nOffsetFirstValInPage, + GetInt32(abyPageFeature + m_nOffsetFirstValInPage, iCurFeatureInPage); nComp = COMPARE(sValue.Integer, nVal); break; @@ -1650,7 +1765,7 @@ int FileGDBIndexIterator::GetNextRow() case FGFT_FLOAT32: { const float fVal = - GetFloat32(abyPageFeature + nOffsetFirstValInPage, + GetFloat32(abyPageFeature + m_nOffsetFirstValInPage, iCurFeatureInPage); nComp = COMPARE(sValue.Real, fVal); break; @@ -1659,7 +1774,7 @@ int FileGDBIndexIterator::GetNextRow() case FGFT_FLOAT64: { const double dfVal = - GetFloat64(abyPageFeature + nOffsetFirstValInPage, + GetFloat64(abyPageFeature + m_nOffsetFirstValInPage, iCurFeatureInPage); nComp = COMPARE(sValue.Real, dfVal); break; @@ -1671,7 +1786,7 @@ int FileGDBIndexIterator::GetNextRow() case FGFT_DATETIME_WITH_OFFSET: { const double dfVal = - GetFloat64(abyPageFeature + nOffsetFirstValInPage, + GetFloat64(abyPageFeature + m_nOffsetFirstValInPage, iCurFeatureInPage); if (sValue.Real + 1e-10 < dfVal) nComp = -1; @@ -1686,7 +1801,7 @@ int FileGDBIndexIterator::GetNextRow() { GUInt16 asVal[MAX_CAR_COUNT_INDEXED_STR]; memcpy(asVal, - abyPageFeature + nOffsetFirstValInPage + + abyPageFeature + m_nOffsetFirstValInPage + nStrLen * 2 * iCurFeatureInPage, nStrLen * 2); for (int j = 0; j < nStrLen; j++) @@ -1699,7 +1814,7 @@ int FileGDBIndexIterator::GetNextRow() case FGFT_GLOBALID: { nComp = memcmp(szUUID, - abyPageFeature + nOffsetFirstValInPage + + abyPageFeature + m_nOffsetFirstValInPage + UUID_LEN_AS_STRING * iCurFeatureInPage, UUID_LEN_AS_STRING); break; @@ -1708,7 +1823,7 @@ int FileGDBIndexIterator::GetNextRow() case FGFT_INT64: { const int64_t nVal = - GetInt64(abyPageFeature + nOffsetFirstValInPage, + GetInt64(abyPageFeature + m_nOffsetFirstValInPage, iCurFeatureInPage); nComp = COMPARE(sValue.Integer64, nVal); break; @@ -1767,17 +1882,21 @@ int FileGDBIndexIterator::GetNextRow() if (bMatch) { - const GUInt32 nFID = - GetUInt32(abyPageFeature + 12, iCurFeatureInPage); + const GUInt64 nFID = + m_nVersion == 1 + ? GetUInt32(abyPageFeature + m_nLeafPageHeaderSize, + iCurFeatureInPage) + : GetUInt64(abyPageFeature + m_nLeafPageHeaderSize, + iCurFeatureInPage); if (bAscending) iCurFeatureInPage++; else iCurFeatureInPage--; returnErrorAndCleanupIf( - nFID < 1 || nFID > static_cast( + nFID < 1 || nFID > static_cast( poParent->GetTotalRecordCount()), bEOF = true); - return static_cast(nFID - 1); + return static_cast(nFID - 1); } else { @@ -1801,14 +1920,15 @@ int FileGDBIndexIterator::SortRows() Reset(); while (true) { - int nRow = GetNextRow(); + int64_t nRow = GetNextRow(); if (nRow < 0) break; if (nSortedCount == nSortedAlloc) { int nNewSortedAlloc = 4 * nSortedAlloc / 3 + 16; - int *panNewSortedRows = static_cast(VSI_REALLOC_VERBOSE( - panSortedRows, sizeof(int) * nNewSortedAlloc)); + int64_t *panNewSortedRows = + static_cast(VSI_REALLOC_VERBOSE( + panSortedRows, sizeof(int64_t) * nNewSortedAlloc)); if (panNewSortedRows == nullptr) { nSortedCount = 0; @@ -1822,8 +1942,8 @@ int FileGDBIndexIterator::SortRows() if (nSortedCount == 0) return FALSE; std::sort(panSortedRows, panSortedRows + nSortedCount); -#ifdef nValueCountInIdx_reliable - if (eOp == FGSO_ISNOTNULL && (int)nValueCountInIdx != nSortedCount) +#ifdef m_nValueCountInIdx_reliable + if (eOp == FGSO_ISNOTNULL && (int64_t)m_nValueCountInIdx != nSortedCount) PrintError(); #endif return TRUE; @@ -1833,7 +1953,7 @@ int FileGDBIndexIterator::SortRows() /* GetNextRowSortedByFID() */ /************************************************************************/ -int FileGDBIndexIterator::GetNextRowSortedByFID() +int64_t FileGDBIndexIterator::GetNextRowSortedByFID() { if (eOp == FGSO_EQ) return GetNextRow(); @@ -1857,21 +1977,21 @@ int FileGDBIndexIterator::GetNextRowSortedByFID() /* GetRowCount() */ /************************************************************************/ -int FileGDBIndexIterator::GetRowCount() +int64_t FileGDBIndexIterator::GetRowCount() { - // The nValueCountInIdx value has been found to be unreliable when the index + // The m_nValueCountInIdx value has been found to be unreliable when the index // is built as features are inserted (and when they are not in increasing // order) (with FileGDB SDK 1.3) So disable this optimization as there's no // fast way to know if the value is reliable or not. -#ifdef nValueCountInIdx_reliable +#ifdef m_nValueCountInIdx_reliable if (eOp == FGSO_ISNOTNULL) - return (int)nValueCountInIdx; + return (int64_t)m_nValueCountInIdx; #endif if (nSortedCount >= 0) return nSortedCount; - int nRowCount = 0; + int64_t nRowCount = 0; bool bSaveAscending = bAscending; bAscending = true; /* for a tiny bit of more efficiency */ Reset(); @@ -1891,38 +2011,52 @@ const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField, { const OGRField *errorRetValue = nullptr; eOutType = -1; - if (nValueCountInIdx == 0) + if (m_nValueCountInIdx == 0) return nullptr; - GByte l_abyPage[FGDB_PAGE_SIZE]; - GUInt32 nPage = 1; + std::vector l_abyPageV; + try + { + l_abyPageV.resize(m_nPageSize); + } + catch (const std::exception &) + { + return nullptr; + } + GByte *l_abyPage = l_abyPageV.data(); + uint64_t nPage = 1; for (GUInt32 iLevel = 0; iLevel < nIndexDepth - 1; iLevel++) { - VSIFSeekL(fpCurIdx, - static_cast(nPage - 1) * FGDB_PAGE_SIZE, + VSIFSeekL(fpCurIdx, static_cast(nPage - 1) * m_nPageSize, SEEK_SET); #ifdef DEBUG iLoadedPage[iLevel] = nPage; #endif - returnErrorIf(VSIFReadL(l_abyPage, FGDB_PAGE_SIZE, 1, fpCurIdx) != 1); - GUInt32 l_nSubPagesCount = GetUInt32(l_abyPage + 4, 0); + returnErrorIf(VSIFReadL(l_abyPage, m_nPageSize, 1, fpCurIdx) != 1); + GUInt32 l_nSubPagesCount = GetUInt32(l_abyPage + m_nObjectIDSize, 0); returnErrorIf(l_nSubPagesCount == 0 || l_nSubPagesCount > nMaxPerPages); - if (bIsMin) - nPage = GetUInt32(l_abyPage + 8, 0); + if (m_nVersion == 1) + { + nPage = GetUInt32(l_abyPage + m_nNonLeafPageHeaderSize, + bIsMin ? 0 : l_nSubPagesCount); + } else - nPage = GetUInt32(l_abyPage + 8, l_nSubPagesCount); + { + nPage = GetUInt64(l_abyPage + m_nNonLeafPageHeaderSize, + bIsMin ? 0 : l_nSubPagesCount); + } returnErrorIf(nPage < 2); } - VSIFSeekL(fpCurIdx, static_cast(nPage - 1) * FGDB_PAGE_SIZE, + VSIFSeekL(fpCurIdx, static_cast(nPage - 1) * m_nPageSize, SEEK_SET); #ifdef DEBUG iLoadedPage[nIndexDepth - 1] = nPage; #endif - returnErrorIf(VSIFReadL(l_abyPage, FGDB_PAGE_SIZE, 1, fpCurIdx) != 1); + returnErrorIf(VSIFReadL(l_abyPage, m_nPageSize, 1, fpCurIdx) != 1); - GUInt32 nFeatures = GetUInt32(l_abyPage + 4, 0); + GUInt32 nFeatures = GetUInt32(l_abyPage + m_nObjectIDSize, 0); returnErrorIf(nFeatures < 1 || nFeatures > nMaxPerPages); int iFeature = (bIsMin) ? 0 : nFeatures - 1; @@ -1932,7 +2066,7 @@ const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField, case FGFT_INT16: { const GInt16 nVal = - GetInt16(l_abyPage + nOffsetFirstValInPage, iFeature); + GetInt16(l_abyPage + m_nOffsetFirstValInPage, iFeature); psField->Integer = nVal; eOutType = OFTInteger; return psField; @@ -1941,7 +2075,7 @@ const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField, case FGFT_INT32: { const GInt32 nVal = - GetInt32(l_abyPage + nOffsetFirstValInPage, iFeature); + GetInt32(l_abyPage + m_nOffsetFirstValInPage, iFeature); psField->Integer = nVal; eOutType = OFTInteger; return psField; @@ -1950,7 +2084,7 @@ const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField, case FGFT_FLOAT32: { const float fVal = - GetFloat32(l_abyPage + nOffsetFirstValInPage, iFeature); + GetFloat32(l_abyPage + m_nOffsetFirstValInPage, iFeature); psField->Real = fVal; eOutType = OFTReal; return psField; @@ -1959,7 +2093,7 @@ const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField, case FGFT_FLOAT64: { const double dfVal = - GetFloat64(l_abyPage + nOffsetFirstValInPage, iFeature); + GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature); psField->Real = dfVal; eOutType = OFTReal; return psField; @@ -1969,7 +2103,7 @@ const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField, case FGFT_DATETIME_WITH_OFFSET: { const double dfVal = - GetFloat64(l_abyPage + nOffsetFirstValInPage, iFeature); + GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature); FileGDBDoubleDateToOGRDate(dfVal, false, psField); eOutType = OFTDateTime; return psField; @@ -1978,7 +2112,7 @@ const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField, case FGFT_DATE: { const double dfVal = - GetFloat64(l_abyPage + nOffsetFirstValInPage, iFeature); + GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature); FileGDBDoubleDateToOGRDate(dfVal, false, psField); eOutType = OFTDate; return psField; @@ -1987,7 +2121,7 @@ const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField, case FGFT_TIME: { const double dfVal = - GetFloat64(l_abyPage + nOffsetFirstValInPage, iFeature); + GetFloat64(l_abyPage + m_nOffsetFirstValInPage, iFeature); FileGDBDoubleTimeToOGRTime(dfVal, psField); eOutType = OFTTime; return psField; @@ -1999,7 +2133,7 @@ const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField, for (int j = 0; j < nStrLen; j++) { GUInt16 nCh = - GetUInt16(l_abyPage + nOffsetFirstValInPage + + GetUInt16(l_abyPage + m_nOffsetFirstValInPage + nStrLen * sizeof(GUInt16) * iFeature, j); awsVal[j] = nCh; @@ -2021,7 +2155,7 @@ const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField, case FGFT_GLOBALID: { memcpy(psField->String, - l_abyPage + nOffsetFirstValInPage + + l_abyPage + m_nOffsetFirstValInPage + UUID_LEN_AS_STRING * iFeature, UUID_LEN_AS_STRING); psField->String[UUID_LEN_AS_STRING] = 0; @@ -2032,7 +2166,7 @@ const OGRField *FileGDBIndexIterator::GetMinMaxValue(OGRField *psField, case FGFT_INT64: { const int64_t nVal = - GetInt64(l_abyPage + nOffsetFirstValInPage, iFeature); + GetInt64(l_abyPage + m_nOffsetFirstValInPage, iFeature); psField->Integer64 = nVal; eOutType = OFTInteger64; return psField; @@ -2140,7 +2274,7 @@ void FileGDBIndexIterator::GetMinMaxSumCount(double &dfMin, double &dfMax, } } - dfVal = Getter::GetAsDouble(abyPageFeature + nOffsetFirstValInPage, + dfVal = Getter::GetAsDouble(abyPageFeature + m_nOffsetFirstValInPage, iCurFeatureInPage); dfLocalSum += dfVal; @@ -2155,10 +2289,10 @@ void FileGDBIndexIterator::GetMinMaxSumCount(double &dfMin, double &dfMax, dfMax = dfVal; } -int FileGDBIndexIterator::GetMinMaxSumCount(double &dfMin, double &dfMax, - double &dfSum, int &nCount) +bool FileGDBIndexIterator::GetMinMaxSumCount(double &dfMin, double &dfMax, + double &dfSum, int &nCount) { - const int errorRetValue = FALSE; + const bool errorRetValue = false; dfMin = 0.0; dfMax = 0.0; dfSum = 0.0; @@ -2213,7 +2347,7 @@ int FileGDBIndexIterator::GetMinMaxSumCount(double &dfMin, double &dfMax, bAscending = bSaveAscending; Reset(); - return TRUE; + return true; } /************************************************************************/ @@ -2225,7 +2359,7 @@ class FileGDBSpatialIndexIteratorImpl final : public FileGDBIndexIteratorBase, { OGREnvelope m_sFilterEnvelope; bool m_bHasBuiltSetFID = false; - std::vector m_oFIDVector{}; + std::vector m_oFIDVector{}; size_t m_nVectorIdx = 0; int m_nGridNo = 0; GInt64 m_nMinVal = 0; @@ -2233,7 +2367,7 @@ class FileGDBSpatialIndexIteratorImpl final : public FileGDBIndexIteratorBase, GInt32 m_nCurX = 0; GInt32 m_nMaxX = 0; - virtual bool FindPages(int iLevel, int nPage) override; + virtual bool FindPages(int iLevel, uint64_t nPage) override; int GetNextRow(); bool ReadNewXRange(); bool ResetInternal(); @@ -2252,7 +2386,7 @@ class FileGDBSpatialIndexIteratorImpl final : public FileGDBIndexIteratorBase, return poParent; } // avoid MSVC C4250 inherits via dominance warning - virtual int GetNextRowSortedByFID() override; + virtual int64_t GetNextRowSortedByFID() override; virtual void Reset() override; virtual bool SetEnvelope(const OGREnvelope &sFilterEnvelope) override; @@ -2426,7 +2560,7 @@ bool FileGDBSpatialIndexIteratorImpl::ReadNewXRange() } const bool errorRetValue = false; - if (nValueCountInIdx > 0) + if (m_nValueCountInIdx > 0) { if (nIndexDepth == 1) { @@ -2500,7 +2634,7 @@ static bool FindMinMaxIdx(const GByte *pBaseAddr, const int nVals, /* FindPages() */ /************************************************************************/ -bool FileGDBSpatialIndexIteratorImpl::FindPages(int iLevel, int nPage) +bool FileGDBSpatialIndexIteratorImpl::FindPages(int iLevel, uint64_t nPage) { const bool errorRetValue = false; @@ -2510,7 +2644,7 @@ bool FileGDBSpatialIndexIteratorImpl::FindPages(int iLevel, int nPage) m_oCachePage[iLevel].getPtr(nPage); if (cachedPagePtr) { - memcpy(abyPage[iLevel], cachedPagePtr->data(), FGDB_PAGE_SIZE); + memcpy(abyPage[iLevel], cachedPagePtr->data(), m_nPageSize); } else { @@ -2521,35 +2655,46 @@ bool FileGDBSpatialIndexIteratorImpl::FindPages(int iLevel, int nPage) cachedPage.clear(); } - VSIFSeekL(fpCurIdx, - static_cast(nPage - 1) * FGDB_PAGE_SIZE, + VSIFSeekL(fpCurIdx, static_cast(nPage - 1) * m_nPageSize, SEEK_SET); #ifdef DEBUG iLoadedPage[iLevel] = nPage; #endif - returnErrorIf(VSIFReadL(abyPage[iLevel], FGDB_PAGE_SIZE, 1, fpCurIdx) != + returnErrorIf(VSIFReadL(abyPage[iLevel], m_nPageSize, 1, fpCurIdx) != 1); cachedPage.insert(cachedPage.end(), abyPage[iLevel], - abyPage[iLevel] + FGDB_PAGE_SIZE); + abyPage[iLevel] + m_nPageSize); m_oCachePage[iLevel].insert(nPage, std::move(cachedPage)); } - nSubPagesCount[iLevel] = GetUInt32(abyPage[iLevel] + 4, 0); + nSubPagesCount[iLevel] = GetUInt32(abyPage[iLevel] + m_nObjectIDSize, 0); returnErrorIf(nSubPagesCount[iLevel] == 0 || nSubPagesCount[iLevel] > nMaxPerPages); - if (GetInt64(abyPage[iLevel] + nOffsetFirstValInPage, 0) > m_nMaxVal) + if (GetInt64(abyPage[iLevel] + m_nOffsetFirstValInPage, 0) > m_nMaxVal) { iFirstPageIdx[iLevel] = 0; - // nSubPagesCount[iLevel] == 1 && GetUInt32(abyPage[iLevel] + 12, 0) == + // nSubPagesCount[iLevel] == 1 && GetUInt32(abyPage[iLevel] + m_nLeafPageHeaderSize, 0) == // 0 should only happen on non-nominal cases where one forces the depth // of the index to be greater than needed. - iLastPageIdx[iLevel] = (nSubPagesCount[iLevel] == 1 && - GetUInt32(abyPage[iLevel] + 12, 0) == 0) - ? 0 - : 1; + if (m_nVersion == 1) + { + iLastPageIdx[iLevel] = + (nSubPagesCount[iLevel] == 1 && + GetUInt32(abyPage[iLevel] + m_nLeafPageHeaderSize, 0) == 0) + ? 0 + : 1; + } + else + { + iLastPageIdx[iLevel] = + (nSubPagesCount[iLevel] == 1 && + GetUInt64(abyPage[iLevel] + m_nLeafPageHeaderSize, 0) == 0) + ? 0 + : 1; + } } - else if (!FindMinMaxIdx(abyPage[iLevel] + nOffsetFirstValInPage, + else if (!FindMinMaxIdx(abyPage[iLevel] + m_nOffsetFirstValInPage, static_cast(nSubPagesCount[iLevel]), m_nMinVal, m_nMaxVal, iFirstPageIdx[iLevel], iLastPageIdx[iLevel])) @@ -2582,7 +2727,7 @@ int FileGDBSpatialIndexIteratorImpl::GetNextRow() int nMinIdx = 0; int nMaxIdx = 0; if (!LoadNextFeaturePage() || - !FindMinMaxIdx(abyPageFeature + nOffsetFirstValInPage, + !FindMinMaxIdx(abyPageFeature + m_nOffsetFirstValInPage, nFeaturesInPage, m_nMinVal, m_nMaxVal, nMinIdx, nMaxIdx) || nMinIdx > nMaxIdx) @@ -2623,17 +2768,21 @@ int FileGDBSpatialIndexIteratorImpl::GetNextRow() } #ifdef DEBUG - const GInt64 nVal = - GetInt64(abyPageFeature + nOffsetFirstValInPage, iCurFeatureInPage); + const GInt64 nVal = GetInt64(abyPageFeature + m_nOffsetFirstValInPage, + iCurFeatureInPage); CPL_IGNORE_RET_VAL(nVal); CPLAssert(nVal >= m_nMinVal && nVal <= m_nMaxVal); #endif - const GUInt32 nFID = GetUInt32(abyPageFeature + 12, iCurFeatureInPage); + const GUInt64 nFID = + m_nVersion == 1 ? GetUInt32(abyPageFeature + m_nLeafPageHeaderSize, + iCurFeatureInPage) + : GetUInt64(abyPageFeature + m_nLeafPageHeaderSize, + iCurFeatureInPage); iCurFeatureInPage++; returnErrorAndCleanupIf( nFID < 1 || - nFID > static_cast(poParent->GetTotalRecordCount()), + nFID > static_cast(poParent->GetTotalRecordCount()), bEOF = true); return static_cast(nFID - 1); } @@ -2673,7 +2822,7 @@ void FileGDBSpatialIndexIteratorImpl::Reset() /* GetNextRowSortedByFID() */ /************************************************************************/ -int FileGDBSpatialIndexIteratorImpl::GetNextRowSortedByFID() +int64_t FileGDBSpatialIndexIteratorImpl::GetNextRowSortedByFID() { if (m_nVectorIdx == 0) { @@ -2684,7 +2833,7 @@ int FileGDBSpatialIndexIteratorImpl::GetNextRowSortedByFID() // than using a unordered_set (or set) while (true) { - const int nFID = GetNextRow(); + const auto nFID = GetNextRow(); if (nFID < 0) break; m_oFIDVector.push_back(nFID); @@ -2694,16 +2843,16 @@ int FileGDBSpatialIndexIteratorImpl::GetNextRowSortedByFID() if (m_oFIDVector.empty()) return -1; - const int nFID = m_oFIDVector[m_nVectorIdx]; + const auto nFID = m_oFIDVector[m_nVectorIdx]; ++m_nVectorIdx; return nFID; } - const int nLastFID = m_oFIDVector[m_nVectorIdx - 1]; + const auto nLastFID = m_oFIDVector[m_nVectorIdx - 1]; while (m_nVectorIdx < m_oFIDVector.size()) { // Do not return consecutive identical FID - const int nFID = m_oFIDVector[m_nVectorIdx]; + const auto nFID = m_oFIDVector[m_nVectorIdx]; ++m_nVectorIdx; if (nFID == nLastFID) { diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbindex_write.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbindex_write.cpp index fb477682c525..b2561ed88b1d 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbindex_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbindex_write.cpp @@ -305,7 +305,7 @@ void FileGDBTable::ComputeOptimalSpatialIndexGridResolution() { // For point, use the density as the grid resolution int nValid = 0; - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) { iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -349,7 +349,7 @@ void FileGDBTable::ComputeOptimalSpatialIndexGridResolution() int64_t nValid = 0; auto poGeomConverter = std::unique_ptr( FileGDBOGRGeometryConverter::BuildConverter(poGeomField)); - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) { iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -403,7 +403,7 @@ void FileGDBTable::ComputeOptimalSpatialIndexGridResolution() // of all geometries double dfMaxSize = 0; OGREnvelope sEnvelope; - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) { iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -847,7 +847,7 @@ bool FileGDBTable::CreateSpatialIndex() } auto poGeomConverter = std::unique_ptr( FileGDBOGRGeometryConverter::BuildConverter(poGeomField)); - typedef std::pair ValueOIDPair; + typedef std::pair ValueOIDPair; std::vector asValues; const double dfGridStep = m_adfSpatialIndexGridResolution.back(); @@ -1159,11 +1159,11 @@ bool FileGDBTable::CreateSpatialIndex() }; std::vector aSetValues; - int iLastReported = 0; + int64_t iLastReported = 0; const auto nReportIncrement = m_nTotalRecordCount / 20; try { - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) { if (m_nTotalRecordCount > 10000 && (iCurFeat + 1 == m_nTotalRecordCount || @@ -1343,9 +1343,10 @@ bool FileGDBTable::CreateAttributeIndex(const FileGDBIndex *poIndex) const auto eFieldType = m_apoFields[iField]->GetType(); if (eFieldType == FGFT_INT16) { - typedef std::pair ValueOIDPair; + typedef std::pair ValueOIDPair; std::vector asValues; - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; + ++iCurFeat) { iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -1368,9 +1369,10 @@ bool FileGDBTable::CreateAttributeIndex(const FileGDBIndex *poIndex) } else if (eFieldType == FGFT_INT32) { - typedef std::pair ValueOIDPair; + typedef std::pair ValueOIDPair; std::vector asValues; - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; + ++iCurFeat) { iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -1393,9 +1395,10 @@ bool FileGDBTable::CreateAttributeIndex(const FileGDBIndex *poIndex) } else if (eFieldType == FGFT_INT64) { - typedef std::pair ValueOIDPair; + typedef std::pair ValueOIDPair; std::vector asValues; - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; + ++iCurFeat) { iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -1418,9 +1421,10 @@ bool FileGDBTable::CreateAttributeIndex(const FileGDBIndex *poIndex) } else if (eFieldType == FGFT_FLOAT32) { - typedef std::pair ValueOIDPair; + typedef std::pair ValueOIDPair; std::vector asValues; - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; + ++iCurFeat) { iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -1444,11 +1448,12 @@ bool FileGDBTable::CreateAttributeIndex(const FileGDBIndex *poIndex) eFieldType == FGFT_DATE || eFieldType == FGFT_TIME || eFieldType == FGFT_DATETIME_WITH_OFFSET) { - typedef std::pair ValueOIDPair; + typedef std::pair ValueOIDPair; std::vector asValues; // Hack to force reading DateTime as double m_apoFields[iField]->m_bReadAsDouble = true; - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; + ++iCurFeat) { iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -1471,13 +1476,14 @@ bool FileGDBTable::CreateAttributeIndex(const FileGDBIndex *poIndex) } else if (eFieldType == FGFT_STRING) { - typedef std::pair, int> ValueOIDPair; + typedef std::pair, int64_t> ValueOIDPair; std::vector asValues; bRet = true; const bool bIsLower = STARTS_WITH_CI(poIndex->GetExpression().c_str(), "LOWER("); int maxStrSize = 0; - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; + ++iCurFeat) { iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp index a4a7a235891c..89f07f9cb451 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp @@ -30,6 +30,7 @@ #include "filegdbtable.h" #include +#include #include #include #include @@ -569,45 +570,55 @@ bool FileGDBTable::GuessFeatureLocations() } } - int nInvalidRecords = 0; - while (nOffset < m_nFileSize) + int64_t nInvalidRecords = 0; + try { - GUInt32 nSize; - int bDeletedRecord; - if (!IsLikelyFeatureAtOffset(nOffset, &nSize, &bDeletedRecord)) - { - nOffset++; - } - else + while (nOffset < m_nFileSize) { - /*CPLDebug("OpenFileGDB", "Feature found at offset %d (size = %d)", - nOffset, nSize);*/ - if (bDeletedRecord) + GUInt32 nSize; + int bDeletedRecord; + if (!IsLikelyFeatureAtOffset(nOffset, &nSize, &bDeletedRecord)) + { + nOffset++; + } + else { - if (bReportDeletedFeatures) + /*CPLDebug("OpenFileGDB", "Feature found at offset %d (size = %d)", + nOffset, nSize);*/ + if (bDeletedRecord) { - m_bHasDeletedFeaturesListed = TRUE; - m_anFeatureOffsets.push_back(MARK_DELETED(nOffset)); + if (bReportDeletedFeatures) + { + m_bHasDeletedFeaturesListed = TRUE; + m_anFeatureOffsets.push_back(MARK_DELETED(nOffset)); + } + else + { + nInvalidRecords++; + m_anFeatureOffsets.push_back(0); + } } else - { - nInvalidRecords++; - m_anFeatureOffsets.push_back(0); - } + m_anFeatureOffsets.push_back(nOffset); + nOffset += nSize; } - else - m_anFeatureOffsets.push_back(nOffset); - nOffset += nSize; } } - m_nTotalRecordCount = static_cast(m_anFeatureOffsets.size()); + catch (const std::bad_alloc &) + { + CPLError(CE_Failure, CPLE_OutOfMemory, + "Out of memory in FileGDBTable::GuessFeatureLocations()"); + return false; + } + m_nTotalRecordCount = static_cast(m_anFeatureOffsets.size()); if (m_nTotalRecordCount - nInvalidRecords > m_nValidRecordCount) { if (!m_bHasDeletedFeaturesListed) { CPLError(CE_Warning, CPLE_AppDefined, - "More features found (%d) than declared number of valid " - "features (%d). " + "More features found (%" PRId64 + ") than declared number of valid " + "features ((%" PRId64 "). " "So deleted features will likely be reported.", m_nTotalRecordCount - nInvalidRecords, m_nValidRecordCount); @@ -619,16 +630,26 @@ bool FileGDBTable::GuessFeatureLocations() } /************************************************************************/ -/* ReadTableXHeader() */ +/* ReadTableXHeaderV3() */ /************************************************************************/ -int FileGDBTable::ReadTableXHeader() +bool FileGDBTable::ReadTableXHeaderV3() { - const int errorRetValue = FALSE; + const bool errorRetValue = false; GByte abyHeader[16]; // Read .gdbtablx file header returnErrorIf(VSIFReadL(abyHeader, 16, 1, m_fpTableX) != 1); + + const int nGDBTablxVersion = GetUInt32(abyHeader, 0); + if (nGDBTablxVersion != static_cast(m_eGDBTableVersion)) + { + CPLError(CE_Failure, CPLE_AppDefined, + ".gdbtablx version is %d whereas it should be %d", + nGDBTablxVersion, static_cast(m_eGDBTableVersion)); + return false; + } + m_n1024BlocksPresent = GetUInt32(abyHeader + 4, 0); m_nTotalRecordCount = GetInt32(abyHeader + 8, 0); @@ -697,7 +718,100 @@ int FileGDBTable::ReadTableXHeader() returnErrorIf(nCountBlocks != m_n1024BlocksPresent); } } - return TRUE; + return true; +} + +/************************************************************************/ +/* ReadTableXHeaderV4() */ +/************************************************************************/ + +bool FileGDBTable::ReadTableXHeaderV4() +{ + const bool errorRetValue = false; + GByte abyHeader[16]; + + // Read .gdbtablx file header + returnErrorIf(VSIFReadL(abyHeader, 16, 1, m_fpTableX) != 1); + + const int nGDBTablxVersion = GetUInt32(abyHeader, 0); + if (nGDBTablxVersion != static_cast(m_eGDBTableVersion)) + { + CPLError(CE_Failure, CPLE_AppDefined, + ".gdbtablx version is %d whereas it should be %d", + nGDBTablxVersion, static_cast(m_eGDBTableVersion)); + return false; + } + + m_n1024BlocksPresent = GetUInt64(abyHeader + 4, 0); + + m_nTablxOffsetSize = GetUInt32(abyHeader + 12, 0); + returnErrorIf(m_nTablxOffsetSize < 4 || m_nTablxOffsetSize > 6); + + m_nOffsetTableXTrailer = + 16 + m_nTablxOffsetSize * 1024 * + static_cast(m_n1024BlocksPresent); + if (m_n1024BlocksPresent != 0) + { + GByte abyTrailer[12]; + + VSIFSeekL(m_fpTableX, m_nOffsetTableXTrailer, SEEK_SET); + returnErrorIf(VSIFReadL(abyTrailer, 12, 1, m_fpTableX) != 1); + + m_nTotalRecordCount = GetUInt64(abyTrailer, 0); + + // Cf https://github.com/rouault/dump_gdbtable/wiki/FGDB-Spec#trailing-section-16-bytes--variable-number- + // for all below magic numbers and byte sequences + GUInt32 nSizeBitmapSection = GetUInt32(abyTrailer + 8, 0); + if (nSizeBitmapSection == 0) + { + // no bitmap. Fine + } + else if (nSizeBitmapSection == 22 + 32768 + 52 && + m_nTotalRecordCount <= 32768 * 1024 * 8) + { + try + { + std::vector abyBitmapSection(nSizeBitmapSection); + returnErrorIf(VSIFReadL(abyBitmapSection.data(), + abyBitmapSection.size(), 1, + m_fpTableX) != 1); + if (memcmp(abyBitmapSection.data(), "\x01\x00\x01\x00\x00\x00", + 6) == 0 && + memcmp(abyBitmapSection.data() + 22 + 32768, + "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + 12) == 0) + { + m_abyTablXBlockMap.insert( + m_abyTablXBlockMap.end(), abyBitmapSection.data() + 22, + abyBitmapSection.data() + 22 + 32768); + } + else + { + m_bReliableObjectID = false; + } + } + catch (const std::exception &e) + { + CPLError(CE_Failure, CPLE_OutOfMemory, + "Cannot allocate m_abyTablXBlockMap: %s", e.what()); + return false; + } + } + else + { + m_bReliableObjectID = false; + } + if (!m_bReliableObjectID) + { + m_nTotalRecordCount = 1024 * m_n1024BlocksPresent; + CPLError(CE_Warning, CPLE_AppDefined, + "Due to partial reverse engineering of the format, " + "ObjectIDs will not be accurate and attribute and spatial " + "indices cannot be used on %s", + m_osFilenameWithLayerName.c_str()); + } + } + return true; } /************************************************************************/ @@ -713,7 +827,7 @@ bool FileGDBTable::Open(const char *pszFilename, bool bUpdate, m_bUpdate = bUpdate; m_osFilename = pszFilename; - CPLString m_osFilenameWithLayerName(m_osFilename); + m_osFilenameWithLayerName = m_osFilename; if (pszLayerName) m_osFilenameWithLayerName += CPLSPrintf(" (layer %s)", pszLayerName); @@ -725,11 +839,44 @@ bool FileGDBTable::Open(const char *pszFilename, bool bUpdate, return false; } - // Read .gdtable file header + // Read .gdbtable file header GByte abyHeader[40]; returnErrorIf(VSIFReadL(abyHeader, 40, 1, m_fpTable) != 1); - m_nValidRecordCount = GetInt32(abyHeader + 4, 0); - returnErrorIf(m_nValidRecordCount < 0); + + int nGDBTableVersion = GetInt32(abyHeader, 0); + if (nGDBTableVersion == 3) + { + m_eGDBTableVersion = GDBTableVersion::V3; + } + else if (nGDBTableVersion == 4) + { + m_eGDBTableVersion = GDBTableVersion::V4; + if (m_bUpdate) + { + CPLError(CE_Failure, CPLE_NotSupported, + "Version 4 of the FileGeodatabase format is not supported " + "for update."); + return false; + } + } + else + { + CPLError(CE_Failure, CPLE_NotSupported, + "Version %u of the FileGeodatabase format is not supported.", + nGDBTableVersion); + return false; + } + + if (m_eGDBTableVersion == GDBTableVersion::V3) + { + m_nValidRecordCount = GetInt32(abyHeader + 4, 0); + returnErrorIf(m_nValidRecordCount < 0); + } + else + { + m_nValidRecordCount = GetInt64(abyHeader + 16, 0); + returnErrorIf(m_nValidRecordCount < 0); + } m_nHeaderBufferMaxSize = GetInt32(abyHeader + 8, 0); @@ -765,7 +912,11 @@ bool FileGDBTable::Open(const char *pszFilename, bool bUpdate, returnErrorIf(m_fpTableX == nullptr); } } - else if (!ReadTableXHeader()) + else if (m_eGDBTableVersion == GDBTableVersion::V3 && + !ReadTableXHeaderV3()) + return false; + else if (m_eGDBTableVersion == GDBTableVersion::V4 && + !ReadTableXHeaderV4()) return false; } @@ -778,8 +929,8 @@ bool FileGDBTable::Open(const char *pszFilename, bool bUpdate, { /* Potentially unsafe. See #5842 */ CPLDebug("OpenFileGDB", - "%s: nTotalRecordCount (was %d) forced to " - "nValidRecordCount=%d", + "%s: nTotalRecordCount (was %" PRId64 ") forced to " + "nValidRecordCount=%" PRId64, m_osFilenameWithLayerName.c_str(), m_nTotalRecordCount, m_nValidRecordCount); m_nTotalRecordCount = m_nValidRecordCount; @@ -789,8 +940,10 @@ bool FileGDBTable::Open(const char *pszFilename, bool bUpdate, /* By default err on the safe side */ CPLError( CE_Warning, CPLE_AppDefined, - "File %s declares %d valid records, but %s declares " - "only %d total records. Using that later value for safety " + "File %s declares %" PRId64 + " valid records, but %s declares " + "only %" PRId64 + " total records. Using that later value for safety " "(this possibly ignoring features). " "You can also try setting OPENFILEGDB_IGNORE_GDBTABLX=YES " "to " @@ -809,7 +962,8 @@ bool FileGDBTable::Open(const char *pszFilename, bool bUpdate, else if (m_nTotalRecordCount != m_nValidRecordCount) { CPLDebug("OpenFileGDB", - "%s: nTotalRecordCount=%d nValidRecordCount=%d", + "%s: nTotalRecordCount=%" PRId64 + " nValidRecordCount=%" PRId64, pszFilename, m_nTotalRecordCount, m_nValidRecordCount); } #endif @@ -836,18 +990,19 @@ bool FileGDBTable::Open(const char *pszFilename, bool bUpdate, returnErrorIf(VSIFReadL(abyHeader, 14, 1, m_fpTable) != 1); m_nFieldDescLength = GetUInt32(abyHeader, 0); - const auto nVersion = GetUInt32(abyHeader + 4, 0); - // nVersion == 6 is used in table arcgis_pro_32_types.gdb/a0000000b.gdbtable (big_int) + const auto nSecondaryHeaderVersion = GetUInt32(abyHeader + 4, 0); + // nSecondaryHeaderVersion == 6 is used in table arcgis_pro_32_types.gdb/a0000000b.gdbtable (big_int) // Not sure why... - if (m_bUpdate && nVersion != 4 && nVersion != 6) // FileGDB v10 + if (m_bUpdate && nSecondaryHeaderVersion != 4 && + nSecondaryHeaderVersion != 6) // FileGDB v10 { CPLError(CE_Failure, CPLE_NotSupported, - "Version %u of the FileGeodatabase format is not supported " - "for update.", - nVersion); + "Version %u of the secondary header of the FileGeodatabase " + "format is not supported for update.", + nSecondaryHeaderVersion); return false; } - m_bIsV9 = (nVersion == 3); + m_bIsV9 = (nSecondaryHeaderVersion == 3); returnErrorIf(m_nOffsetFieldDesc > std::numeric_limits::max() - m_nFieldDescLength); @@ -1364,25 +1519,27 @@ static void ReadVarIntAndAddNoCheck(GByte *&pabyIter, GIntBig &nOutVal) /************************************************************************/ vsi_l_offset -FileGDBTable::GetOffsetInTableForRow(int iRow, vsi_l_offset *pnOffsetInTableX) +FileGDBTable::GetOffsetInTableForRow(int64_t iRow, + vsi_l_offset *pnOffsetInTableX) { const int errorRetValue = 0; if (pnOffsetInTableX) *pnOffsetInTableX = 0; returnErrorIf(iRow < 0 || iRow >= m_nTotalRecordCount); - m_bIsDeleted = FALSE; + m_bIsDeleted = false; if (m_fpTableX == nullptr) { - m_bIsDeleted = IS_DELETED(m_anFeatureOffsets[iRow]); - return GET_OFFSET(m_anFeatureOffsets[iRow]); + m_bIsDeleted = + IS_DELETED(m_anFeatureOffsets[static_cast(iRow)]); + return GET_OFFSET(m_anFeatureOffsets[static_cast(iRow)]); } vsi_l_offset nOffsetInTableX; if (!m_abyTablXBlockMap.empty()) { GUInt32 nCountBlocksBefore = 0; - int iBlock = iRow / 1024; + const int iBlock = static_cast(iRow / 1024); // Check if the block is not empty if (TEST_BIT(m_abyTablXBlockMap.data(), iBlock) == 0) @@ -1406,7 +1563,8 @@ FileGDBTable::GetOffsetInTableForRow(int iRow, vsi_l_offset *pnOffsetInTableX) } m_nCountBlocksBeforeIBlockIdx = iBlock; m_nCountBlocksBeforeIBlockValue = nCountBlocksBefore; - const int iCorrectedRow = nCountBlocksBefore * 1024 + (iRow % 1024); + const int64_t iCorrectedRow = + static_cast(nCountBlocksBefore) * 1024 + (iRow % 1024); nOffsetInTableX = 16 + static_cast(m_nTablxOffsetSize) * iCorrectedRow; } @@ -1455,9 +1613,9 @@ uint64_t FileGDBTable::ReadFeatureOffset(const GByte *pabyBuffer) /* GetAndSelectNextNonEmptyRow() */ /************************************************************************/ -int FileGDBTable::GetAndSelectNextNonEmptyRow(int iRow) +int64_t FileGDBTable::GetAndSelectNextNonEmptyRow(int64_t iRow) { - const int errorRetValue = -1; + const int64_t errorRetValue = -1; returnErrorAndCleanupIf(iRow < 0 || iRow >= m_nTotalRecordCount, m_nCurRow = -1); @@ -1465,17 +1623,18 @@ int FileGDBTable::GetAndSelectNextNonEmptyRow(int iRow) { if (!m_abyTablXBlockMap.empty() && (iRow % 1024) == 0) { - int iBlock = iRow / 1024; + int iBlock = static_cast(iRow / 1024); if (TEST_BIT(m_abyTablXBlockMap.data(), iBlock) == 0) { - int nBlocks = DIV_ROUND_UP(m_nTotalRecordCount, 1024); + int nBlocks = + static_cast(DIV_ROUND_UP(m_nTotalRecordCount, 1024)); do { iBlock++; } while (iBlock < nBlocks && TEST_BIT(m_abyTablXBlockMap.data(), iBlock) == 0); - iRow = iBlock * 1024; + iRow = static_cast(iBlock) * 1024; if (iRow >= m_nTotalRecordCount) return -1; } @@ -1495,7 +1654,7 @@ int FileGDBTable::GetAndSelectNextNonEmptyRow(int iRow) /* SelectRow() */ /************************************************************************/ -int FileGDBTable::SelectRow(int iRow) +bool FileGDBTable::SelectRow(int64_t iRow) { const int errorRetValue = FALSE; returnErrorAndCleanupIf(iRow < 0 || iRow >= m_nTotalRecordCount, @@ -1539,7 +1698,8 @@ int FileGDBTable::SelectRow(int iRow) "NO"))) { CPLError(CE_Failure, CPLE_AppDefined, - "Invalid row length (%u) on feature %u compared " + "Invalid row length (%u) on feature %" PRId64 + " compared " "to the maximum size in the header (%u)", m_nRowBlobLength, iRow + 1, m_nHeaderBufferMaxSize); @@ -1549,7 +1709,8 @@ int FileGDBTable::SelectRow(int iRow) else { CPLDebug("OpenFileGDB", - "Invalid row length (%u) on feature %u compared " + "Invalid row length (%u) on feature %" PRId64 + " compared " "to the maximum size in the header (%u)", m_nRowBlobLength, iRow + 1, m_nHeaderBufferMaxSize); @@ -1571,7 +1732,7 @@ int FileGDBTable::SelectRow(int iRow) if (nOffsetTable + 4 + m_nRowBlobLength > m_nFileSize) { CPLError(CE_Failure, CPLE_AppDefined, - "Invalid row length (%u) on feature %u", + "Invalid row length (%u) on feature %" PRId64, m_nRowBlobLength, iRow + 1); m_nCurRow = -1; return errorRetValue; @@ -2253,7 +2414,7 @@ const OGRField *FileGDBTable::GetFieldValue(int iCol) if (iCol == static_cast(m_apoFields.size()) - 1 && m_pabyIterVals < pabyEnd) { - CPLDebug("OpenFileGDB", "%d bytes remaining at end of record %d", + CPLDebug("OpenFileGDB", "%d bytes remaining at end of record %" PRId64, static_cast(pabyEnd - m_pabyIterVals), m_nCurRow); } diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h index a5a4fc67f126..d057f3f5935e 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h @@ -428,10 +428,19 @@ class FileGDBTable { VSILFILE *m_fpTable = nullptr; VSILFILE *m_fpTableX = nullptr; + + enum class GDBTableVersion + { + V3 = 3, // 32-bit object id + V4 = 4, // 64-bit object id (ince ArcGIS Pro 3.2) + }; + GDBTableVersion m_eGDBTableVersion = GDBTableVersion::V3; vsi_l_offset m_nFileSize = 0; /* only read when needed */ bool m_bUpdate = false; + bool m_bReliableObjectID = true; // can be set to false on some V4 files std::string m_osFilename{}; + std::string m_osFilenameWithLayerName{}; bool m_bIsV9 = false; std::vector> m_apoFields{}; int m_iObjectIdField = -1; @@ -465,7 +474,7 @@ class FileGDBTable no .gdbtablx file */ uint64_t m_nOffsetTableXTrailer = 0; - uint32_t m_n1024BlocksPresent = 0; + uint64_t m_n1024BlocksPresent = 0; std::vector m_abyTablXBlockMap{}; int m_nCountBlocksBeforeIBlockIdx = 0; /* optimization */ int m_nCountBlocksBeforeIBlockValue = 0; /* optimization */ @@ -479,9 +488,9 @@ class FileGDBTable int m_nChSaved = -1; int m_bError = FALSE; - int m_nCurRow = -1; + int64_t m_nCurRow = -1; int m_bHasDeletedFeaturesListed = FALSE; - int m_bIsDeleted = FALSE; + bool m_bIsDeleted = false; int m_nLastCol = -1; GByte *m_pabyIterVals = nullptr; int m_iAccNullable = 0; @@ -494,8 +503,8 @@ class FileGDBTable bool m_bStringsAreUTF8 = true; // if false, UTF16 std::string m_osTempString{}; // used as a temporary to store strings // recoded from UTF16 to UTF8 - int m_nValidRecordCount = 0; - int m_nTotalRecordCount = 0; + int64_t m_nValidRecordCount = 0; + int64_t m_nTotalRecordCount = 0; int m_iGeomField = -1; int m_nCountNullableFields = 0; int m_nNullableFieldsSizeInBytes = 0; @@ -556,7 +565,8 @@ class FileGDBTable bool WriteHeader(VSILFILE *fpTable); bool WriteHeaderX(VSILFILE *fpTableX); - int ReadTableXHeader(); + bool ReadTableXHeaderV3(); + bool ReadTableXHeaderV4(); int IsLikelyFeatureAtOffset(vsi_l_offset nOffset, GUInt32 *pnSize, int *pbDeletedRecord); bool GuessFeatureLocations(); @@ -624,12 +634,12 @@ class FileGDBTable return m_bGeomTypeHasM; } - int GetValidRecordCount() const + int64_t GetValidRecordCount() const { return m_nValidRecordCount; } - int GetTotalRecordCount() const + int64_t GetTotalRecordCount() const { return m_nTotalRecordCount; } @@ -670,6 +680,15 @@ class FileGDBTable return m_apoIndexes[i].get(); } + /** Return if we can use attribute or spatial indices. + * This can be false for some sparse tables with 64-bit ObjectID since + * the format of the sparse bitmap isn't fully understood yet. + */ + bool CanUseIndices() const + { + return m_bReliableObjectID; + } + bool HasSpatialIndex(); bool CreateIndex(const std::string &osIndexName, const std::string &osExpression); @@ -677,7 +696,8 @@ class FileGDBTable bool CreateSpatialIndex(); vsi_l_offset - GetOffsetInTableForRow(int iRow, vsi_l_offset *pnOffsetInTableX = nullptr); + GetOffsetInTableForRow(int64_t iRow, + vsi_l_offset *pnOffsetInTableX = nullptr); int HasDeletedFeaturesListed() const { @@ -686,20 +706,20 @@ class FileGDBTable /* Next call to SelectRow() or GetFieldValue() invalidates previously * returned values */ - int SelectRow(int iRow); - int GetAndSelectNextNonEmptyRow(int iRow); + bool SelectRow(int64_t iRow); + int64_t GetAndSelectNextNonEmptyRow(int64_t iRow); int HasGotError() const { return m_bError; } - int GetCurRow() const + int64_t GetCurRow() const { return m_nCurRow; } - int IsCurRowDeleted() const + bool IsCurRowDeleted() const { return m_bIsDeleted; } @@ -731,9 +751,9 @@ class FileGDBTable bool CreateFeature(const std::vector &asRawFields, const OGRGeometry *poGeom, int *pnFID = nullptr); - bool UpdateFeature(int nFID, const std::vector &asRawFields, + bool UpdateFeature(int64_t nFID, const std::vector &asRawFields, const OGRGeometry *poGeom); - bool DeleteFeature(int nFID); + bool DeleteFeature(int64_t nFID); bool CheckFreeListConsistency(); void DeleteFreeList(); @@ -766,18 +786,18 @@ class FileGDBIterator virtual FileGDBTable *GetTable() = 0; virtual void Reset() = 0; - virtual int GetNextRowSortedByFID() = 0; - virtual int GetRowCount(); + virtual int64_t GetNextRowSortedByFID() = 0; + virtual int64_t GetRowCount(); /* Only available on a BuildIsNotNull() iterator */ virtual const OGRField *GetMinValue(int &eOutOGRFieldType); virtual const OGRField *GetMaxValue(int &eOutOGRFieldType); /* will reset the iterator */ - virtual int GetMinMaxSumCount(double &dfMin, double &dfMax, double &dfSum, - int &nCount); + virtual bool GetMinMaxSumCount(double &dfMin, double &dfMax, double &dfSum, + int &nCount); /* Only available on a BuildIsNotNull() or Build() iterator */ - virtual int GetNextRowSortedByValue(); + virtual int64_t GetNextRowSortedByValue(); static FileGDBIterator *Build(FileGDBTable *poParent, int nFieldIdx, int bAscending, FileGDBSQLOp op, diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp index aed5fe2a9b3b..befd9bc90993 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp @@ -33,6 +33,7 @@ #include "filegdbtable.h" #include +#include #include #include #include @@ -73,6 +74,7 @@ bool FileGDBTable::Create(const char *pszFilename, int nTablxOffsetSize, { CPLAssert(m_fpTable == nullptr); + m_eGDBTableVersion = GDBTableVersion::V3; m_bUpdate = true; m_eTableGeomType = eTableGeomType; m_nTablxOffsetSize = nTablxOffsetSize; @@ -88,6 +90,7 @@ bool FileGDBTable::Create(const char *pszFilename, int nTablxOffsetSize, } m_osFilename = pszFilename; + m_osFilenameWithLayerName = m_osFilename; m_fpTable = VSIFOpenL(pszFilename, "wb+"); if (m_fpTable == nullptr) { @@ -152,8 +155,9 @@ bool FileGDBTable::WriteHeader(VSILFILE *fpTable) VSIFSeekL(fpTable, 0, SEEK_SET); bool bRet = - WriteUInt32(fpTable, 3) && // version number - WriteUInt32(fpTable, m_nValidRecordCount) && // number of valid rows + WriteUInt32(fpTable, 3) && // version number + // number of valid rows + WriteUInt32(fpTable, static_cast(m_nValidRecordCount)) && WriteUInt32(fpTable, m_nHeaderBufferMaxSize) && // largest size of a feature // record / field description @@ -190,8 +194,8 @@ bool FileGDBTable::WriteHeaderX(VSILFILE *fpTableX) { VSIFSeekL(fpTableX, 0, SEEK_SET); if (!WriteUInt32(fpTableX, 3) || // version number - !WriteUInt32(fpTableX, m_n1024BlocksPresent) || - !WriteUInt32(fpTableX, m_nTotalRecordCount) || + !WriteUInt32(fpTableX, static_cast(m_n1024BlocksPresent)) || + !WriteUInt32(fpTableX, static_cast(m_nTotalRecordCount)) || !WriteUInt32(fpTableX, m_nTablxOffsetSize)) { CPLError(CE_Failure, CPLE_FileIO, "Cannot write .gdbtablx header"); @@ -267,7 +271,8 @@ bool FileGDBTable::Sync(VSILFILE *fpTable, VSILFILE *fpTableX) if (m_bDirtyHeader && fpTable) { VSIFSeekL(fpTable, 4, SEEK_SET); - bRet &= WriteUInt32(fpTable, m_nValidRecordCount); + bRet &= + WriteUInt32(fpTable, static_cast(m_nValidRecordCount)); m_nHeaderBufferMaxSize = std::max(m_nHeaderBufferMaxSize, std::max(m_nRowBufferMaxSize, m_nFieldDescLength)); @@ -285,8 +290,10 @@ bool FileGDBTable::Sync(VSILFILE *fpTable, VSILFILE *fpTableX) if (m_bDirtyTableXHeader && fpTableX) { VSIFSeekL(fpTableX, 4, SEEK_SET); - bRet &= WriteUInt32(fpTableX, m_n1024BlocksPresent); - bRet &= WriteUInt32(fpTableX, m_nTotalRecordCount); + bRet &= + WriteUInt32(fpTableX, static_cast(m_n1024BlocksPresent)); + bRet &= + WriteUInt32(fpTableX, static_cast(m_nTotalRecordCount)); m_bDirtyTableXHeader = false; } @@ -297,8 +304,8 @@ bool FileGDBTable::Sync(VSILFILE *fpTable, VSILFILE *fpTableX) m_nTablxOffsetSize * TABLX_FEATURES_PER_PAGE * static_cast(m_n1024BlocksPresent); VSIFSeekL(fpTableX, m_nOffsetTableXTrailer, SEEK_SET); - const uint32_t n1024BlocksTotal = - DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE); + const uint32_t n1024BlocksTotal = static_cast( + DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE)); if (!m_abyTablXBlockMap.empty()) { CPLAssert(m_abyTablXBlockMap.size() >= (n1024BlocksTotal + 7) / 8); @@ -314,7 +321,8 @@ bool FileGDBTable::Sync(VSILFILE *fpTable, VSILFILE *fpTableX) m_abyTablXBlockMap.resize(nBitmapInt32Words * 4); bRet &= WriteUInt32(fpTableX, nBitmapInt32Words); bRet &= WriteUInt32(fpTableX, n1024BlocksTotal); - bRet &= WriteUInt32(fpTableX, m_n1024BlocksPresent); + bRet &= + WriteUInt32(fpTableX, static_cast(m_n1024BlocksPresent)); uint32_t nTrailingZero32BitWords = 0; for (int i = static_cast(m_abyTablXBlockMap.size() / 4) - 1; i >= 0; --i) @@ -339,10 +347,10 @@ bool FileGDBTable::Sync(VSILFILE *fpTable, VSILFILE *fpTableX) nCountBlocks += TEST_BIT(m_abyTablXBlockMap.data(), i) != 0; if (nCountBlocks != m_n1024BlocksPresent) { - CPLError( - CE_Failure, CPLE_AppDefined, - "Sync(): nCountBlocks(=%u) != m_n1024BlocksPresent(=%u)", - nCountBlocks, m_n1024BlocksPresent); + CPLError(CE_Failure, CPLE_AppDefined, + "Sync(): nCountBlocks(=%u) != " + "m_n1024BlocksPresent(=%" PRIu64 ")", + nCountBlocks, m_n1024BlocksPresent); } #endif bRet &= VSIFWriteL(m_abyTablXBlockMap.data(), 1, @@ -1602,20 +1610,21 @@ bool FileGDBTable::SeekIntoTableXForNewFeature(int nObjectID) int iCorrectedRow; bool bWriteEmptyPageAtEnd = false; const uint32_t nPageSize = TABLX_FEATURES_PER_PAGE * m_nTablxOffsetSize; + const int nTotalRecordCount = static_cast(m_nTotalRecordCount); if (m_abyTablXBlockMap.empty()) { // Is the OID to write in the current allocated pages, or in the next // page ? if ((nObjectID - 1) / TABLX_FEATURES_PER_PAGE <= - ((m_nTotalRecordCount == 0) + ((nTotalRecordCount == 0) ? 0 - : (1 + (m_nTotalRecordCount - 1) / TABLX_FEATURES_PER_PAGE))) + : (1 + (nTotalRecordCount - 1) / TABLX_FEATURES_PER_PAGE))) { iCorrectedRow = nObjectID - 1; const auto n1024BlocksPresentBefore = m_n1024BlocksPresent; m_n1024BlocksPresent = - DIV_ROUND_UP(std::max(m_nTotalRecordCount, nObjectID), + DIV_ROUND_UP(std::max(nTotalRecordCount, nObjectID), TABLX_FEATURES_PER_PAGE); bWriteEmptyPageAtEnd = m_n1024BlocksPresent > n1024BlocksPresentBefore; @@ -1626,13 +1635,13 @@ bool FileGDBTable::SeekIntoTableXForNewFeature(int nObjectID) m_abyTablXBlockMap.resize( (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) + 7) / 8); for (int i = 0; - i < DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE); + i < DIV_ROUND_UP(nTotalRecordCount, TABLX_FEATURES_PER_PAGE); ++i) m_abyTablXBlockMap[i / 8] |= (1 << (i % 8)); const int iBlock = (nObjectID - 1) / TABLX_FEATURES_PER_PAGE; m_abyTablXBlockMap[iBlock / 8] |= (1 << (iBlock % 8)); iCorrectedRow = - DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE) * + DIV_ROUND_UP(nTotalRecordCount, TABLX_FEATURES_PER_PAGE) * TABLX_FEATURES_PER_PAGE + ((nObjectID - 1) % TABLX_FEATURES_PER_PAGE); m_n1024BlocksPresent++; @@ -1643,7 +1652,7 @@ bool FileGDBTable::SeekIntoTableXForNewFeature(int nObjectID) { const int iBlock = (nObjectID - 1) / TABLX_FEATURES_PER_PAGE; - if (nObjectID <= m_nTotalRecordCount) + if (nObjectID <= nTotalRecordCount) { CPLAssert(iBlock / 8 < static_cast(m_abyTablXBlockMap.size())); if (TEST_BIT(m_abyTablXBlockMap.data(), iBlock) == 0) @@ -1657,9 +1666,8 @@ bool FileGDBTable::SeekIntoTableXForNewFeature(int nObjectID) std::vector abyTmp(nPageSize); uint64_t nOffset = - TABLX_HEADER_SIZE + - static_cast(m_n1024BlocksPresent) * nPageSize; - for (int i = m_n1024BlocksPresent - 1; + TABLX_HEADER_SIZE + m_n1024BlocksPresent * nPageSize; + for (int i = static_cast(m_n1024BlocksPresent - 1); i >= static_cast(nCountBlocksBefore); --i) { nOffset -= nPageSize; @@ -1702,7 +1710,7 @@ bool FileGDBTable::SeekIntoTableXForNewFeature(int nObjectID) } } else if (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) > - DIV_ROUND_UP(m_nTotalRecordCount, TABLX_FEATURES_PER_PAGE)) + DIV_ROUND_UP(nTotalRecordCount, TABLX_FEATURES_PER_PAGE)) { m_abyTablXBlockMap.resize( (DIV_ROUND_UP(nObjectID, TABLX_FEATURES_PER_PAGE) + 7) / 8); @@ -1817,7 +1825,7 @@ bool FileGDBTable::CreateFeature(const std::vector &asRawFields, "Maximum number of records per table reached"); return false; } - nObjectID = m_nTotalRecordCount + 1; + nObjectID = static_cast(m_nTotalRecordCount + 1); } try @@ -1882,7 +1890,8 @@ bool FileGDBTable::CreateFeature(const std::vector &asRawFields, m_nFileSize += sizeof(uint32_t) + m_nRowBlobLength; } - m_nTotalRecordCount = std::max(m_nTotalRecordCount, nObjectID); + m_nTotalRecordCount = + std::max(m_nTotalRecordCount, static_cast(nObjectID)); m_nValidRecordCount++; m_bDirtyHeader = true; @@ -1897,7 +1906,7 @@ bool FileGDBTable::CreateFeature(const std::vector &asRawFields, /* UpdateFeature() */ /************************************************************************/ -bool FileGDBTable::UpdateFeature(int nFID, +bool FileGDBTable::UpdateFeature(int64_t nFID, const std::vector &asRawFields, const OGRGeometry *poGeom) { @@ -2050,7 +2059,7 @@ bool FileGDBTable::UpdateFeature(int nFID, /* DeleteFeature() */ /************************************************************************/ -bool FileGDBTable::DeleteFeature(int nFID) +bool FileGDBTable::DeleteFeature(int64_t nFID) { if (!m_bUpdate) return false; @@ -2627,7 +2636,7 @@ void FileGDBTable::RecomputeExtent() // Scan all features OGREnvelope sLayerEnvelope; OGREnvelope sFeatureEnvelope; - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) { iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write_fields.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write_fields.cpp index c8e61c7e6f3c..894e9ff23a3d 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write_fields.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write_fields.cpp @@ -837,7 +837,7 @@ bool FileGDBTable::DeleteField(int iField) m_apoFields[m_iGeomField]->m_eType = FGFT_BINARY; m_iGeomField = -1; - for (int iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < m_nTotalRecordCount; ++iCurFeat) { iCurFeat = GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) diff --git a/ogr/ogrsf_frmts/openfilegdb/gdalopenfilegdbrasterband.cpp b/ogr/ogrsf_frmts/openfilegdb/gdalopenfilegdbrasterband.cpp index 6a0119005011..7e337ebb0910 100644 --- a/ogr/ogrsf_frmts/openfilegdb/gdalopenfilegdbrasterband.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/gdalopenfilegdbrasterband.cpp @@ -117,10 +117,14 @@ bool OGROpenFileGDBDataSource::OpenRaster(const GDALOpenInfo *poOpenInfo, return false; } - int iRow = 0; + int64_t iRow = 0; while (iRow < oTable.GetTotalRecordCount() && (iRow = oTable.GetAndSelectNextNonEmptyRow(iRow)) >= 0) { + if (iRow >= INT32_MAX) + { + return false; + } auto psField = oTable.GetFieldValue(i_raster_id); if (!psField) { @@ -137,7 +141,7 @@ bool OGROpenFileGDBDataSource::OpenRaster(const GDALOpenInfo *poOpenInfo, continue; } - const int nGDBRasterBandId = iRow + 1; + const int nGDBRasterBandId = static_cast(iRow) + 1; psField = oTable.GetFieldValue(i_sequence_nbr); if (!psField) diff --git a/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h b/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h index 2dd3a8a2a654..edc16d750672 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h +++ b/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h @@ -138,7 +138,7 @@ class OGROpenFileGDBLayer final : public OGRLayer int m_iGeomFieldIdx = -1; int m_iAreaField = -1; // index of Shape_Area field int m_iLengthField = -1; // index of Shape_Length field - int m_iCurFeat = 0; + int64_t m_iCurFeat = 0; int m_iFIDAsRegularColumnIndex = -1; std::string m_osDefinition{}; std::string m_osDocumentation{}; @@ -239,7 +239,7 @@ class OGROpenFileGDBLayer final : public OGRLayer int &eOutType); int GetMinMaxSumCount(OGRFieldDefn *poFieldDefn, double &dfMin, double &dfMax, double &dfSum, int &nCount); - int HasIndexForField(const char *pszFieldName); + bool HasIndexForField(const char *pszFieldName); FileGDBIterator *BuildIndex(const char *pszFieldName, int bAscending, int op, swq_expr_node *poValue); diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp index 77dae116b5b7..16d0c20092eb 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp @@ -1504,7 +1504,7 @@ OGRFeature *OGROpenFileGDBSimpleSQLLayer::GetNextFeature() if (m_nLimit >= 0 && m_nIterated == m_nLimit) return nullptr; - int nRow = poIter->GetNextRowSortedByValue(); + const int64_t nRow = poIter->GetNextRowSortedByValue(); if (nRow < 0) return nullptr; OGRFeature *poFeature = GetFeature(nRow + 1); diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp index 6cf35031911b..411bbbb368c3 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp @@ -77,7 +77,7 @@ bool OGROpenFileGDBDataSource::GetExistingSpatialRef( FETCH_FIELD_IDX(iZTolerance, "ZTolerance", FGFT_FLOAT64); FETCH_FIELD_IDX(iMTolerance, "MTolerance", FGFT_FLOAT64); - int iCurFeat = 0; + int64_t iCurFeat = 0; while (iCurFeat < oTable.GetTotalRecordCount()) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); @@ -246,7 +246,8 @@ bool OGROpenFileGDBDataSource::RemoveRelationshipFromItemRelationships( FETCH_FIELD_IDX_WITH_RET(iOriginID, "OriginID", FGFT_GUID, false); FETCH_FIELD_IDX_WITH_RET(iDestID, "DestID", FGFT_GUID, false); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -290,7 +291,7 @@ bool OGROpenFileGDBDataSource::LinkDomainToTable( FETCH_FIELD_IDX(iOriginID, "OriginID", FGFT_GUID); FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); @@ -331,7 +332,8 @@ bool OGROpenFileGDBDataSource::UnlinkDomainToTable( FETCH_FIELD_IDX(iOriginID, "OriginID", FGFT_GUID); FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -365,7 +367,8 @@ bool OGROpenFileGDBDataSource::UpdateXMLDefinition( FETCH_FIELD_IDX(iName, "Name", FGFT_STRING); FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -406,7 +409,8 @@ bool OGROpenFileGDBDataSource::FindUUIDFromName(const std::string &osName, FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID); FETCH_FIELD_IDX(iName, "Name", FGFT_STRING); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -1262,9 +1266,10 @@ OGROpenFileGDBDataSource::ICreateLayer(const char *pszLayerName, auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; FileGDBTable oTable; - if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), false)) + if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), false) || + oTable.GetTotalRecordCount() >= INT32_MAX) return nullptr; - const int nTableNum = 1 + oTable.GetTotalRecordCount(); + const int nTableNum = static_cast(1 + oTable.GetTotalRecordCount()); oTable.Close(); const std::string osFilename(CPLFormFilename( @@ -1317,7 +1322,7 @@ OGRErr OGROpenFileGDBDataSource::DeleteLayer(int iLayer) FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); @@ -1342,7 +1347,7 @@ OGRErr OGROpenFileGDBDataSource::DeleteLayer(int iLayer) FETCH_FIELD_IDX_WITH_RET(iUUID, "UUID", FGFT_GLOBALID, OGRERR_FAILURE); FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); @@ -1374,7 +1379,7 @@ OGRErr OGROpenFileGDBDataSource::DeleteLayer(int iLayer) OGRERR_FAILURE); FETCH_FIELD_IDX_WITH_RET(iDestID, "DestID", FGFT_GUID, OGRERR_FAILURE); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); @@ -1561,7 +1566,7 @@ bool OGROpenFileGDBDataSource::DeleteFieldDomain( FETCH_FIELD_IDX(iType, "Type", FGFT_GUID); FETCH_FIELD_IDX(iName, "Name", FGFT_STRING); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); @@ -1601,7 +1606,7 @@ bool OGROpenFileGDBDataSource::DeleteFieldDomain( FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID); FETCH_FIELD_IDX(iType, "Type", FGFT_GUID); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); @@ -1673,7 +1678,8 @@ bool OGROpenFileGDBDataSource::UpdateFieldDomain( FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML); bool bMatchFound = false; - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) @@ -1818,12 +1824,13 @@ bool OGROpenFileGDBDataSource::AddRelationship( const std::string osThisGUID = OFGDBGenerateUUID(); FileGDBTable oTable; - if (!oTable.Open(m_osGDBItemsFilename.c_str(), true)) + if (!oTable.Open(m_osGDBItemsFilename.c_str(), true) || + oTable.GetTotalRecordCount() >= INT32_MAX) return false; // hopefully this just needs to be a unique value. Seems to autoincrement // when created from ArcMap at least! - const int iDsId = oTable.GetTotalRecordCount() + 1; + const int iDsId = static_cast(oTable.GetTotalRecordCount() + 1); std::string osMappingTableOidName; if (relationship->GetCardinality() == @@ -2002,7 +2009,7 @@ bool OGROpenFileGDBDataSource::DeleteRelationship(const std::string &name, FETCH_FIELD_IDX_WITH_RET(iType, "Type", FGFT_GUID, false); FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, false); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); @@ -2102,12 +2109,15 @@ bool OGROpenFileGDBDataSource::UpdateRelationship( } FileGDBTable oTable; - if (!oTable.Open(m_osGDBItemsFilename.c_str(), true)) + if (!oTable.Open(m_osGDBItemsFilename.c_str(), true) || + oTable.GetTotalRecordCount() >= INT32_MAX) + { return false; + } // hopefully this just needs to be a unique value. Seems to autoincrement // when created from ArcMap at least! - const int iDsId = oTable.GetTotalRecordCount() + 1; + const int iDsId = static_cast(oTable.GetTotalRecordCount()) + 1; std::string osMappingTableOidName; if (relationship->GetCardinality() == @@ -2145,7 +2155,8 @@ bool OGROpenFileGDBDataSource::UpdateRelationship( bool bMatchFound = false; std::string osUUID; - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); if (iCurFeat < 0) diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer.cpp index c98a978a49fa..1c33b099cc7e 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer.cpp @@ -330,7 +330,7 @@ void OGROpenFileGDBLayer::TryToDetectMultiPatchKind() if (m_poLyrTable->GetTotalRecordCount() == 0) return; - int nFirstIdx = m_poLyrTable->GetAndSelectNextNonEmptyRow(0); + const int64_t nFirstIdx = m_poLyrTable->GetAndSelectNextNonEmptyRow(0); if (nFirstIdx < 0) return; @@ -343,7 +343,7 @@ void OGROpenFileGDBLayer::TryToDetectMultiPatchKind() const OGRwkbGeometryType eType = poGeom->getGeometryType(); delete poGeom; - int nLastIdx = m_poLyrTable->GetTotalRecordCount() - 1; + int64_t nLastIdx = m_poLyrTable->GetTotalRecordCount() - 1; const GUInt32 nErrorCount = CPLGetErrorCounter(); while (nLastIdx > nFirstIdx && m_poLyrTable->GetOffsetInTableForRow(nLastIdx) == 0 && @@ -449,7 +449,8 @@ int OGROpenFileGDBLayer::BuildLayerDefinition() } #endif - if (!(m_poLyrTable->HasSpatialIndex() && + if (!(m_poLyrTable->CanUseIndices() && + m_poLyrTable->HasSpatialIndex() && CPLTestBool(CPLGetConfigOption("OPENFILEGDB_USE_SPATIAL_INDEX", "YES"))) && CPLTestBool(CPLGetConfigOption("OPENFILEGDB_IN_MEMORY_SPI", "YES"))) @@ -460,9 +461,11 @@ int OGROpenFileGDBLayer::BuildLayerDefinition() sGlobalBounds.maxx = poGDBGeomField->GetXMax(); sGlobalBounds.maxy = poGDBGeomField->GetYMax(); m_pQuadTree = CPLQuadTreeCreate(&sGlobalBounds, nullptr); - CPLQuadTreeSetMaxDepth(m_pQuadTree, - CPLQuadTreeGetAdvisedMaxDepth( - m_poLyrTable->GetValidRecordCount())); + CPLQuadTreeSetMaxDepth( + m_pQuadTree, + CPLQuadTreeGetAdvisedMaxDepth( + static_cast(std::min( + INT_MAX, m_poLyrTable->GetValidRecordCount())))); } else { @@ -957,7 +960,7 @@ void OGROpenFileGDBLayer::SetSpatialFilter(OGRGeometry *poGeom) if (poGeom != nullptr) { if (m_poSpatialIndexIterator == nullptr && - m_poLyrTable->HasSpatialIndex() && + m_poLyrTable->CanUseIndices() && m_poLyrTable->HasSpatialIndex() && CPLTestBool( CPLGetConfigOption("OPENFILEGDB_USE_SPATIAL_INDEX", "YES"))) { @@ -1703,7 +1706,7 @@ OGRFeature *OGROpenFileGDBLayer::GetCurrentFeature() { OGRFeature *poFeature = nullptr; int iOGRIdx = 0; - int iRow = m_poLyrTable->GetCurRow(); + int64_t iRow = m_poLyrTable->GetCurRow(); for (int iGDBIdx = 0; iGDBIdx < m_poLyrTable->GetFieldCount(); iGDBIdx++) { if (iOGRIdx == m_iFIDAsRegularColumnIndex) @@ -1727,16 +1730,27 @@ OGRFeature *OGROpenFileGDBLayer::GetCurrentFeature() if (m_poLyrTable->GetFeatureExtent(psField, &sFeatureEnvelope)) { - CPLRectObj sBounds; - sBounds.minx = sFeatureEnvelope.MinX; - sBounds.miny = sFeatureEnvelope.MinY; - sBounds.maxx = sFeatureEnvelope.MaxX; - sBounds.maxy = sFeatureEnvelope.MaxY; - CPLQuadTreeInsertWithBounds( - m_pQuadTree, - reinterpret_cast( - static_cast(iRow)), - &sBounds); +#if SIZEOF_VOIDP < 8 + if (iRow > INT32_MAX) + { + // m_pQuadTree stores iRow values as void* + // This would overflow here. + m_eSpatialIndexState = SPI_INVALID; + } + else +#endif + { + CPLRectObj sBounds; + sBounds.minx = sFeatureEnvelope.MinX; + sBounds.miny = sFeatureEnvelope.MinY; + sBounds.maxx = sFeatureEnvelope.MaxX; + sBounds.maxy = sFeatureEnvelope.MaxY; + CPLQuadTreeInsertWithBounds( + m_pQuadTree, + reinterpret_cast( + static_cast(iRow)), + &sBounds); + } } } @@ -1863,8 +1877,9 @@ OGRFeature *OGROpenFileGDBLayer::GetNextFeature() { return nullptr; } - int iRow = static_cast(reinterpret_cast( - m_pahFilteredFeatures[m_iCurFeat++])); + const auto iRow = + static_cast(reinterpret_cast( + m_pahFilteredFeatures[m_iCurFeat++])); if (m_poLyrTable->SelectRow(iRow)) { poFeature = GetCurrentFeature(); @@ -1882,7 +1897,7 @@ OGRFeature *OGROpenFileGDBLayer::GetNextFeature() { while (true) { - int iRow = poIterator->GetNextRowSortedByFID(); + const auto iRow = poIterator->GetNextRowSortedByFID(); if (iRow < 0) return nullptr; if (m_poLyrTable->SelectRow(iRow)) @@ -1954,7 +1969,7 @@ OGRFeature *OGROpenFileGDBLayer::GetFeature(GIntBig nFeatureId) if (nFeatureId < 1 || nFeatureId > m_poLyrTable->GetTotalRecordCount()) return nullptr; - if (!m_poLyrTable->SelectRow(static_cast(nFeatureId) - 1)) + if (!m_poLyrTable->SelectRow(nFeatureId - 1)) return nullptr; /* Temporarily disable spatial filter */ @@ -1993,7 +2008,7 @@ OGRErr OGROpenFileGDBLayer::SetNextByIndex(GIntBig nIndex) { if (nIndex < 0 || nIndex >= m_nFilteredFeatureCount) return OGRERR_FAILURE; - m_iCurFeat = static_cast(nIndex); + m_iCurFeat = nIndex; return OGRERR_NONE; } else if (m_poLyrTable->GetValidRecordCount() == @@ -2001,7 +2016,7 @@ OGRErr OGROpenFileGDBLayer::SetNextByIndex(GIntBig nIndex) { if (nIndex < 0 || nIndex >= m_poLyrTable->GetValidRecordCount()) return OGRERR_FAILURE; - m_iCurFeat = static_cast(nIndex); + m_iCurFeat = nIndex; return OGRERR_NONE; } else @@ -2105,7 +2120,7 @@ GIntBig OGROpenFileGDBLayer::GetFeatureCount(int bForce) int nCount = 0; while (true) { - const int nRowIdx = + const auto nRowIdx = m_poSpatialIndexIterator->GetNextRowSortedByFID(); if (nRowIdx < 0) break; @@ -2149,7 +2164,7 @@ GIntBig OGROpenFileGDBLayer::GetFeatureCount(int bForce) m_nFilteredFeatureCount = 0; } - for (int i = 0; i < m_poLyrTable->GetTotalRecordCount(); i++) + for (int64_t i = 0; i < m_poLyrTable->GetTotalRecordCount(); i++) { if (!m_poLyrTable->SelectRow(i)) { @@ -2158,6 +2173,15 @@ GIntBig OGROpenFileGDBLayer::GetFeatureCount(int bForce) else continue; } +#if SIZEOF_VOIDP < 8 + if (i > INT32_MAX) + { + // CPLQuadTreeInsertWithBounds stores row index values as void* + // This would overflow here. + m_eSpatialIndexState = SPI_INVALID; + break; + } +#endif const OGRField *psField = m_poLyrTable->GetFieldValue(m_iGeomFieldIdx); @@ -2307,7 +2331,8 @@ int OGROpenFileGDBLayer::TestCapability(const char *pszCap) else if (EQUAL(pszCap, OLCFastSpatialFilter)) { return m_eSpatialIndexState == SPI_COMPLETED || - m_poLyrTable->HasSpatialIndex(); + (m_poLyrTable->CanUseIndices() && + m_poLyrTable->HasSpatialIndex()); } return FALSE; @@ -2317,11 +2342,12 @@ int OGROpenFileGDBLayer::TestCapability(const char *pszCap) /* HasIndexForField() */ /***********************************************************************/ -int OGROpenFileGDBLayer::HasIndexForField(const char *pszFieldName) +bool OGROpenFileGDBLayer::HasIndexForField(const char *pszFieldName) { if (!BuildLayerDefinition()) - return FALSE; - + return false; + if (!m_poLyrTable->CanUseIndices()) + return false; int nTableColIdx = m_poLyrTable->GetFieldIdx(pszFieldName); return (nTableColIdx >= 0 && m_poLyrTable->GetField(nTableColIdx)->HasIndex()); @@ -2393,6 +2419,8 @@ const OGRField *OGROpenFileGDBLayer::GetMinMaxValue(OGRFieldDefn *poFieldDefn, eOutType = -1; if (!BuildLayerDefinition()) return nullptr; + if (!m_poLyrTable->CanUseIndices()) + return nullptr; const int nTableColIdx = m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef()); @@ -2427,21 +2455,21 @@ int OGROpenFileGDBLayer::GetMinMaxSumCount(OGRFieldDefn *poFieldDefn, dfSum = 0.0; nCount = 0; if (!BuildLayerDefinition()) - return FALSE; + return false; + if (!m_poLyrTable->CanUseIndices()) + return false; int nTableColIdx = m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef()); if (nTableColIdx >= 0 && m_poLyrTable->GetField(nTableColIdx)->HasIndex()) { - FileGDBIterator *poIter = - FileGDBIterator::BuildIsNotNull(m_poLyrTable, nTableColIdx, TRUE); - if (poIter != nullptr) + auto poIter = std::unique_ptr( + FileGDBIterator::BuildIsNotNull(m_poLyrTable, nTableColIdx, TRUE)); + if (poIter) { - int nRet = poIter->GetMinMaxSumCount(dfMin, dfMax, dfSum, nCount); - delete poIter; - return nRet; + return poIter->GetMinMaxSumCount(dfMin, dfMax, dfSum, nCount); } } - return FALSE; + return false; } /************************************************************************/ diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp index 819b24554db3..a12daa4c93cd 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp @@ -30,6 +30,7 @@ #include "ogr_openfilegdb.h" #include "filegdb_gdbtoogrfieldtype.h" +#include #include #include #include @@ -314,7 +315,8 @@ bool OGROpenFileGDBLayer::CreateFeatureDataset(const char *pszFeatureDataset) if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), false)) return false; CPLCreateXMLElementAndValue( - psRoot, "DSID", CPLSPrintf("%d", 1 + oTable.GetTotalRecordCount())); + psRoot, "DSID", + CPLSPrintf("%" PRId64, 1 + oTable.GetTotalRecordCount())); } CPLCreateXMLElementAndValue(psRoot, "Versioned", "false"); @@ -458,7 +460,7 @@ bool OGROpenFileGDBLayer::Create(const OGRGeomFieldDefn *poSrcGeomFieldDefn) FETCH_FIELD_IDX(iName, "Name", FGFT_STRING); FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); @@ -2635,7 +2637,8 @@ void OGROpenFileGDBLayer::RefreshXMLDefinitionInMemory() if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), false)) return; CPLCreateXMLElementAndValue( - psRoot, "DSID", CPLSPrintf("%d", 1 + oTable.GetTotalRecordCount())); + psRoot, "DSID", + CPLSPrintf("%" PRId64, 1 + oTable.GetTotalRecordCount())); } CPLCreateXMLElementAndValue(psRoot, "Versioned", "false"); @@ -3186,7 +3189,7 @@ OGRErr OGROpenFileGDBLayer::Rename(const char *pszDstTableName) FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); @@ -3224,7 +3227,7 @@ OGRErr OGROpenFileGDBLayer::Rename(const char *pszDstTableName) FETCH_FIELD_IDX_WITH_RET(iDefinition, "Definition", FGFT_XML, OGRERR_FAILURE); - for (int iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); + for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount(); ++iCurFeat) { iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat); From 8f324ef9a795ca9cb0f3e218066c23df6a16b5ed Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 25 May 2024 00:11:50 +0200 Subject: [PATCH 0002/1119] scripts/cppcheck.sh: ignore stlIfStrFind warning --- scripts/cppcheck.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/cppcheck.sh b/scripts/cppcheck.sh index 43cc9518cd05..474e87d79697 100755 --- a/scripts/cppcheck.sh +++ b/scripts/cppcheck.sh @@ -168,6 +168,10 @@ mv ${LOG_FILE}.tmp ${LOG_FILE} grep -v -e "duplInheritedMember" ${LOG_FILE} > ${LOG_FILE}.tmp mv ${LOG_FILE}.tmp ${LOG_FILE} +# Ignore stlIfStrFind warning "Inefficient usage of string::find() in condition; string::starts_with() could be faster" (requires C++20) +grep -v -e "stlIfStrFind" ${LOG_FILE} > ${LOG_FILE}.tmp +mv ${LOG_FILE}.tmp ${LOG_FILE} + if grep "null pointer" ${LOG_FILE} ; then echo "Null pointer check failed" ret_code=1 From 6982b57d4690535af26e3178cf2eaec8086455fa Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 24 May 2024 18:45:39 +0200 Subject: [PATCH 0003/1119] ESRIJSON: make it able to parse response of some CadastralSpecialServices ESRI APIs Fixes #9996 --- autotest/ogr/data/esrijson/GetLatLon.json | 139 ++++++++++++++++++ autotest/ogr/ogr_esrijson.py | 17 +++ ogr/ogrsf_frmts/geojson/ogresrijsonreader.cpp | 127 +++++++++++++--- ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp | 18 ++- 4 files changed, 276 insertions(+), 25 deletions(-) create mode 100644 autotest/ogr/data/esrijson/GetLatLon.json diff --git a/autotest/ogr/data/esrijson/GetLatLon.json b/autotest/ogr/data/esrijson/GetLatLon.json new file mode 100644 index 000000000000..e40a4ae2b9a1 --- /dev/null +++ b/autotest/ogr/data/esrijson/GetLatLon.json @@ -0,0 +1,139 @@ +{ + "trs": "WA330160N0260E0SN070", + "generatedplss": [ + "WA330160N0260E0SN070" + ], + "coordinates": [ + { + "plssid": "WA330160N0260E0SN070", + "lat": 46.889846925914661, + "lon": -119.61030783431359 + } + ], + "features": [ + { + "attributes": { + "landdescription": "WA330160N0260E0SN070" + }, + "geometry": { + "rings": [ + [ + [ + -119.60204327043593, + 46.886243867876424 + ], + [ + -119.60206398468807, + 46.882628224420934 + ], + [ + -119.60732767297685, + 46.882649819203635 + ], + [ + -119.6125944631483, + 46.882671183730082 + ], + [ + -119.6126044856519, + 46.882671170222224 + ], + [ + -119.61785486360311, + 46.882664173764368 + ], + [ + -119.61875770280301, + 46.882662936567044 + ], + [ + -119.61874405829215, + 46.883568936820438 + ], + [ + -119.61868552316993, + 46.886246722756596 + ], + [ + -119.61868498238411, + 46.886271509249347 + ], + [ + -119.61867486286243, + 46.886734870031582 + ], + [ + -119.61866382256761, + 46.887240158406691 + ], + [ + -119.6186061309634, + 46.889880080462206 + ], + [ + -119.61859592700011, + 46.890346766295536 + ], + [ + -119.61858348713005, + 46.890915726238376 + ], + [ + -119.61858325446639, + 46.89092222006439 + ], + [ + -119.61847427175444, + 46.893957399712157 + ], + [ + -119.61845184261844, + 46.894581451904436 + ], + [ + -119.61836141820194, + 46.897097152613171 + ], + [ + -119.61782949338284, + 46.897096441799754 + ], + [ + -119.61263370425482, + 46.897089367425643 + ], + [ + -119.61256327453992, + 46.8970893336651 + ], + [ + -119.60729432661518, + 46.897086677014869 + ], + [ + -119.60202277267778, + 46.897083939339161 + ], + [ + -119.60202262984565, + 46.893471725151699 + ], + [ + -119.60202248521686, + 46.8898595819661 + ], + [ + -119.60204327043593, + 46.886243867876424 + ] + ] + ], + "spatialReference": { + "wkid": 4326, + "latestWkid": 4326 + } + } + } + ], + "status": "success" +} \ No newline at end of file diff --git a/autotest/ogr/ogr_esrijson.py b/autotest/ogr/ogr_esrijson.py index 0f5eda1c2e48..3c00e5d61e23 100755 --- a/autotest/ogr/ogr_esrijson.py +++ b/autotest/ogr/ogr_esrijson.py @@ -682,3 +682,20 @@ def test_ogr_esrijson_identify_srs(): sr = lyr.GetSpatialRef() assert sr assert sr.GetAuthorityCode(None) == "2223" + + +############################################################################### +# Test for https://github.com/OSGeo/gdal/issues/9996 + + +def test_ogr_esrijson_read_CadastralSpecialServies(): + + ds = ogr.Open("data/esrijson/GetLatLon.json") + lyr = ds.GetLayer(0) + sr = lyr.GetSpatialRef() + assert sr + assert sr.GetAuthorityCode(None) == "4326" + assert lyr.GetGeomType() != ogr.wkbNone + f = lyr.GetNextFeature() + assert f["landdescription"] == "WA330160N0260E0SN070" + assert f.GetGeometryRef().GetGeometryType() == ogr.wkbPolygon diff --git a/ogr/ogrsf_frmts/geojson/ogresrijsonreader.cpp b/ogr/ogrsf_frmts/geojson/ogresrijsonreader.cpp index 1de41008c0e9..311087b4f66e 100644 --- a/ogr/ogrsf_frmts/geojson/ogresrijsonreader.cpp +++ b/ogr/ogrsf_frmts/geojson/ogresrijsonreader.cpp @@ -121,9 +121,39 @@ void OGRESRIJSONReader::ReadLayers(OGRGeoJSONDataSource *poDS, } auto eGeomType = OGRESRIJSONGetGeometryType(poGJObject_); - if (eGeomType == wkbNone && poSRS != nullptr) + if (eGeomType == wkbNone) { - eGeomType = wkbUnknown; + if (poSRS) + { + eGeomType = wkbUnknown; + } + else + { + json_object *poObjFeatures = + OGRGeoJSONFindMemberByName(poGJObject_, "features"); + if (poObjFeatures && + json_type_array == json_object_get_type(poObjFeatures)) + { + const auto nFeatures = json_object_array_length(poObjFeatures); + for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i) + { + json_object *poObjFeature = + json_object_array_get_idx(poObjFeatures, i); + if (poObjFeature != nullptr && + json_object_get_type(poObjFeature) == json_type_object) + { + if (auto poObjGeometry = OGRGeoJSONFindMemberByName( + poObjFeature, "geometry")) + { + eGeomType = wkbUnknown; + poSRS = + OGRESRIJSONReadSpatialReference(poObjGeometry); + break; + } + } + } + } + } } poLayer_ = new OGRGeoJSONLayer(pszName, poSRS, eGeomType, poDS, nullptr); @@ -185,28 +215,87 @@ bool OGRESRIJSONReader::GenerateLayerDefn() } } } + else if ((poFields = OGRGeoJSONFindMemberByName( + poGJObject_, "fieldAliases")) != nullptr && + json_object_get_type(poFields) == json_type_object) + { + json_object_iter it; + it.key = nullptr; + it.val = nullptr; + it.entry = nullptr; + json_object_object_foreachC(poFields, it) + { + OGRFieldDefn fldDefn(it.key, OFTString); + poDefn->AddFieldDefn(&fldDefn); + } + } else { - poFields = OGRGeoJSONFindMemberByName(poGJObject_, "fieldAliases"); - if (nullptr != poFields && - json_object_get_type(poFields) == json_type_object) + // Guess the fields' schema from the content of the features' "attributes" + // element + json_object *poObjFeatures = + OGRGeoJSONFindMemberByName(poGJObject_, "features"); + if (poObjFeatures && + json_type_array == json_object_get_type(poObjFeatures)) { - json_object_iter it; - it.key = nullptr; - it.val = nullptr; - it.entry = nullptr; - json_object_object_foreachC(poFields, it) + gdal::DirectedAcyclicGraph dag; + std::vector> apoFieldDefn{}; + std::map oMapFieldNameToIdx{}; + std::vector anCurFieldIndices; + std::set aoSetUndeterminedTypeFields; + + const auto nFeatures = json_object_array_length(poObjFeatures); + for (auto i = decltype(nFeatures){0}; i < nFeatures; ++i) { - OGRFieldDefn fldDefn(it.key, OFTString); - poDefn->AddFieldDefn(&fldDefn); + json_object *poObjFeature = + json_object_array_get_idx(poObjFeatures, i); + if (poObjFeature != nullptr && + json_object_get_type(poObjFeature) == json_type_object) + { + int nPrevFieldIdx = -1; + + json_object *poObjProps = + OGRGeoJSONFindMemberByName(poObjFeature, "attributes"); + if (nullptr != poObjProps && + json_object_get_type(poObjProps) == json_type_object) + { + json_object_iter it; + it.key = nullptr; + it.val = nullptr; + it.entry = nullptr; + json_object_object_foreachC(poObjProps, it) + { + anCurFieldIndices.clear(); + OGRGeoJSONReaderAddOrUpdateField( + anCurFieldIndices, oMapFieldNameToIdx, + apoFieldDefn, it.key, it.val, + /*bFlattenNestedAttributes = */ true, + /* chNestedAttributeSeparator = */ '.', + /* bArrayAsString =*/false, + /* bDateAsString = */ false, + aoSetUndeterminedTypeFields); + for (int idx : anCurFieldIndices) + { + dag.addNode(idx, + apoFieldDefn[idx]->GetNameRef()); + if (nPrevFieldIdx != -1) + { + dag.addEdge(nPrevFieldIdx, idx); + } + nPrevFieldIdx = idx; + } + } + } + } + } + + const auto sortedFields = dag.getTopologicalOrdering(); + CPLAssert(sortedFields.size() == apoFieldDefn.size()); + for (int idx : sortedFields) + { + // cppcheck-suppress containerOutOfBounds + poDefn->AddFieldDefn(apoFieldDefn[idx].get()); } - } - else - { - CPLError(CE_Failure, CPLE_AppDefined, - "Invalid FeatureCollection object. " - "Missing \'fields\' member."); - bSuccess = false; } } diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp index 4ec6ba2b3ae6..0f80c4524ff5 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp @@ -38,9 +38,12 @@ #include #include -const char szESRIJSonPotentialStart1[] = +const char szESRIJSonFeaturesGeometryRings[] = "{\"features\":[{\"geometry\":{\"rings\":["; +// Cf https://github.com/OSGeo/gdal/issues/9996#issuecomment-2129845692 +const char szESRIJSonFeaturesAttributes[] = "{\"features\":[{\"attributes\":{"; + /************************************************************************/ /* IsJSONObject() */ /************************************************************************/ @@ -181,9 +184,10 @@ static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence, return true; } - CPLString osWithoutSpace = GetCompactJSon(pszText, strlen(pszText)); + const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText)); if (osWithoutSpace.find("{\"features\":[") == 0 && - osWithoutSpace.find(szESRIJSonPotentialStart1) != 0) + osWithoutSpace.find(szESRIJSonFeaturesGeometryRings) != 0 && + osWithoutSpace.find(szESRIJSonFeaturesAttributes) != 0) { return true; } @@ -262,9 +266,11 @@ bool ESRIJSONIsObject(const char *pszText) return true; } - CPLString osWithoutSpace = - GetCompactJSon(pszText, strlen(szESRIJSonPotentialStart1)); - if (osWithoutSpace.find(szESRIJSonPotentialStart1) == 0) + const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText)); + if (osWithoutSpace.find(szESRIJSonFeaturesGeometryRings) == 0 || + osWithoutSpace.find(szESRIJSonFeaturesAttributes) == 0 || + osWithoutSpace.find("\"spatialReference\":{\"wkid\":") != + std::string::npos) { return true; } From 91af0177d07a73b2c08249b5b5edc76dc42146ce Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 25 May 2024 19:13:16 +0200 Subject: [PATCH 0004/1119] OpenFileGDB writer: correctly set flag on non-editable Shape_Length/Shape_Area special fields We fix both the flag attribute of the field declaration in .gdbtable, and the XML declaration of the field. --- ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp | 25 +- ogr/ogrsf_frmts/openfilegdb/filegdbtable.h | 31 +- .../openfilegdb/filegdbtable_write_fields.cpp | 66 ++-- .../ogropenfilegdbdatasource_write.cpp | 318 ++++++++++++------ .../openfilegdb/ogropenfilegdblayer_write.cpp | 58 +++- .../openfilegdb/test_ofgdb_write.cpp | 41 ++- 6 files changed, 347 insertions(+), 192 deletions(-) diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp index a4a7a235891c..29d4dbb54ab3 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp @@ -965,7 +965,7 @@ bool FileGDBTable::Open(const char *pszFilename, bool bUpdate, OGRField sDefault; OGR_RawField_SetUnset(&sDefault); - if ((flags & 4) != 0) + if (flags & FileGDBField::MASK_EDITABLE) { /* Default value */ /* Found on PreNIS.gdb/a0000000d.gdbtable */ @@ -1043,7 +1043,7 @@ bool FileGDBTable::Open(const char *pszFilename, bool bUpdate, if (eType == FGFT_OBJECTID) { - returnErrorIf(flags != 2); + returnErrorIf(flags != FileGDBField::MASK_REQUIRED); returnErrorIf(m_iObjectIdField >= 0); m_iObjectIdField = static_cast(m_apoFields.size()); } @@ -1052,7 +1052,9 @@ bool FileGDBTable::Open(const char *pszFilename, bool bUpdate, poField->m_osName = osName; poField->m_osAlias = osAlias; poField->m_eType = eType; - poField->m_bNullable = (flags & 1) != 0; + poField->m_bNullable = (flags & FileGDBField::MASK_NULLABLE) != 0; + poField->m_bRequired = (flags & FileGDBField::MASK_REQUIRED) != 0; + poField->m_bEditable = (flags & FileGDBField::MASK_EDITABLE) != 0; poField->m_nMaxWidth = nMaxWidth; poField->m_sDefault = sDefault; m_apoFields.emplace_back(std::move(poField)); @@ -2834,11 +2836,19 @@ FileGDBField::FileGDBField(FileGDBTable *poParentIn) : m_poParent(poParentIn) FileGDBField::FileGDBField(const std::string &osName, const std::string &osAlias, FileGDBFieldType eType, - bool bNullable, int nMaxWidth, - const OGRField &sDefault) + bool bNullable, bool bRequired, bool bEditable, + int nMaxWidth, const OGRField &sDefault) : m_osName(osName), m_osAlias(osAlias), m_eType(eType), - m_bNullable(bNullable), m_nMaxWidth(nMaxWidth) + m_bNullable(bNullable), m_bRequired(bRequired), m_bEditable(bEditable), + m_nMaxWidth(nMaxWidth) { + if (m_eType == FGFT_OBJECTID || m_eType == FGFT_GLOBALID) + { + CPLAssert(!m_bNullable); + CPLAssert(m_bRequired); + CPLAssert(!m_bEditable); + } + if (m_eType == FGFT_STRING && !OGR_RawField_IsUnset(&sDefault) && !OGR_RawField_IsNull(&sDefault)) { @@ -2918,7 +2928,8 @@ FileGDBGeomField::FileGDBGeomField( const std::string &osWKT, double dfXOrigin, double dfYOrigin, double dfXYScale, double dfXYTolerance, const std::vector &adfSpatialIndexGridResolution) - : FileGDBField(osName, osAlias, FGFT_GEOMETRY, bNullable, 0, + : FileGDBField(osName, osAlias, FGFT_GEOMETRY, bNullable, + /* bRequired = */ true, /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD), m_osWKT(osWKT), m_dfXOrigin(dfXOrigin), m_dfYOrigin(dfYOrigin), m_dfXYScale(dfXYScale), m_dfXYTolerance(dfXYTolerance), diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h index a5a4fc67f126..c55082b44041 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h @@ -102,8 +102,11 @@ class FileGDBField std::string m_osAlias{}; FileGDBFieldType m_eType = FGFT_UNDEFINED; - bool m_bNullable = false; - bool m_bHighPrecsion = false; // for FGFT_DATETIME + bool m_bNullable = false; // Bit 1 of flag field + bool m_bRequired = + false; // Bit 2 of flag field. Set for ObjectID, geometry field and Shape_Area/Shape_Length + bool m_bEditable = false; // Bit 3 of flag field. + bool m_bHighPrecision = false; // for FGFT_DATETIME bool m_bReadAsDouble = false; // used by FileGDBTable::CreateAttributeIndex() int m_nMaxWidth = 0; /* for string */ @@ -117,11 +120,17 @@ class FileGDBField public: static const OGRField UNSET_FIELD; + static constexpr int BIT_NULLABLE = 0; + static constexpr int BIT_REQUIRED = 1; + static constexpr int BIT_EDITABLE = 2; + static constexpr int MASK_NULLABLE = 1 << BIT_NULLABLE; + static constexpr int MASK_REQUIRED = 1 << BIT_REQUIRED; + static constexpr int MASK_EDITABLE = 1 << BIT_EDITABLE; explicit FileGDBField(FileGDBTable *m_poParent); FileGDBField(const std::string &osName, const std::string &osAlias, - FileGDBFieldType eType, bool bNullable, int nMaxWidth, - const OGRField &sDefault); + FileGDBFieldType eType, bool bNullable, bool bRequired, + bool bEditable, int nMaxWidth, const OGRField &sDefault); virtual ~FileGDBField(); void SetParent(FileGDBTable *poParent) @@ -149,6 +158,16 @@ class FileGDBField return m_bNullable; } + bool IsRequired() const + { + return m_bRequired; + } + + bool IsEditable() const + { + return m_bEditable; + } + int GetMaxWidth() const { return m_nMaxWidth; @@ -161,12 +180,12 @@ class FileGDBField void SetHighPrecision() { - m_bHighPrecsion = true; + m_bHighPrecision = true; } bool IsHighPrecision() const { - return m_bHighPrecsion; + return m_bHighPrecision; } int HasIndex(); diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write_fields.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write_fields.cpp index c8e61c7e6f3c..29d7a0cdba7c 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write_fields.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write_fields.cpp @@ -383,8 +383,17 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, WriteUTF16String(abyBuffer, psField->GetAlias().c_str(), NUMBER_OF_CHARS_ON_UINT8); WriteUInt8(abyBuffer, static_cast(psField->GetType())); - constexpr int UNKNOWN_FIELD_FLAG = 4; + const auto &sDefault = *(psField->GetDefault()); + + uint8_t nFlag = 0; + if (psField->IsNullable()) + nFlag = static_cast(nFlag | FileGDBField::MASK_NULLABLE); + if (psField->IsRequired()) + nFlag = static_cast(nFlag | FileGDBField::MASK_REQUIRED); + if (psField->IsEditable()) + nFlag = static_cast(nFlag | FileGDBField::MASK_EDITABLE); + switch (psField->GetType()) { case FGFT_UNDEFINED: @@ -396,9 +405,7 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, case FGFT_INT16: { WriteUInt8(abyBuffer, 2); // sizeof(int16) - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); if (!OGR_RawField_IsNull(&sDefault) && !OGR_RawField_IsUnset(&sDefault)) { @@ -415,9 +422,7 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, case FGFT_INT32: { WriteUInt8(abyBuffer, 4); // sizeof(int32) - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); if (!OGR_RawField_IsNull(&sDefault) && !OGR_RawField_IsUnset(&sDefault)) { @@ -434,9 +439,7 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, case FGFT_FLOAT32: { WriteUInt8(abyBuffer, 4); // sizeof(float32) - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); if (!OGR_RawField_IsNull(&sDefault) && !OGR_RawField_IsUnset(&sDefault)) { @@ -453,9 +456,7 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, case FGFT_FLOAT64: { WriteUInt8(abyBuffer, 8); // sizeof(float64) - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); if (!OGR_RawField_IsNull(&sDefault) && !OGR_RawField_IsUnset(&sDefault)) { @@ -472,9 +473,7 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, case FGFT_STRING: { WriteUInt32(abyBuffer, psField->GetMaxWidth()); - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); if (!OGR_RawField_IsNull(&sDefault) && !OGR_RawField_IsUnset(&sDefault)) { @@ -505,9 +504,7 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, case FGFT_DATE: { WriteUInt8(abyBuffer, 8); // sizeof(float64) - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); if (!OGR_RawField_IsNull(&sDefault) && !OGR_RawField_IsUnset(&sDefault)) { @@ -536,9 +533,7 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, const auto *geomField = cpl::down_cast(psField); WriteUInt8(abyBuffer, 0); // unknown role - WriteUInt8(abyBuffer, static_cast( - 2 | UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); WriteUTF16String(abyBuffer, geomField->GetWKT().c_str(), NUMBER_OF_BYTES_ON_UINT16); WriteUInt8( @@ -600,9 +595,7 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, case FGFT_BINARY: { WriteUInt8(abyBuffer, 0); // unknown role - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); break; } @@ -617,27 +610,21 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, case FGFT_GLOBALID: { WriteUInt8(abyBuffer, 38); // size - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); break; } case FGFT_XML: { WriteUInt8(abyBuffer, 0); // unknown role - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); break; } case FGFT_INT64: { WriteUInt8(abyBuffer, 8); // sizeof(int64) - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); if (!OGR_RawField_IsNull(&sDefault) && !OGR_RawField_IsUnset(&sDefault)) { @@ -654,9 +641,7 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, case FGFT_TIME: { WriteUInt8(abyBuffer, 8); // sizeof(float64) - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); if (!OGR_RawField_IsNull(&sDefault) && !OGR_RawField_IsUnset(&sDefault)) { @@ -673,9 +658,7 @@ WriteFieldDescriptor(std::vector &abyBuffer, const FileGDBField *psField, case FGFT_DATETIME_WITH_OFFSET: { WriteUInt8(abyBuffer, 8 + 2); // sizeof(float64) + sizeof(int16) - WriteUInt8(abyBuffer, static_cast( - UNKNOWN_FIELD_FLAG | - static_cast(psField->IsNullable()))); + WriteUInt8(abyBuffer, nFlag); if (!OGR_RawField_IsNull(&sDefault) && !OGR_RawField_IsUnset(&sDefault)) { @@ -978,7 +961,8 @@ bool FileGDBTable::AlterField(int iField, const std::string &osName, auto poIndex = m_apoFields[iField]->m_poIndex; m_apoFields[iField] = std::make_unique( - osName, osAlias, eType, bNullable, nMaxWidth, sDefault); + osName, osAlias, eType, bNullable, m_apoFields[iField]->IsRequired(), + m_apoFields[iField]->IsEditable(), nMaxWidth, sDefault); m_apoFields[iField]->SetParent(this); m_apoFields[iField]->m_poIndex = poIndex; if (poIndex && bRenameField) diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp index 6cf35031911b..8b880ecc2903 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp @@ -566,14 +566,20 @@ bool OGROpenFileGDBDataSource::CreateGDBSystemCatalog() if (!oTable.Create(m_osGDBSystemCatalogFilename.c_str(), 4, FGTGT_NONE, false, false) || !oTable.CreateField(std::make_unique( - "ID", std::string(), FGFT_OBJECTID, false, 0, - FileGDBField::UNSET_FIELD)) || + "ID", std::string(), FGFT_OBJECTID, + /* bNullable = */ false, + /* bRequired = */ true, + /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Name", std::string(), FGFT_STRING, false, 160, - FileGDBField::UNSET_FIELD)) || + "Name", std::string(), FGFT_STRING, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "FileFormat", std::string(), FGFT_INT32, false, 0, - FileGDBField::UNSET_FIELD))) + "FileFormat", std::string(), FGFT_INT32, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD))) { return false; } @@ -616,14 +622,20 @@ bool OGROpenFileGDBDataSource::CreateGDBDBTune() FileGDBTable oTable; if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) || !oTable.CreateField(std::make_unique( - "Keyword", std::string(), FGFT_STRING, false, 32, - FileGDBField::UNSET_FIELD)) || + "Keyword", std::string(), FGFT_STRING, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 32, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "ParameterName", std::string(), FGFT_STRING, false, 32, - FileGDBField::UNSET_FIELD)) || + "ParameterName", std::string(), FGFT_STRING, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 32, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "ConfigString", std::string(), FGFT_STRING, true, 2048, - FileGDBField::UNSET_FIELD))) + "ConfigString", std::string(), FGFT_STRING, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 2048, FileGDBField::UNSET_FIELD))) { return false; } @@ -709,41 +721,65 @@ bool OGROpenFileGDBDataSource::CreateGDBSpatialRefs() if (!oTable.Create(m_osGDBSpatialRefsFilename.c_str(), 4, FGTGT_NONE, false, false) || !oTable.CreateField(std::make_unique( - "ID", std::string(), FGFT_OBJECTID, false, 0, - FileGDBField::UNSET_FIELD)) || + "ID", std::string(), FGFT_OBJECTID, + /* bNullable = */ false, + /* bRequired = */ true, + /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "SRTEXT", std::string(), FGFT_STRING, false, 2048, - FileGDBField::UNSET_FIELD)) || + "SRTEXT", std::string(), FGFT_STRING, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 2048, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "FalseX", std::string(), FGFT_FLOAT64, true, 0, - FileGDBField::UNSET_FIELD)) || + "FalseX", std::string(), FGFT_FLOAT64, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "FalseY", std::string(), FGFT_FLOAT64, true, 0, - FileGDBField::UNSET_FIELD)) || + "FalseY", std::string(), FGFT_FLOAT64, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "XYUnits", std::string(), FGFT_FLOAT64, true, 0, - FileGDBField::UNSET_FIELD)) || + "XYUnits", std::string(), FGFT_FLOAT64, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "FalseZ", std::string(), FGFT_FLOAT64, true, 0, - FileGDBField::UNSET_FIELD)) || + "FalseZ", std::string(), FGFT_FLOAT64, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "ZUnits", std::string(), FGFT_FLOAT64, true, 0, - FileGDBField::UNSET_FIELD)) || + "ZUnits", std::string(), FGFT_FLOAT64, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "FalseM", std::string(), FGFT_FLOAT64, true, 0, - FileGDBField::UNSET_FIELD)) || + "FalseM", std::string(), FGFT_FLOAT64, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "MUnits", std::string(), FGFT_FLOAT64, true, 0, - FileGDBField::UNSET_FIELD)) || + "MUnits", std::string(), FGFT_FLOAT64, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "XYTolerance", std::string(), FGFT_FLOAT64, true, 0, - FileGDBField::UNSET_FIELD)) || + "XYTolerance", std::string(), FGFT_FLOAT64, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "ZTolerance", std::string(), FGFT_FLOAT64, true, 0, - FileGDBField::UNSET_FIELD)) || + "ZTolerance", std::string(), FGFT_FLOAT64, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "MTolerance", std::string(), FGFT_FLOAT64, true, 0, - FileGDBField::UNSET_FIELD))) + "MTolerance", std::string(), FGFT_FLOAT64, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD))) { return false; } @@ -789,53 +825,85 @@ bool OGROpenFileGDBDataSource::CreateGDBItems() if (!oTable.Create(m_osGDBItemsFilename.c_str(), 4, FGTGT_POLYGON, false, false) || !oTable.CreateField(std::make_unique( - "ObjectID", std::string(), FGFT_OBJECTID, false, 0, - FileGDBField::UNSET_FIELD)) || + "ObjectID", std::string(), FGFT_OBJECTID, + /* bNullable = */ false, + /* bRequired = */ true, + /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "UUID", std::string(), FGFT_GLOBALID, false, 0, - FileGDBField::UNSET_FIELD)) || + "UUID", std::string(), FGFT_GLOBALID, + /* bNullable = */ false, + /* bRequired = */ true, + /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Type", std::string(), FGFT_GUID, false, 0, - FileGDBField::UNSET_FIELD)) || + "Type", std::string(), FGFT_GUID, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Name", std::string(), FGFT_STRING, true, 160, - FileGDBField::UNSET_FIELD)) || + "Name", std::string(), FGFT_STRING, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "PhysicalName", std::string(), FGFT_STRING, true, 160, - FileGDBField::UNSET_FIELD)) || + "PhysicalName", std::string(), FGFT_STRING, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Path", std::string(), FGFT_STRING, true, 260, - FileGDBField::UNSET_FIELD)) || + "Path", std::string(), FGFT_STRING, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 260, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "DatasetSubtype1", std::string(), FGFT_INT32, true, 0, - FileGDBField::UNSET_FIELD)) || + "DatasetSubtype1", std::string(), FGFT_INT32, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "DatasetSubtype2", std::string(), FGFT_INT32, true, 0, - FileGDBField::UNSET_FIELD)) || + "DatasetSubtype2", std::string(), FGFT_INT32, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "DatasetInfo1", std::string(), FGFT_STRING, true, 255, - FileGDBField::UNSET_FIELD)) || + "DatasetInfo1", std::string(), FGFT_STRING, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "DatasetInfo2", std::string(), FGFT_STRING, true, 255, - FileGDBField::UNSET_FIELD)) || + "DatasetInfo2", std::string(), FGFT_STRING, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "URL", std::string(), FGFT_STRING, true, 255, - FileGDBField::UNSET_FIELD)) || + "URL", std::string(), FGFT_STRING, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Definition", std::string(), FGFT_XML, true, 0, - FileGDBField::UNSET_FIELD)) || + "Definition", std::string(), FGFT_XML, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Documentation", std::string(), FGFT_XML, true, 0, - FileGDBField::UNSET_FIELD)) || + "Documentation", std::string(), FGFT_XML, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "ItemInfo", std::string(), FGFT_XML, true, 0, - FileGDBField::UNSET_FIELD)) || + "ItemInfo", std::string(), FGFT_XML, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Properties", std::string(), FGFT_INT32, true, 0, - FileGDBField::UNSET_FIELD)) || + "Properties", std::string(), FGFT_INT32, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Defaults", std::string(), FGFT_BINARY, true, 0, - FileGDBField::UNSET_FIELD)) || + "Defaults", std::string(), FGFT_BINARY, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::move(poGeomField))) { return false; @@ -899,17 +967,25 @@ bool OGROpenFileGDBDataSource::CreateGDBItemTypes() FileGDBTable oTable; if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) || !oTable.CreateField(std::make_unique( - "ObjectID", std::string(), FGFT_OBJECTID, false, 0, - FileGDBField::UNSET_FIELD)) || + "ObjectID", std::string(), FGFT_OBJECTID, + /* bNullable = */ false, + /* bRequired = */ true, + /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "UUID", std::string(), FGFT_GUID, false, 0, - FileGDBField::UNSET_FIELD)) || + "UUID", std::string(), FGFT_GUID, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "ParentTypeID", std::string(), FGFT_GUID, false, 0, - FileGDBField::UNSET_FIELD)) || + "ParentTypeID", std::string(), FGFT_GUID, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Name", std::string(), FGFT_STRING, false, 160, - FileGDBField::UNSET_FIELD))) + "Name", std::string(), FGFT_STRING, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD))) { return false; } @@ -1014,26 +1090,40 @@ bool OGROpenFileGDBDataSource::CreateGDBItemRelationships() if (!oTable.Create(m_osGDBItemRelationshipsFilename.c_str(), 4, FGTGT_NONE, false, false) || !oTable.CreateField(std::make_unique( - "ObjectID", std::string(), FGFT_OBJECTID, false, 0, - FileGDBField::UNSET_FIELD)) || + "ObjectID", std::string(), FGFT_OBJECTID, + /* bNullable = */ false, + /* bRequired = */ true, + /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "UUID", std::string(), FGFT_GLOBALID, false, 0, - FileGDBField::UNSET_FIELD)) || + "UUID", std::string(), FGFT_GLOBALID, + /* bNullable = */ false, + /* bRequired = */ true, + /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "OriginID", std::string(), FGFT_GUID, false, 0, - FileGDBField::UNSET_FIELD)) || + "OriginID", std::string(), FGFT_GUID, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "DestID", std::string(), FGFT_GUID, false, 0, - FileGDBField::UNSET_FIELD)) || + "DestID", std::string(), FGFT_GUID, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Type", std::string(), FGFT_GUID, false, 0, - FileGDBField::UNSET_FIELD)) || + "Type", std::string(), FGFT_GUID, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Attributes", std::string(), FGFT_XML, true, 0, - FileGDBField::UNSET_FIELD)) || + "Attributes", std::string(), FGFT_XML, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Properties", std::string(), FGFT_INT32, true, 0, - FileGDBField::UNSET_FIELD))) + "Properties", std::string(), FGFT_INT32, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD))) { return false; } @@ -1057,29 +1147,45 @@ bool OGROpenFileGDBDataSource::CreateGDBItemRelationshipTypes() FileGDBTable oTable; if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) || !oTable.CreateField(std::make_unique( - "ObjectID", std::string(), FGFT_OBJECTID, false, 0, - FileGDBField::UNSET_FIELD)) || + "ObjectID", std::string(), FGFT_OBJECTID, + /* bNullable = */ false, + /* bRequired = */ true, + /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "UUID", std::string(), FGFT_GUID, false, 0, - FileGDBField::UNSET_FIELD)) || + "UUID", std::string(), FGFT_GUID, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "OrigItemTypeID", std::string(), FGFT_GUID, false, 0, - FileGDBField::UNSET_FIELD)) || + "OrigItemTypeID", std::string(), FGFT_GUID, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "DestItemTypeID", std::string(), FGFT_GUID, false, 0, - FileGDBField::UNSET_FIELD)) || + "DestItemTypeID", std::string(), FGFT_GUID, + /* bNullable = */ false, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "Name", std::string(), FGFT_STRING, true, 160, - FileGDBField::UNSET_FIELD)) || + "Name", std::string(), FGFT_STRING, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "ForwardLabel", std::string(), FGFT_STRING, true, 255, - FileGDBField::UNSET_FIELD)) || + "ForwardLabel", std::string(), FGFT_STRING, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "BackwardLabel", std::string(), FGFT_STRING, true, 255, - FileGDBField::UNSET_FIELD)) || + "BackwardLabel", std::string(), FGFT_STRING, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) || !oTable.CreateField(std::make_unique( - "IsContainment", std::string(), FGFT_INT16, true, 0, - FileGDBField::UNSET_FIELD))) + "IsContainment", std::string(), FGFT_INT16, + /* bNullable = */ true, + /* bRequired = */ false, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD))) { return false; } diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp index 819b24554db3..5cc636fc4477 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp @@ -584,8 +584,10 @@ bool OGROpenFileGDBLayer::Create(const OGRGeomFieldDefn *poSrcGeomFieldDefn) { OGRFieldDefn oField("field_before_geom", OFTString); m_poLyrTable->CreateField(std::make_unique( - oField.GetNameRef(), std::string(), FGFT_STRING, true, 0, - FileGDBField::UNSET_FIELD)); + oField.GetNameRef(), std::string(), FGFT_STRING, + /* bNullable = */ true, + /* bRequired = */ true, + /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)); m_poFeatureDefn->AddFieldDefn(&oField); } @@ -699,9 +701,11 @@ bool OGROpenFileGDBLayer::Create(const OGRGeomFieldDefn *poSrcGeomFieldDefn) const std::string osFIDName = m_aosCreationOptions.FetchNameValueDef("FID", "OBJECTID"); - if (!m_poLyrTable->CreateField(std::unique_ptr( - new FileGDBField(osFIDName, std::string(), FGFT_OBJECTID, false, 0, - FileGDBField::UNSET_FIELD)))) + if (!m_poLyrTable->CreateField(std::make_unique( + osFIDName, std::string(), FGFT_OBJECTID, + /* bNullable = */ false, + /* bRequired = */ true, + /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD))) { Close(); return false; @@ -954,9 +958,18 @@ static CPLXMLNode *CreateXMLFieldDefinition(const OGRFieldDefn *poFieldDefn, CPLAddXMLAttributeAndValue(psFieldType, "xmlns:typens", "http://www.esri.com/schemas/ArcGIS/10.3"); } - CPLCreateXMLElementAndValue(GPFieldInfoEx, "IsNullable", - poGDBFieldDefn->IsNullable() ? "true" - : "false"); + if (poGDBFieldDefn->IsNullable()) + { + CPLCreateXMLElementAndValue(GPFieldInfoEx, "IsNullable", "true"); + } + if (poGDBFieldDefn->IsRequired()) + { + CPLCreateXMLElementAndValue(GPFieldInfoEx, "Required", "true"); + } + if (!poGDBFieldDefn->IsEditable()) + { + CPLCreateXMLElementAndValue(GPFieldInfoEx, "Editable", "false"); + } if (poGDBFieldDefn->IsHighPrecision()) { CPLCreateXMLElementAndValue(GPFieldInfoEx, "HighPrecision", "true"); @@ -1357,23 +1370,36 @@ OGRErr OGROpenFileGDBLayer::CreateField(const OGRFieldDefn *poFieldIn, } } - const char *pszAlias = poField->GetAlternativeNameRef(); - if (!m_poLyrTable->CreateField(std::make_unique( - poField->GetNameRef(), - pszAlias ? std::string(pszAlias) : std::string(), eType, - CPL_TO_BOOL(poField->IsNullable()), nWidth, sDefault))) - { - return OGRERR_FAILURE; - } + const bool bNullable = + CPL_TO_BOOL(poField->IsNullable()) && eType != FGFT_GLOBALID; + bool bRequired = (eType == FGFT_GLOBALID); + bool bEditable = (eType != FGFT_GLOBALID); if (poField->GetType() == OFTReal) { const char *pszDefault = poField->GetDefault(); if (pszDefault && EQUAL(pszDefault, "FILEGEODATABASE_SHAPE_AREA")) + { m_iAreaField = m_poFeatureDefn->GetFieldCount(); + bRequired = true; + bEditable = false; + } else if (pszDefault && EQUAL(pszDefault, "FILEGEODATABASE_SHAPE_LENGTH")) + { m_iLengthField = m_poFeatureDefn->GetFieldCount(); + bRequired = true; + bEditable = false; + } + } + + const char *pszAlias = poField->GetAlternativeNameRef(); + if (!m_poLyrTable->CreateField(std::make_unique( + poField->GetNameRef(), + pszAlias ? std::string(pszAlias) : std::string(), eType, bNullable, + bRequired, bEditable, nWidth, sDefault))) + { + return OGRERR_FAILURE; } whileUnsealing(m_poFeatureDefn)->AddFieldDefn(poField); diff --git a/ogr/ogrsf_frmts/openfilegdb/test_ofgdb_write.cpp b/ogr/ogrsf_frmts/openfilegdb/test_ofgdb_write.cpp index 268a0bb741d7..fdd50e1225b6 100644 --- a/ogr/ogrsf_frmts/openfilegdb/test_ofgdb_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/test_ofgdb_write.cpp @@ -40,24 +40,33 @@ int main() const int nTablxOffsetSize = 4; oTable.Create("test_ofgdb.gdbtable", nTablxOffsetSize, eTableGeomType, bGeomTypeHasZ, bGeomTypeHasM); + oTable.CreateField(std::unique_ptr(new FileGDBField( + "OBJECTID", "OBJECTID", FGFT_OBJECTID, + /* nullable = */ false, + /* required = */ true, + /* editable = */ false, 0, FileGDBField::UNSET_FIELD))); + oTable.CreateField(std::unique_ptr( - new FileGDBField("OBJECTID", "OBJECTID", FGFT_OBJECTID, false, 0, + new FileGDBField("int16", "", FGFT_INT16, true, false, true, 0, + FileGDBField::UNSET_FIELD))); + oTable.CreateField(std::unique_ptr( + new FileGDBField("int32", "", FGFT_INT32, true, false, true, 0, + FileGDBField::UNSET_FIELD))); + oTable.CreateField(std::unique_ptr( + new FileGDBField("float32", "", FGFT_FLOAT32, true, false, true, 0, + FileGDBField::UNSET_FIELD))); + oTable.CreateField(std::unique_ptr( + new FileGDBField("float64", "", FGFT_FLOAT64, true, false, true, 0, + FileGDBField::UNSET_FIELD))); + oTable.CreateField(std::unique_ptr( + new FileGDBField("str", "", FGFT_STRING, true, false, true, 0, + FileGDBField::UNSET_FIELD))); + oTable.CreateField(std::unique_ptr( + new FileGDBField("datetime", "", FGFT_DATETIME, true, false, true, 0, + FileGDBField::UNSET_FIELD))); + oTable.CreateField(std::unique_ptr( + new FileGDBField("binary", "", FGFT_BINARY, true, false, true, 0, FileGDBField::UNSET_FIELD))); - - oTable.CreateField(std::unique_ptr(new FileGDBField( - "int16", "", FGFT_INT16, true, 0, FileGDBField::UNSET_FIELD))); - oTable.CreateField(std::unique_ptr(new FileGDBField( - "int32", "", FGFT_INT32, true, 0, FileGDBField::UNSET_FIELD))); - oTable.CreateField(std::unique_ptr(new FileGDBField( - "float32", "", FGFT_FLOAT32, true, 0, FileGDBField::UNSET_FIELD))); - oTable.CreateField(std::unique_ptr(new FileGDBField( - "float64", "", FGFT_FLOAT64, true, 0, FileGDBField::UNSET_FIELD))); - oTable.CreateField(std::unique_ptr(new FileGDBField( - "str", "", FGFT_STRING, true, 0, FileGDBField::UNSET_FIELD))); - oTable.CreateField(std::unique_ptr(new FileGDBField( - "datetime", "", FGFT_DATETIME, true, 0, FileGDBField::UNSET_FIELD))); - oTable.CreateField(std::unique_ptr(new FileGDBField( - "binary", "", FGFT_BINARY, true, 0, FileGDBField::UNSET_FIELD))); auto poGeomField = std::unique_ptr(new FileGDBGeomField( "SHAPE", "", true, "{B286C06B-0879-11D2-AACA-00C04FA33C20}", -400, -400, From eeb0626cd2764a5292e1867f208387d2cfd874b5 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 25 May 2024 19:33:19 +0200 Subject: [PATCH 0005/1119] OpenFileGDB writer: .gdbtable header must be rewritten when updating an existing feature at the end of file The only consequence of the omission of that fix is that the file size in the .gdbtable could be incorrect. A wrong file size in the header has no consequence on OpenFileGDB reader/writer. It also doesn't seem to affect Esri reader side, but it could *potentially* affect the writing side, if that field was trusted to seek to the end of the file. --- ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp index aed5fe2a9b3b..945f710e9817 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp @@ -2016,6 +2016,7 @@ bool FileGDBTable::UpdateFeature(int nFID, m_nRowBufferMaxSize = std::max(m_nRowBufferMaxSize, m_nRowBlobLength); if (nFreeOffset == OFFSET_MINUS_ONE) { + m_bDirtyHeader = true; m_nFileSize += sizeof(uint32_t) + m_nRowBlobLength; } From 87ee6b7ca602a9615d49a585992b605641ffb30f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 25 May 2024 21:08:50 +0200 Subject: [PATCH 0006/1119] OpenFileGDB: BuildSRS(): do not use CPLErrorReset() --- ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp index 77dae116b5b7..f0b433a81162 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp @@ -2151,7 +2151,8 @@ OGROpenFileGDBDataSource::BuildSRS(const CPLXMLNode *psInfo) [](OGRSpatialReference &oSRS, int nLatestCode, int nCode) { bool bSuccess = false; - CPLPushErrorHandler(CPLQuietErrorHandler); + CPLErrorStateBackuper oQuietError(CPLQuietErrorHandler); + // Try first with nLatestWKID as there is a higher chance it is a // EPSG code and not an ESRI one. if (nLatestCode > 0) @@ -2193,8 +2194,7 @@ OGROpenFileGDBDataSource::BuildSRS(const CPLXMLNode *psInfo) CPLDebug("OpenFileGDB", "Cannot import SRID %d", nCode); } } - CPLPopErrorHandler(); - CPLErrorReset(); + return bSuccess; }; From a9d2913285fea2c62524a6fa498d4000a43ddcfe Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 25 May 2024 21:10:08 +0200 Subject: [PATCH 0007/1119] OpenFileGDB: detect and try to repair corruption of .gdbtable header Versions of the driver before commit fdf39012788b1110b3bf0ae6b8422a528f0ae8b6 didn't properly update the m_nHeaderBufferMaxSize field when updating an existing feature when the new version takes more space than the previous version. OpenFileGDB doesn't care but Esri software (FileGDB SDK or ArcMap/ArcGis) do, leading to issues such as https://github.com/qgis/QGIS/issues/57536 --- autotest/ogr/ogr_openfilegdb_write.py | 55 +++++++++++++++++++ ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp | 53 ++++++++++++++++++ ogr/ogrsf_frmts/openfilegdb/filegdbtable.h | 5 ++ .../openfilegdb/ogropenfilegdbdatasource.cpp | 8 ++- 4 files changed, 120 insertions(+), 1 deletion(-) diff --git a/autotest/ogr/ogr_openfilegdb_write.py b/autotest/ogr/ogr_openfilegdb_write.py index 628f4b75a74c..08ef72a8221f 100755 --- a/autotest/ogr/ogr_openfilegdb_write.py +++ b/autotest/ogr/ogr_openfilegdb_write.py @@ -4510,3 +4510,58 @@ def test_ogr_openfilegdb_write_geom_coord_precision(tmp_vsimem): "ZTolerance": 0.0001, "HighPrecision": "true", } + + +############################################################################### +# Test repairing a corrupted header +# Scenario similar to https://github.com/qgis/QGIS/issues/57536 + + +def test_ogr_openfilegdb_repair_corrupted_header(tmp_vsimem): + + filename = str(tmp_vsimem / "out.gdb") + ds = ogr.GetDriverByName("OpenFileGDB").CreateDataSource(filename) + srs = osr.SpatialReference() + srs.ImportFromEPSG(4326) + lyr = ds.CreateLayer("test", srs, ogr.wkbLineString) + f = ogr.Feature(lyr.GetLayerDefn()) + g = ogr.Geometry(ogr.wkbLineString) + g.SetPoint_2D(10, 0, 0) + f.SetGeometry(g) + lyr.CreateFeature(f) + ds = None + + # Corrupt m_nHeaderBufferMaxSize field + corrupted_filename = filename + "/a00000004.gdbtable" + f = gdal.VSIFOpenL(corrupted_filename, "r+b") + assert f + gdal.VSIFSeekL(f, 8, 0) + gdal.VSIFWriteL(b"\x00" * 4, 4, 1, f) + gdal.VSIFCloseL(f) + + with gdal.config_option( + "OGR_OPENFILEGDB_ERROR_ON_INCONSISTENT_BUFFER_MAX_SIZE", "NO" + ), gdal.quiet_errors(): + ds = ogr.Open(filename) + assert ( + gdal.GetLastErrorMsg() + == f"A corruption in the header of {corrupted_filename} has been detected. It would need to be repaired to be properly read by other software, either by using ogr2ogr to generate a new dataset, or by opening this dataset in update mode and reading all its records." + ) + assert ds.GetLayerCount() == 1 + + with gdal.config_option( + "OGR_OPENFILEGDB_ERROR_ON_INCONSISTENT_BUFFER_MAX_SIZE", "NO" + ), gdal.quiet_errors(): + ds = ogr.Open(filename, update=1) + assert ( + gdal.GetLastErrorMsg() + == f"A corruption in the header of {corrupted_filename} has been detected. It is going to be repaired to be properly read by other software." + ) + assert ds.GetLayerCount() == 1 + + with gdal.config_option( + "OGR_OPENFILEGDB_ERROR_ON_INCONSISTENT_BUFFER_MAX_SIZE", "NO" + ), gdal.quiet_errors(): + ds = ogr.Open(filename) + assert gdal.GetLastErrorMsg() == "" + assert ds.GetLayerCount() == 1 diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp index 29d4dbb54ab3..284dc2a16242 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp @@ -1550,11 +1550,64 @@ int FileGDBTable::SelectRow(int iRow) } else { + // Versions of the driver before commit + // fdf39012788b1110b3bf0ae6b8422a528f0ae8b6 didn't + // properly update the m_nHeaderBufferMaxSize field + // when updating an existing feature when the new version + // takes more space than the previous version. + // OpenFileGDB doesn't care but Esri software (FileGDB SDK + // or ArcMap/ArcGis) do, leading to issues such as + // https://github.com/qgis/QGIS/issues/57536 + CPLDebug("OpenFileGDB", "Invalid row length (%u) on feature %u compared " "to the maximum size in the header (%u)", m_nRowBlobLength, iRow + 1, m_nHeaderBufferMaxSize); + + if (m_bUpdate) + { + if (!m_bHasWarnedAboutHeaderRepair) + { + m_bHasWarnedAboutHeaderRepair = true; + CPLError(CE_Warning, CPLE_AppDefined, + "A corruption in the header of %s has " + "been detected. It is going to be " + "repaired to be properly read by other " + "software.", + m_osFilename.c_str()); + + m_bDirtyHeader = true; + + // Invalidate existing indices, as the corrupted + // m_nHeaderBufferMaxSize value may have cause + // Esri software to generate corrupted indices. + m_bDirtyIndices = true; + + // Compute file size + VSIFSeekL(m_fpTable, 0, SEEK_END); + m_nFileSize = VSIFTellL(m_fpTable); + VSIFSeekL(m_fpTable, nOffsetTable + 4, SEEK_SET); + } + } + else + { + if (!m_bHasWarnedAboutHeaderRepair) + { + m_bHasWarnedAboutHeaderRepair = true; + CPLError(CE_Warning, CPLE_AppDefined, + "A corruption in the header of %s has " + "been detected. It would need to be " + "repaired to be properly read by other " + "software, either by using ogr2ogr to " + "generate a new dataset, or by opening " + "this dataset in update mode and reading " + "all its records.", + m_osFilename.c_str()); + } + } + + m_nHeaderBufferMaxSize = m_nRowBlobLength; } } diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h index c55082b44041..11a27363b626 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.h @@ -450,6 +450,11 @@ class FileGDBTable vsi_l_offset m_nFileSize = 0; /* only read when needed */ bool m_bUpdate = false; + //! This flag is set when we detect that a corruption of m_nHeaderBufferMaxSize + // prior to fix needs to fdf39012788b1110b3bf0ae6b8422a528f0ae8b6 to be + // repaired + bool m_bHasWarnedAboutHeaderRepair = false; + std::string m_osFilename{}; bool m_bIsV9 = false; std::vector> m_apoFields{}; diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp index f0b433a81162..0fa057fa2987 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp @@ -681,7 +681,13 @@ bool OGROpenFileGDBDataSource::OpenFileGDBv10( CPLString osFilename(CPLFormFilename( m_osDirName, CPLSPrintf("a%08x.gdbtable", iGDBItems + 1), nullptr)); - if (!oTable.Open(osFilename, false)) + + // Normally we don't need to update in update mode the GDB_Items table, + // but this may help repairing it, if we have corrupted it with past + // GDAL versions. + const bool bOpenInUpdateMode = + (poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0; + if (!oTable.Open(osFilename, bOpenInUpdateMode)) return false; const int iUUID = oTable.GetFieldIdx("UUID"); From 48f78793d8af9cb78eb129c05252026cea15bb4d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 27 May 2024 19:03:23 +0200 Subject: [PATCH 0008/1119] Add GDALArgumentParser::add_extra_usage_hint() --- apps/gdalargumentparser.cpp | 26 ++++++++++++++++++++++++++ apps/gdalargumentparser.h | 7 +++++++ 2 files changed, 33 insertions(+) diff --git a/apps/gdalargumentparser.cpp b/apps/gdalargumentparser.cpp index 1d70e28ea28c..c70a939be599 100644 --- a/apps/gdalargumentparser.cpp +++ b/apps/gdalargumentparser.cpp @@ -103,6 +103,32 @@ void GDALArgumentParser::display_error_and_usage(const std::exception &err) << _(" --long-usage for full help.") << std::endl; } +/************************************************************************/ +/* usage() */ +/************************************************************************/ + +std::string GDALArgumentParser::usage() const +{ + std::string ret(ArgumentParser::usage()); + if (!m_osExtraUsageHint.empty()) + { + ret += '\n'; + ret += '\n'; + ret += m_osExtraUsageHint; + } + return ret; +} + +/************************************************************************/ +/* add_extra_usage_hint() */ +/************************************************************************/ + +void GDALArgumentParser::add_extra_usage_hint( + const std::string &osExtraUsageHint) +{ + m_osExtraUsageHint = osExtraUsageHint; +} + /************************************************************************/ /* add_quiet_argument() */ /************************************************************************/ diff --git a/apps/gdalargumentparser.h b/apps/gdalargumentparser.h index 5a6f162109d8..bb4ebb522534 100644 --- a/apps/gdalargumentparser.h +++ b/apps/gdalargumentparser.h @@ -61,6 +61,12 @@ class GDALArgumentParser : public ArgumentParser explicit GDALArgumentParser(const std::string &program_name, bool bForBinary); + //! Return usage message + std::string usage() const; + + //! Adds an extra usage hint. + void add_extra_usage_hint(const std::string &osExtraUsageHint); + //! Format an exception as an error message and display the program usage void display_error_and_usage(const std::exception &err); @@ -141,6 +147,7 @@ class GDALArgumentParser : public ArgumentParser std::map::iterator find_argument(const std::string &name); std::vector> aoSubparsers; + std::string m_osExtraUsageHint{}; }; #endif /* GDALARGUMENTPARSER_H */ From d9db075865097dea6e3e06c044f949ccea59a5e0 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 27 May 2024 19:03:43 +0200 Subject: [PATCH 0009/1119] gdal_contour: add help hint that one and only of -i, -fl or -e must be used --- apps/gdal_contour.cpp | 10 ++++++++++ autotest/utilities/test_gdal_contour.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/apps/gdal_contour.cpp b/apps/gdal_contour.cpp index 8b57c62dd0df..f5d70e592694 100644 --- a/apps/gdal_contour.cpp +++ b/apps/gdal_contour.cpp @@ -81,6 +81,9 @@ GDALContourAppOptionsGetParser(GDALContourOptions *psOptions) "For more details, consult the full documentation for the gdal_contour " "utility: http://gdal.org/gdal_contour.html")); + argParser->add_extra_usage_hint( + _("One and only one of -i, -fl or -e must be specified.")); + argParser->add_argument("-b") .metavar("") .default_value(1) @@ -250,6 +253,13 @@ MAIN_START(argc, argv) { auto argParser = GDALContourAppOptionsGetParser(&sOptions); argParser->parse_args_without_binary_name(argv + 1); + + if (sOptions.dfInterval == 0.0 && sOptions.adfFixedLevels.empty() && + sOptions.dfExpBase == 0.0) + { + fprintf(stderr, "%s\n", argParser->usage().c_str()); + exit(1); + } } catch (const std::exception &error) { diff --git a/autotest/utilities/test_gdal_contour.py b/autotest/utilities/test_gdal_contour.py index c4af38485bd2..772c503d1581 100755 --- a/autotest/utilities/test_gdal_contour.py +++ b/autotest/utilities/test_gdal_contour.py @@ -388,3 +388,17 @@ def test_gdal_contour_5(gdal_contour_path, tmp_path): i = i + 1 feat = lyr.GetNextFeature() + + +############################################################################### +# Test missing -fl, -i or -e + + +def test_gdal_contour_missing_fl_i_or_e(gdal_contour_path, testdata_tif, tmp_path): + + contour_shp = str(tmp_path / "contour.shp") + + _, err = gdaltest.runexternal_out_and_err( + gdal_contour_path + f" {testdata_tif} {contour_shp}" + ) + assert "One and only one of -i, -fl or -e must be specified." in err From 16ade8253f26200246abb5ab24d17e18216e7a11 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 29 May 2024 00:52:05 +0200 Subject: [PATCH 0010/1119] HDF5: add support for libhdf5 >= 1.14.4.2 when built with Float16 Fixes https://github.com/HDFGroup/hdf5/issues/4527 --- autotest/gdrivers/hdf5multidim.py | 64 +++++++++++++++++++++ frmts/hdf5/gh5_convenience.cpp | 11 ++++ frmts/hdf5/hdf5_api.h | 4 ++ frmts/hdf5/hdf5dataset.cpp | 68 ++++++++++++++++++++++ frmts/hdf5/hdf5dataset.h | 4 +- frmts/hdf5/hdf5imagedataset.cpp | 95 +++++++++++++++++++++++++++---- frmts/hdf5/hdf5multidim.cpp | 63 +++++++++++++++++++- 7 files changed, 293 insertions(+), 16 deletions(-) diff --git a/autotest/gdrivers/hdf5multidim.py b/autotest/gdrivers/hdf5multidim.py index 6f1f003c326f..31260e9ab766 100755 --- a/autotest/gdrivers/hdf5multidim.py +++ b/autotest/gdrivers/hdf5multidim.py @@ -840,3 +840,67 @@ def test_hdf5_multidim_block_size_structural_info(): var = rg.OpenMDArray("Band1") assert var.GetBlockSize() == [1, 2] assert var.GetStructuralInfo() == {"COMPRESSION": "DEFLATE", "FILTER": "SHUFFLE"} + + +############################################################################### +# Test reading a compound data type made of 2 Float16 values + + +def test_hdf5_multidim_read_cfloat16(): + + ds = gdal.OpenEx("data/hdf5/complex.h5", gdal.OF_MULTIDIM_RASTER) + rg = ds.GetRootGroup() + var = rg.OpenMDArray("f16") + assert var.GetDataType().GetNumericDataType() == gdal.GDT_CFloat32 + assert struct.unpack("f" * (5 * 5 * 2), var.Read()) == ( + 0.0, + 0.0, + 1.0, + 1.0, + 2.0, + 2.0, + 3.0, + 3.0, + 4.0, + 4.0, + 5.0, + 5.0, + 6.0, + 6.0, + 7.0, + 7.0, + 8.0, + 8.0, + 9.0, + 9.0, + 10.0, + 10.0, + 11.0, + 11.0, + 12.0, + 12.0, + 13.0, + 13.0, + 14.0, + 14.0, + 15.0, + 15.0, + 16.0, + 16.0, + 17.0, + 17.0, + 18.0, + 18.0, + 19.0, + 19.0, + 20.0, + 20.0, + 21.0, + 21.0, + 22.0, + 22.0, + 23.0, + 23.0, + 24.0, + 24.0, + ) diff --git a/frmts/hdf5/gh5_convenience.cpp b/frmts/hdf5/gh5_convenience.cpp index 304032e08e3f..e48b5c630d21 100644 --- a/frmts/hdf5/gh5_convenience.cpp +++ b/frmts/hdf5/gh5_convenience.cpp @@ -26,6 +26,7 @@ * DEALINGS IN THE SOFTWARE. ****************************************************************************/ +#include "cpl_float.h" #include "gh5_convenience.h" /************************************************************************/ @@ -209,6 +210,16 @@ bool GH5_FetchAttribute(hid_t loc_id, const char *pszAttrName, double &dfResult, pszAttrName, static_cast(nVal), dfResult); } } +#ifdef HDF5_HAVE_FLOAT16 + else if (H5Tequal(H5T_NATIVE_FLOAT16, hAttrNativeType)) + { + const uint16_t nVal16 = *((uint16_t *)buf); + const uint32_t nVal32 = CPLHalfToFloat(nVal16); + float fVal; + memcpy(&fVal, &nVal32, sizeof(fVal)); + dfResult = fVal; + } +#endif else if (H5Tequal(H5T_NATIVE_FLOAT, hAttrNativeType)) dfResult = *((float *)buf); else if (H5Tequal(H5T_NATIVE_DOUBLE, hAttrNativeType)) diff --git a/frmts/hdf5/hdf5_api.h b/frmts/hdf5/hdf5_api.h index 013534310488..0b3110d2cc2e 100644 --- a/frmts/hdf5/hdf5_api.h +++ b/frmts/hdf5/hdf5_api.h @@ -46,6 +46,10 @@ #include "hdf5.h" +#if defined(H5T_NATIVE_FLOAT16) && defined(H5_HAVE__FLOAT16) +#define HDF5_HAVE_FLOAT16 +#endif + #ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/frmts/hdf5/hdf5dataset.cpp b/frmts/hdf5/hdf5dataset.cpp index 499b8a86c877..64c1fea2e92b 100644 --- a/frmts/hdf5/hdf5dataset.cpp +++ b/frmts/hdf5/hdf5dataset.cpp @@ -44,6 +44,7 @@ #include "cpl_conv.h" #include "cpl_error.h" +#include "cpl_float.h" #include "cpl_string.h" #include "gdal.h" #include "gdal_frmts.h" @@ -201,6 +202,10 @@ GDALDataType HDF5Dataset::GetDataType(hid_t TypeID) return GDT_Unknown; #endif } +#ifdef HDF5_HAVE_FLOAT16 + else if (H5Tequal(H5T_NATIVE_FLOAT16, TypeID)) + return GDT_Float32; +#endif else if (H5Tequal(H5T_NATIVE_FLOAT, TypeID)) return GDT_Float32; else if (H5Tequal(H5T_NATIVE_DOUBLE, TypeID)) @@ -258,6 +263,10 @@ GDALDataType HDF5Dataset::GetDataType(hid_t TypeID) eDataType = GDT_Unknown; #endif } +#ifdef HDF5_HAVE_FLOAT16 + else if (H5Tequal(H5T_NATIVE_FLOAT16, ElemTypeID)) + eDataType = GDT_CFloat32; +#endif else if (H5Tequal(H5T_NATIVE_FLOAT, ElemTypeID)) eDataType = GDT_CFloat32; else if (H5Tequal(H5T_NATIVE_DOUBLE, ElemTypeID)) @@ -272,6 +281,32 @@ GDALDataType HDF5Dataset::GetDataType(hid_t TypeID) return GDT_Unknown; } +/************************************************************************/ +/* IsNativeCFloat16() */ +/************************************************************************/ + +/* static*/ bool HDF5Dataset::IsNativeCFloat16(hid_t hDataType) +{ +#ifdef HDF5_HAVE_FLOAT16 + // For complex the compound type must contain 2 elements + if (H5Tget_class(hDataType) != H5T_COMPOUND || + H5Tget_nmembers(hDataType) != 2) + return false; + + // For complex the native types of both elements should be the same + hid_t ElemTypeID = H5Tget_member_type(hDataType, 0); + hid_t Elem2TypeID = H5Tget_member_type(hDataType, 1); + const bool bRet = H5Tequal(ElemTypeID, H5T_NATIVE_FLOAT16) > 0 && + H5Tequal(Elem2TypeID, H5T_NATIVE_FLOAT16) > 0; + H5Tclose(ElemTypeID); + H5Tclose(Elem2TypeID); + return bRet; +#else + CPL_IGNORE_RET_VAL(hDataType); + return false; +#endif +} + /************************************************************************/ /* GetDataTypeName() */ /* */ @@ -304,6 +339,10 @@ const char *HDF5Dataset::GetDataTypeName(hid_t TypeID) return "32/64-bit integer"; else if (H5Tequal(H5T_NATIVE_ULONG, TypeID)) return "32/64-bit unsigned integer"; +#ifdef HDF5_HAVE_FLOAT16 + else if (H5Tequal(H5T_NATIVE_FLOAT16, TypeID)) + return "16-bit floating-point"; +#endif else if (H5Tequal(H5T_NATIVE_FLOAT, TypeID)) return "32-bit floating-point"; else if (H5Tequal(H5T_NATIVE_DOUBLE, TypeID)) @@ -348,6 +387,13 @@ const char *HDF5Dataset::GetDataTypeName(hid_t TypeID) H5Tclose(ElemTypeID); return "complex, 32/64-bit integer"; } +#ifdef HDF5_HAVE_FLOAT16 + else if (H5Tequal(H5T_NATIVE_FLOAT16, ElemTypeID)) + { + H5Tclose(ElemTypeID); + return "complex, 16-bit floating-point"; + } +#endif else if (H5Tequal(H5T_NATIVE_FLOAT, ElemTypeID)) { H5Tclose(ElemTypeID); @@ -1132,6 +1178,28 @@ static herr_t HDF5AttrIterate(hid_t hH5ObjID, const char *pszAttrName, psContext->m_osValue += szData; } } +#ifdef HDF5_HAVE_FLOAT16 + else if (H5Tequal(H5T_NATIVE_FLOAT16, hAttrNativeType) > 0) + { + for (hsize_t i = 0; i < nAttrElmts; i++) + { + const uint16_t nVal16 = static_cast(buf)[i]; + const uint32_t nVal32 = CPLHalfToFloat(nVal16); + float fVal; + memcpy(&fVal, &nVal32, sizeof(fVal)); + CPLsnprintf(szData, nDataLen, "%.8g", fVal); + if (psContext->m_osValue.size() > MAX_METADATA_LEN) + { + CPLError(CE_Warning, CPLE_OutOfMemory, + "Header data too long. Truncated"); + break; + } + if (i > 0) + psContext->m_osValue += ' '; + psContext->m_osValue += szData; + } + } +#endif else if (H5Tequal(H5T_NATIVE_FLOAT, hAttrNativeType) > 0) { for (hsize_t i = 0; i < nAttrElmts; i++) diff --git a/frmts/hdf5/hdf5dataset.h b/frmts/hdf5/hdf5dataset.h index dc9245b4ca31..646f7c6b6297 100644 --- a/frmts/hdf5/hdf5dataset.h +++ b/frmts/hdf5/hdf5dataset.h @@ -240,8 +240,6 @@ class HDF5Dataset CPL_NON_FINAL : public GDALPamDataset char *CreatePath(HDF5GroupObjects *); static void DestroyH5Objects(HDF5GroupObjects *); - static const char *GetDataTypeName(hid_t); - /** * Reads an array of double attributes from the HDF5 metadata. * It reads the attributes directly on its binary form directly, @@ -275,6 +273,8 @@ class HDF5Dataset CPL_NON_FINAL : public GDALPamDataset static std::shared_ptr OpenGroup( const std::shared_ptr &poSharedResources); + static bool IsNativeCFloat16(hid_t hDataType); + static const char *GetDataTypeName(hid_t); static GDALDataType GetDataType(hid_t); }; diff --git a/frmts/hdf5/hdf5imagedataset.cpp b/frmts/hdf5/hdf5imagedataset.cpp index 64a2c33917ea..4d140c523f28 100644 --- a/frmts/hdf5/hdf5imagedataset.cpp +++ b/frmts/hdf5/hdf5imagedataset.cpp @@ -29,6 +29,7 @@ #include "hdf5_api.h" +#include "cpl_float.h" #include "cpl_string.h" #include "gdal_frmts.h" #include "gdal_pam.h" @@ -72,9 +73,10 @@ class HDF5ImageDataset final : public HDF5Dataset int dimensions; hid_t dataset_id; hid_t dataspace_id; - hsize_t size; - hid_t datatype; hid_t native; +#ifdef HDF5_HAVE_FLOAT16 + bool m_bConvertFromFloat16 = false; +#endif Hdf5ProductType iSubdatasetType; HDF5CSKProductEnum iCSKProductType; double adfGeoTransform[6]; @@ -209,9 +211,9 @@ class HDF5ImageDataset final : public HDF5Dataset /************************************************************************/ HDF5ImageDataset::HDF5ImageDataset() : dims(nullptr), maxdims(nullptr), poH5Objects(nullptr), ndims(0), - dimensions(0), dataset_id(-1), dataspace_id(-1), size(0), datatype(-1), - native(-1), iSubdatasetType(UNKNOWN_PRODUCT), - iCSKProductType(PROD_UNKNOWN), bHasGeoTransform(false) + dimensions(0), dataset_id(-1), dataspace_id(-1), native(-1), + iSubdatasetType(UNKNOWN_PRODUCT), iCSKProductType(PROD_UNKNOWN), + bHasGeoTransform(false) { m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); @@ -236,8 +238,6 @@ HDF5ImageDataset::~HDF5ImageDataset() H5Dclose(dataset_id); if (dataspace_id > 0) H5Sclose(dataspace_id); - if (datatype > 0) - H5Tclose(datatype); if (native > 0) H5Tclose(native); @@ -465,6 +465,42 @@ CPLErr HDF5ImageRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, return CE_Failure; } +#ifdef HDF5_HAVE_FLOAT16 + if (eDataType == GDT_Float32 && poGDS->m_bConvertFromFloat16) + { + for (size_t i = static_cast(nBlockXSize) * nBlockYSize; i > 0; + /* do nothing */) + { + --i; + uint16_t nVal16; + memcpy(&nVal16, static_cast(pImage) + i, + sizeof(nVal16)); + const uint32_t nVal32 = CPLHalfToFloat(nVal16); + float fVal; + memcpy(&fVal, &nVal32, sizeof(fVal)); + *(static_cast(pImage) + i) = fVal; + } + } + else if (eDataType == GDT_CFloat32 && poGDS->m_bConvertFromFloat16) + { + for (size_t i = static_cast(nBlockXSize) * nBlockYSize; i > 0; + /* do nothing */) + { + --i; + for (int j = 1; j >= 0; --j) + { + uint16_t nVal16; + memcpy(&nVal16, static_cast(pImage) + 2 * i + j, + sizeof(nVal16)); + const uint32_t nVal32 = CPLHalfToFloat(nVal16); + float fVal; + memcpy(&fVal, &nVal32, sizeof(fVal)); + *(static_cast(pImage) + 2 * i + j) = fVal; + } + } + } +#endif + return CE_None; } @@ -482,6 +518,15 @@ CPLErr HDF5ImageRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, { HDF5ImageDataset *poGDS = static_cast(poDS); +#ifdef HDF5_HAVE_FLOAT16 + if (poGDS->m_bConvertFromFloat16) + { + return GDALPamRasterBand::IRasterIO( + eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize, + eBufType, nPixelSpace, nLineSpace, psExtraArg); + } +#endif + const bool bIsBandInterleavedData = poGDS->ndims == 3 && poGDS->m_nOtherDimIndex == 0 && poGDS->GetYIndex() == 1 && poGDS->GetXIndex() == 2; @@ -761,6 +806,16 @@ CPLErr HDF5ImageDataset::IRasterIO( GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg) { +#ifdef HDF5_HAVE_FLOAT16 + if (m_bConvertFromFloat16) + { + return HDF5Dataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, + pData, nBufXSize, nBufYSize, eBufType, + nBandCount, panBandMap, nPixelSpace, + nLineSpace, nBandSpace, psExtraArg); + } +#endif + const auto IsConsecutiveBands = [](const int *panVals, int nCount) { for (int i = 1; i < nCount; ++i) @@ -1021,9 +1076,25 @@ GDALDataset *HDF5ImageDataset::Open(GDALOpenInfo *poOpenInfo) static_cast(CPLCalloc(poDS->ndims, sizeof(hsize_t))); poDS->dimensions = H5Sget_simple_extent_dims(poDS->dataspace_id, poDS->dims, poDS->maxdims); - poDS->datatype = H5Dget_type(poDS->dataset_id); - poDS->size = H5Tget_size(poDS->datatype); - poDS->native = H5Tget_native_type(poDS->datatype, H5T_DIR_ASCEND); + auto datatype = H5Dget_type(poDS->dataset_id); + poDS->native = H5Tget_native_type(datatype, H5T_DIR_ASCEND); + H5Tclose(datatype); + + const auto eGDALDataType = poDS->GetDataType(poDS->native); + if (eGDALDataType == GDT_Unknown) + { + CPLError(CE_Failure, CPLE_AppDefined, "Unhandled HDF5 data type"); + delete poDS; + return nullptr; + } + +#ifdef HDF5_HAVE_FLOAT16 + if (H5Tequal(H5T_NATIVE_FLOAT16, poDS->native) || + IsNativeCFloat16(poDS->native)) + { + poDS->m_bConvertFromFloat16 = true; + } +#endif // CSK code in IdentifyProductType() and CreateProjections() // uses dataset metadata. @@ -1239,8 +1310,8 @@ GDALDataset *HDF5ImageDataset::Open(GDALOpenInfo *poOpenInfo) for (int i = 0; i < nBands; i++) { - HDF5ImageRasterBand *const poBand = new HDF5ImageRasterBand( - poDS, i + 1, poDS->GetDataType(poDS->native)); + HDF5ImageRasterBand *const poBand = + new HDF5ImageRasterBand(poDS, i + 1, eGDALDataType); poDS->SetBand(i + 1, poBand); diff --git a/frmts/hdf5/hdf5multidim.cpp b/frmts/hdf5/hdf5multidim.cpp index fa27ff79a051..f5a0704c458f 100644 --- a/frmts/hdf5/hdf5multidim.cpp +++ b/frmts/hdf5/hdf5multidim.cpp @@ -29,6 +29,8 @@ #include "hdf5eosparser.h" #include "s100.h" +#include "cpl_float.h" + #include #include #include @@ -159,7 +161,16 @@ BuildDataType(hid_t hDataType, bool &bHasString, bool &bNonNativeDataType, const auto klass = H5Tget_class(hDataType); GDALDataType eDT = ::HDF5Dataset::GetDataType(hDataType); if (eDT != GDT_Unknown) + { +#ifdef HDF5_HAVE_FLOAT16 + if (H5Tequal(hDataType, H5T_NATIVE_FLOAT16) || + HDF5Dataset::IsNativeCFloat16(hDataType)) + { + bNonNativeDataType = true; + } +#endif return GDALExtendedDataType::Create(eDT); + } else if (klass == H5T_STRING) { bHasString = true; @@ -2326,9 +2337,44 @@ static void CopyValue(const GByte *pabySrcBuffer, hid_t hSrcDataType, { if (dstDataType.GetClass() != GEDTC_COMPOUND) { + const auto eSrcDataType = ::HDF5Dataset::GetDataType(hSrcDataType); // Typically source is complex data type - auto srcDataType(GDALExtendedDataType::Create( - ::HDF5Dataset::GetDataType(hSrcDataType))); +#ifdef HDF5_HAVE_FLOAT16 + if (eSrcDataType == GDT_CFloat32 && + ::HDF5Dataset::IsNativeCFloat16(hSrcDataType)) + { + if (dstDataType.GetNumericDataType() == GDT_CFloat32) + { + for (int j = 0; j <= 1; ++j) + { + uint16_t nVal16; + memcpy(&nVal16, pabySrcBuffer + j * sizeof(nVal16), + sizeof(nVal16)); + const uint32_t nVal32 = CPLHalfToFloat(nVal16); + memcpy(pabyDstBuffer + j * sizeof(float), &nVal32, + sizeof(nVal32)); + } + } + else if (dstDataType.GetNumericDataType() == GDT_CFloat64) + { + for (int j = 0; j <= 1; ++j) + { + uint16_t nVal16; + memcpy(&nVal16, pabySrcBuffer + j * sizeof(nVal16), + sizeof(nVal16)); + const uint32_t nVal32 = CPLHalfToFloat(nVal16); + float fVal; + memcpy(&fVal, &nVal32, sizeof(fVal)); + double dfVal = fVal; + memcpy(pabyDstBuffer + j * sizeof(double), &dfVal, + sizeof(dfVal)); + } + } + return; + } + +#endif + auto srcDataType(GDALExtendedDataType::Create(eSrcDataType)); if (srcDataType.GetClass() == GEDTC_NUMERIC && srcDataType.GetNumericDataType() != GDT_Unknown) { @@ -2364,6 +2410,19 @@ static void CopyValue(const GByte *pabySrcBuffer, hid_t hSrcDataType, CopyValue(pabySrcBuffer, hParent, pabyDstBuffer, dstDataType, {}); H5Tclose(hParent); } +#ifdef HDF5_HAVE_FLOAT16 + else if (H5Tequal(hSrcDataType, H5T_NATIVE_FLOAT16)) + { + uint16_t nVal16; + memcpy(&nVal16, pabySrcBuffer, sizeof(nVal16)); + const uint32_t nVal32 = CPLHalfToFloat(nVal16); + float fVal; + memcpy(&fVal, &nVal32, sizeof(fVal)); + GDALExtendedDataType::CopyValue( + &fVal, GDALExtendedDataType::Create(GDT_Float32), pabyDstBuffer, + dstDataType); + } +#endif else { GDALDataType eDT = ::HDF5Dataset::GetDataType(hSrcDataType); From a8b37fa0eee03bb3c422e569817de04014855af9 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 29 May 2024 14:02:24 +0200 Subject: [PATCH 0011/1119] CSV: error out if invalid/inconsistent value for GEOMETRY layer creation option Fixes #10055 --- autotest/ogr/ogr_csv.py | 25 ++++++++++++++++++++++++ ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp | 10 ++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/autotest/ogr/ogr_csv.py b/autotest/ogr/ogr_csv.py index 0d861ffa26d3..80c536399840 100755 --- a/autotest/ogr/ogr_csv.py +++ b/autotest/ogr/ogr_csv.py @@ -3089,6 +3089,31 @@ def test_ogr_csv_geom_coord_precision_OGR_APPLY_GEOM_SET_PRECISION(tmp_vsimem): assert b"MULTIPOLYGON" in data +############################################################################### +# Test invalid GEOMETRY option + + +@gdaltest.enable_exceptions() +def test_ogr_csv_invalid_geometry_option(tmp_vsimem): + + filename = str(tmp_vsimem / "test.csv") + ds = gdal.GetDriverByName("CSV").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + with pytest.raises( + Exception, + match="Geometry type 3D Line String is not compatible with GEOMETRY=AS_XYZ", + ): + ds.CreateLayer( + "test", geom_type=ogr.wkbLineString25D, options=["GEOMETRY=AS_XYZ"] + ) + + filename = str(tmp_vsimem / "test2.csv") + ds = gdal.GetDriverByName("CSV").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + with gdal.quiet_errors(), pytest.raises( + Exception, match="Unsupported value foo for creation option GEOMETRY" + ): + ds.CreateLayer("test", geom_type=ogr.wkbLineString25D, options=["GEOMETRY=foo"]) + + ############################################################################### diff --git a/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp b/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp index 53e3ef5c2296..eeb267197098 100644 --- a/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp +++ b/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp @@ -1085,17 +1085,19 @@ OGRCSVDataSource::ICreateLayer(const char *pszLayerName, } else { - CPLError(CE_Warning, CPLE_AppDefined, + CPLError(CE_Failure, CPLE_AppDefined, "Geometry type %s is not compatible with " - "GEOMETRY=AS_XYZ.", - OGRGeometryTypeToName(eGType)); + "GEOMETRY=%s.", + OGRGeometryTypeToName(eGType), pszGeometry); + return nullptr; } } else { - CPLError(CE_Warning, CPLE_AppDefined, + CPLError(CE_Failure, CPLE_AppDefined, "Unsupported value %s for creation option GEOMETRY", pszGeometry); + return nullptr; } } From 27119019fd0901e4f78a3cc324e0424c8768b263 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 29 May 2024 16:54:47 +0200 Subject: [PATCH 0012/1119] GML: GML_SKIP_RESOLVE_ELEMS=HUGE: keep gml:id in .resolved.gml file --- ogr/ogrsf_frmts/gml/hugefileresolver.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ogr/ogrsf_frmts/gml/hugefileresolver.cpp b/ogr/ogrsf_frmts/gml/hugefileresolver.cpp index 1dac15fbb498..23f9ee2c63f1 100644 --- a/ogr/ogrsf_frmts/gml/hugefileresolver.cpp +++ b/ogr/ogrsf_frmts/gml/hugefileresolver.cpp @@ -1701,7 +1701,15 @@ static bool gmlHugeFileWriteResolved(huge_helper *helper, const int iPropCount = poClass->GetPropertyCount(); bool b_has_geom = false; - VSIFPrintfL(fp, " <%s>\n", poClass->GetElementName()); + VSIFPrintfL(fp, " <%s", poClass->GetElementName()); + const char *pszGmlId = poFeature->GetFID(); + if (pszGmlId) + { + char *gmlText = CPLEscapeString(pszGmlId, -1, CPLES_XML); + VSIFPrintfL(fp, " gml:id=\"%s\"", gmlText); + CPLFree(gmlText); + } + VSIFPrintfL(fp, ">\n"); for (int iProp = 0; iProp < iPropCount; iProp++) { From 3d111b732fb5ccceb821858c57614e1ca3f15ecf Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 28 May 2024 00:37:23 +0200 Subject: [PATCH 0013/1119] GML: avoid assertion due to trying to load existing .gfs file after reading .resolved.gml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refs #10015 --- ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp index cb33c90c291c..4ef9d4f6b47a 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp @@ -850,7 +850,8 @@ bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo) } // Can we find a GML Feature Schema (.gfs) for the input file? - if (!osGFSFilename.empty() && !bHaveSchema && osXSDFilename.empty()) + if (!osGFSFilename.empty() && !bHaveSchema && !bSchemaDone && + osXSDFilename.empty()) { VSIStatBufL sGFSStatBuf; if (bCheckAuxFile && VSIStatL(osGFSFilename, &sGFSStatBuf) == 0) From d74d338f98e0f00b842f1b1f65f46204c848a178 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 28 May 2024 00:38:26 +0200 Subject: [PATCH 0014/1119] GML: fix issue with nested elements with identical names in GML_SKIP_RESOLVE_ELEMS=HUGE mode Fixes #10015 --- .../data/gml/same_nested_property_name.gml | 14 +++++++++ autotest/ogr/ogr_gml.py | 29 +++++++++++++++++++ ogr/ogrsf_frmts/gml/hugefileresolver.cpp | 23 +++++++++++++-- 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 autotest/ogr/data/gml/same_nested_property_name.gml diff --git a/autotest/ogr/data/gml/same_nested_property_name.gml b/autotest/ogr/data/gml/same_nested_property_name.gml new file mode 100644 index 000000000000..11dffc32bc8d --- /dev/null +++ b/autotest/ogr/data/gml/same_nested_property_name.gml @@ -0,0 +1,14 @@ + + + + + 0 0 + foo + bar + + + diff --git a/autotest/ogr/ogr_gml.py b/autotest/ogr/ogr_gml.py index 0d592827c032..eadda1826340 100755 --- a/autotest/ogr/ogr_gml.py +++ b/autotest/ogr/ogr_gml.py @@ -1362,6 +1362,35 @@ def test_ogr_gml_38(tmp_path, resolver): ds = None +############################################################################### +# Test GML_SKIP_RESOLVE_ELEMS=HUGE with a file with 2 nested identical property +# names + + +@pytest.mark.require_driver("SQLite") +@pytest.mark.require_geos +def test_ogr_gml_huge_resolver_same_nested_property_name(tmp_path): + + shutil.copy( + "data/gml/same_nested_property_name.gml", + tmp_path, + ) + + def check_ds(ds): + lyr = ds.GetLayer(0) + f = lyr.GetNextFeature() + assert f["gml_id"] == "test.0" + assert f["test"] == "foo" + assert f["bar|test"] == "bar" + + ds = ogr.Open(tmp_path / "same_nested_property_name.gml") + check_ds(ds) + + with gdal.config_option("GML_SKIP_RESOLVE_ELEMS", "HUGE"): + ds = ogr.Open(tmp_path / "same_nested_property_name.gml") + check_ds(ds) + + ############################################################################### # Test parsing XSD where simpleTypes not inlined, but defined elsewhere in the .xsd (#4328) diff --git a/ogr/ogrsf_frmts/gml/hugefileresolver.cpp b/ogr/ogrsf_frmts/gml/hugefileresolver.cpp index 23f9ee2c63f1..971a9e57e4df 100644 --- a/ogr/ogrsf_frmts/gml/hugefileresolver.cpp +++ b/ogr/ogrsf_frmts/gml/hugefileresolver.cpp @@ -1723,8 +1723,27 @@ static bool gmlHugeFileWriteResolved(huge_helper *helper, { char *gmlText = CPLEscapeString( poProp->papszSubProperties[iSub], -1, CPLES_XML); - VSIFPrintfL(fp, " <%s>%s\n", pszPropName, gmlText, - pszPropName); + if (strchr(pszPropName, '|')) + { + const CPLStringList aosPropNameComps( + CSLTokenizeString2(pszPropName, "|", 0)); + VSIFPrintfL(fp, " "); + for (int i = 0; i < aosPropNameComps.size(); ++i) + { + VSIFPrintfL(fp, "<%s>", aosPropNameComps[i]); + } + VSIFPrintfL(fp, "%s", gmlText); + for (int i = aosPropNameComps.size() - 1; i >= 0; --i) + { + VSIFPrintfL(fp, "", aosPropNameComps[i]); + } + VSIFPrintfL(fp, "\n"); + } + else + { + VSIFPrintfL(fp, " <%s>%s\n", pszPropName, + gmlText, pszPropName); + } CPLFree(gmlText); } } From 8234d9d07d4eaac586a053da0d7443ff12a10de7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 29 May 2024 15:02:39 +0200 Subject: [PATCH 0015/1119] GML: make sure SRS is detected when using GML_SKIP_RESOLVE_ELEMS=HUGE on a AIXM file, otherwise GML_SWAP_COORDINATES might not work correctly --- ogr/ogrsf_frmts/gml/gmlreaderp.h | 5 +++++ ogr/ogrsf_frmts/gml/hugefileresolver.cpp | 23 +++++++++++++++++++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/ogr/ogrsf_frmts/gml/gmlreaderp.h b/ogr/ogrsf_frmts/gml/gmlreaderp.h index 39e80b1a84c2..9bba194cdf45 100644 --- a/ogr/ogrsf_frmts/gml/gmlreaderp.h +++ b/ogr/ogrsf_frmts/gml/gmlreaderp.h @@ -226,6 +226,11 @@ class GMLHandler const char *pszAttributeName) = 0; virtual char *GetAttributeByIdx(void *attr, unsigned int idx, char **ppszKey) = 0; + + GMLAppSchemaType GetAppSchemaType() const + { + return eAppSchemaType; + } }; #if defined(HAVE_XERCES) diff --git a/ogr/ogrsf_frmts/gml/hugefileresolver.cpp b/ogr/ogrsf_frmts/gml/hugefileresolver.cpp index 971a9e57e4df..b5e204ad794c 100644 --- a/ogr/ogrsf_frmts/gml/hugefileresolver.cpp +++ b/ogr/ogrsf_frmts/gml/hugefileresolver.cpp @@ -1586,6 +1586,7 @@ static bool gmlHugeResolveEdges(CPL_UNUSED huge_helper *helper, static bool gmlHugeFileWriteResolved(huge_helper *helper, const char *pszOutputFilename, GMLReader *pReader, + GMLAppSchemaType eAppSchemaType, int *m_nHasSequentialLayers) { // Open the resolved GML file for writing. @@ -1612,10 +1613,20 @@ static bool gmlHugeFileWriteResolved(huge_helper *helper, return false; } + const char *pszTopElement = "ResolvedTopoFeatureMembers"; + // For some specific application schema, GMLHandler has specific behavior, + // so re-use the root XML element it recognizes. + if (eAppSchemaType == APPSCHEMA_AIXM) + pszTopElement = "AIXMBasicMessage"; + else if (eAppSchemaType == APPSCHEMA_CITYGML) + pszTopElement = "CityModel"; + VSIFPrintfL(fp, "\n"); - VSIFPrintfL(fp, "\n"); + VSIFPrintfL(fp, + "<%s " + "xmlns:xlink=\"http://www.w3.org/1999/xlink\" " + "xmlns:gml=\"http://www.opengis.net/gml\">\n", + pszTopElement); VSIFPrintfL(fp, " \n"); int iOutCount = 0; @@ -1818,7 +1829,7 @@ static bool gmlHugeFileWriteResolved(huge_helper *helper, } VSIFPrintfL(fp, " \n"); - VSIFPrintfL(fp, "\n"); + VSIFPrintfL(fp, "\n", pszTopElement); VSIFCloseL(fp); @@ -1991,6 +2002,9 @@ bool GMLReader::ParseXMLHugeFile(const char *pszOutputFilename, return false; } + CPLAssert(m_poGMLHandler); + const GMLAppSchemaType eAppSchemaType = m_poGMLHandler->GetAppSchemaType(); + // Restarting the GML parser. if (!SetupParser()) { @@ -2000,6 +2014,7 @@ bool GMLReader::ParseXMLHugeFile(const char *pszOutputFilename, // Output: writing the revolved GML file. if (gmlHugeFileWriteResolved(&helper, pszOutputFilename, this, + eAppSchemaType, &m_nHasSequentialLayers) == false) { gmlHugeFileCleanUp(&helper); From 9201eec84a774d659a49dd53e0dabb32f18bfb84 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 29 May 2024 15:02:53 +0200 Subject: [PATCH 0016/1119] gml.rst: add note about deleting .gfs file --- doc/source/drivers/vector/gml.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/source/drivers/vector/gml.rst b/doc/source/drivers/vector/gml.rst index fa0c465308ba..a33969974d93 100644 --- a/doc/source/drivers/vector/gml.rst +++ b/doc/source/drivers/vector/gml.rst @@ -626,6 +626,14 @@ The following open options are supported: Whether to use gml:boundedBy at feature level as feature geometry, if there are no other geometry. + +.. note:: + + When changing the value of most of the above options, it is recommended to + delete the ``.gfs`` file if it pre-exists, otherwise mis-behavior might be + observed. + + Creation Issues --------------- From b982ac8a585a43b8f359136d81bd6f27d4b71287 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 29 May 2024 23:12:08 +0200 Subject: [PATCH 0017/1119] STACIT: honour MAX_ITEMS=0 as meaning unlimited as documented --- frmts/stacit/stacitdataset.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frmts/stacit/stacitdataset.cpp b/frmts/stacit/stacitdataset.cpp index 58f3f10c1f24..a4c0e46b053f 100644 --- a/frmts/stacit/stacitdataset.cpp +++ b/frmts/stacit/stacitdataset.cpp @@ -747,7 +747,9 @@ bool STACITDataset::Open(GDALOpenInfo *poOpenInfo) GIntBig nMaxItems = CPLAtoGIntBig(CSLFetchNameValueDef( poOpenInfo->papszOpenOptions, "MAX_ITEMS", "1000")); - if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "MAX_ITEMS") == nullptr) + const bool bMaxItemsSpecified = + CSLFetchNameValue(poOpenInfo->papszOpenOptions, "MAX_ITEMS") != nullptr; + if (!bMaxItemsSpecified) { // If the URL includes a limit parameter, and it's larger than our // default MAX_ITEMS value, then increase the later to the former. @@ -785,7 +787,7 @@ bool STACITDataset::Open(GDALOpenInfo *poOpenInfo) for (const auto &oFeature : oFeatures) { nItemIter++; - if (nItemIter > nMaxItems) + if (nMaxItems > 0 && nItemIter > nMaxItems) { break; } @@ -849,10 +851,9 @@ bool STACITDataset::Open(GDALOpenInfo *poOpenInfo) oMapCollection); } } - if (nItemIter >= nMaxItems) + if (nMaxItems > 0 && nItemIter >= nMaxItems) { - if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "MAX_ITEMS") == - nullptr) + if (!bMaxItemsSpecified) { CPLError(CE_Warning, CPLE_AppDefined, "Maximum number of items (" CPL_FRMT_GIB From 834c05f2fb655df7df7f146bd4eaa0176fca2b90 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 30 May 2024 14:32:30 +0200 Subject: [PATCH 0018/1119] STACIT: implement paging using POST method --- autotest/gdrivers/data/stacit/test.json | 2 +- autotest/gdrivers/data/stacit/test_page2.json | 2 +- autotest/gdrivers/stacit.py | 77 ++++++++++++++++++ frmts/stacit/stacitdataset.cpp | 81 ++++++++++++++++++- 4 files changed, 156 insertions(+), 6 deletions(-) diff --git a/autotest/gdrivers/data/stacit/test.json b/autotest/gdrivers/data/stacit/test.json index 5f5a0f6f84b8..120eaae8cfcb 100644 --- a/autotest/gdrivers/data/stacit/test.json +++ b/autotest/gdrivers/data/stacit/test.json @@ -14,7 +14,7 @@ "geometry": null, "properties": { "datetime": "2021-07-19T10:57:30Z", - "proj:epsg": 26711, + "proj:epsg": 26711 }, "collection": "my_collection", "assets": { diff --git a/autotest/gdrivers/data/stacit/test_page2.json b/autotest/gdrivers/data/stacit/test_page2.json index 003d2926f31d..7a4cc1bcdd84 100644 --- a/autotest/gdrivers/data/stacit/test_page2.json +++ b/autotest/gdrivers/data/stacit/test_page2.json @@ -13,7 +13,7 @@ "id": "int16", "geometry": null, "properties": { - "datetime": "2021-07-19T10:57:30Z", + "datetime": "2021-07-19T10:57:30Z" }, "collection": "my_collection", "assets": { diff --git a/autotest/gdrivers/stacit.py b/autotest/gdrivers/stacit.py index 6b0da1308bf3..5b7732b3b156 100755 --- a/autotest/gdrivers/stacit.py +++ b/autotest/gdrivers/stacit.py @@ -28,7 +28,10 @@ # DEALINGS IN THE SOFTWARE. ############################################################################### +import json + import pytest +import webserver from osgeo import gdal @@ -241,3 +244,77 @@ def test_stacit_overlapping_sources_with_nodata(): vrt = ds.GetMetadata("xml:VRT")[0] assert len(ds.GetFileList()) == 3 assert two_sources in vrt + + +# Launch a single webserver in a module-scoped fixture. +@pytest.fixture(scope="module") +def webserver_launch(): + + process, port = webserver.launch(handler=webserver.DispatcherHttpHandler) + + yield process, port + + webserver.server_stop(process, port) + + +@pytest.fixture(scope="function") +def webserver_port(webserver_launch): + + webserver_process, webserver_port = webserver_launch + + if webserver_port == 0: + pytest.skip() + yield webserver_port + + +@pytest.mark.require_curl +def test_stacit_post_paging(tmp_vsimem, webserver_port): + + initial_doc = { + "type": "FeatureCollection", + "stac_version": "1.0.0-beta.2", + "stac_extensions": [], + "features": json.loads(open("data/stacit/test.json", "rb").read())["features"], + "links": [ + { + "rel": "next", + "href": f"http://localhost:{webserver_port}/request", + "method": "POST", + "body": {"token": "page_2"}, + "headers": {"foo": "bar"}, + } + ], + } + + filename = str(tmp_vsimem / "tmp.json") + gdal.FileFromMemBuffer(filename, json.dumps(initial_doc)) + + next_page_doc = { + "type": "FeatureCollection", + "stac_version": "1.0.0-beta.2", + "stac_extensions": [], + "features": json.loads(open("data/stacit/test_page2.json", "rb").read())[ + "features" + ], + } + + handler = webserver.SequentialHandler() + handler.add( + "POST", + "/request", + 200, + {"Content-type": "application/json"}, + json.dumps(next_page_doc), + expected_headers={"Content-Type": "application/json", "foo": "bar"}, + expected_body=b'{\n "token":"page_2"\n}', + ) + with webserver.install_http_handler(handler): + ds = gdal.Open(filename) + assert ds is not None + assert ds.RasterCount == 1 + assert ds.RasterXSize == 40 + assert ds.RasterYSize == 20 + assert ds.GetSpatialRef().GetName() == "NAD27 / UTM zone 11N" + assert ds.GetGeoTransform() == pytest.approx( + [440720.0, 60.0, 0.0, 3751320.0, 0.0, -60.0], rel=1e-8 + ) diff --git a/frmts/stacit/stacitdataset.cpp b/frmts/stacit/stacitdataset.cpp index a4c0e46b053f..a8bc5abf358a 100644 --- a/frmts/stacit/stacitdataset.cpp +++ b/frmts/stacit/stacitdataset.cpp @@ -27,6 +27,7 @@ ****************************************************************************/ #include "cpl_json.h" +#include "cpl_http.h" #include "vrtdataset.h" #include "ogr_spatialref.h" @@ -763,13 +764,70 @@ bool STACITDataset::Open(GDALOpenInfo *poOpenInfo) } auto osCurFilename = osFilename; + std::string osMethod = "GET"; + CPLJSONObject oHeaders; + CPLJSONObject oBody; + bool bMerge = false; + int nLoops = 0; do { + ++nLoops; + if (nMaxItems > 0 && nLoops > nMaxItems) + { + break; + } + CPLJSONDocument oDoc; + if (STARTS_WITH(osCurFilename, "http://") || STARTS_WITH(osCurFilename, "https://")) { - if (!oDoc.LoadUrl(osCurFilename, nullptr)) + // Cf // Cf https://github.com/radiantearth/stac-api-spec/tree/release/v1.0.0/item-search#pagination + CPLStringList aosOptions; + if (oBody.IsValid() && + oBody.GetType() == CPLJSONObject::Type::Object) + { + if (bMerge) + CPLDebug("STACIT", + "Ignoring 'merge' attribute from next link"); + const std::string osPostContent = + oBody.Format(CPLJSONObject::PrettyFormat::Pretty); + aosOptions.SetNameValue("POSTFIELDS", osPostContent.c_str()); + } + aosOptions.SetNameValue("CUSTOMREQUEST", osMethod.c_str()); + CPLString osHeaders; + if (!oHeaders.IsValid() || + oHeaders.GetType() != CPLJSONObject::Type::Object || + oHeaders["Content-Type"].ToString().empty()) + { + osHeaders = "Content-Type: application/json"; + } + if (oHeaders.IsValid() && + oHeaders.GetType() == CPLJSONObject::Type::Object) + { + for (const auto &obj : oHeaders.GetChildren()) + { + osHeaders += "\r\n"; + osHeaders += obj.GetName(); + osHeaders += ": "; + osHeaders += obj.ToString(); + } + } + aosOptions.SetNameValue("HEADERS", osHeaders.c_str()); + CPLHTTPResult *psResult = + CPLHTTPFetch(osCurFilename.c_str(), aosOptions.List()); + if (!psResult) + return false; + if (!psResult->pabyData) + { + CPLHTTPDestroyResult(psResult); + return false; + } + const bool bOK = oDoc.LoadMemory( + reinterpret_cast(psResult->pabyData)); + // CPLDebug("STACIT", "Response: %s", reinterpret_cast(psResult->pabyData)); + CPLHTTPDestroyResult(psResult); + if (!bOK) return false; } else @@ -871,20 +929,35 @@ bool STACITDataset::Open(GDALOpenInfo *poOpenInfo) } // Follow next link + // Cf https://github.com/radiantearth/stac-api-spec/tree/release/v1.0.0/item-search#pagination const auto oLinks = oRoot.GetArray("links"); if (!oLinks.IsValid()) break; std::string osNewFilename; for (const auto &oLink : oLinks) { - if (oLink["rel"].ToString() == "next") + const auto osType = oLink["type"].ToString(); + if (oLink["rel"].ToString() == "next" && + (osType.empty() || osType == "application/geo+json")) { + osMethod = oLink.GetString("method", "GET"); osNewFilename = oLink["href"].ToString(); - break; + oHeaders = oLink["headers"]; + oBody = oLink["body"]; + bMerge = oLink.GetBool("merge", false); + if (osType == "application/geo+json") + { + break; + } } } - if (!osNewFilename.empty() && osNewFilename != osCurFilename) + if (!osNewFilename.empty() && + (osNewFilename != osCurFilename || + (oBody.IsValid() && + oBody.GetType() == CPLJSONObject::Type::Object))) + { osCurFilename = osNewFilename; + } else osCurFilename.clear(); } while (!osCurFilename.empty()); From eae8ae7ddf7652afbc4f2fcea3e4e2c6ac29e76c Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 30 May 2024 18:09:17 +0200 Subject: [PATCH 0019/1119] ogr2ogr: error out if GCP transform creation fails Fixes #10073 --- apps/ogr2ogr_lib.cpp | 3 +++ autotest/utilities/test_ogr2ogr_lib.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/apps/ogr2ogr_lib.cpp b/apps/ogr2ogr_lib.cpp index c2f4305a9d10..e8d739839e52 100644 --- a/apps/ogr2ogr_lib.cpp +++ b/apps/ogr2ogr_lib.cpp @@ -2752,6 +2752,9 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, { delete poGCPCoordTrans; poGCPCoordTrans = nullptr; + if (hDstDS == nullptr) + GDALClose(poODS); + return nullptr; } } diff --git a/autotest/utilities/test_ogr2ogr_lib.py b/autotest/utilities/test_ogr2ogr_lib.py index 0d614e2fb400..376dda667f6d 100755 --- a/autotest/utilities/test_ogr2ogr_lib.py +++ b/autotest/utilities/test_ogr2ogr_lib.py @@ -2710,3 +2710,17 @@ def test_ogr2ogr_lib_coordinate_precision_with_geom(): assert f.GetGeometryRef().ExportToWkt() == "LINESTRING (0 0,10 10)" else: assert f.GetGeometryRef().ExportToWkt() == "LINESTRING (1 1,9 9)" + + +############################################################################### + + +def test_ogr2ogr_lib_not_enough_gcp(): + + src_ds = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + src_ds.CreateLayer("test") + + with pytest.raises( + Exception, match="Failed to compute GCP transform: Not enough points available" + ): + gdal.VectorTranslate("", src_ds, options="-f Memory -gcp 0 0 0 0") From 28cd9102223a4a31c77dc31e26112e128801d32e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 30 May 2024 18:10:23 +0200 Subject: [PATCH 0020/1119] ogr2ogr: use more std::unique_ptr<> --- apps/ogr2ogr_lib.cpp | 76 +++++++++++--------------------------------- 1 file changed, 19 insertions(+), 57 deletions(-) diff --git a/apps/ogr2ogr_lib.cpp b/apps/ogr2ogr_lib.cpp index e8d739839e52..b8c60fe2b3a4 100644 --- a/apps/ogr2ogr_lib.cpp +++ b/apps/ogr2ogr_lib.cpp @@ -2641,6 +2641,10 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, } } + // Automatically close poODS on error, if it has been created by this + // method. + GDALDatasetUniquePtr poODSUniquePtr(hDstDS == nullptr ? poODS : nullptr); + // Some syntaxic sugar to make "ogr2ogr [-f PostgreSQL] PG:dbname=.... // source [srclayer] -lco OVERWRITE=YES" work like "ogr2ogr -overwrite // PG:dbname=.... source [srclayer]" The former syntax used to work at @@ -2658,8 +2662,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, { CPLError(CE_Failure, CPLE_AppDefined, "-append and -lco OVERWRITE=YES are mutually exclusive"); - if (hDstDS == nullptr) - GDALClose(poODS); return nullptr; } bOverwrite = true; @@ -2707,8 +2709,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, CPLError(CE_Failure, CPLE_AppDefined, "Failed to process SRS definition: %s", psOptions->osOutputSRSDef.c_str()); - if (hDstDS == nullptr) - GDALClose(poODS); return nullptr; } oOutputSRSHolder.get()->SetCoordinateEpoch( @@ -2729,8 +2729,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, CPLError(CE_Failure, CPLE_AppDefined, "Failed to process SRS definition: %s", psOptions->osSourceSRSDef.c_str()); - if (hDstDS == nullptr) - GDALClose(poODS); return nullptr; } oSourceSRS.SetCoordinateEpoch(psOptions->dfSourceCoordinateEpoch); @@ -2741,19 +2739,15 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, /* Create a transformation object from the source to */ /* destination coordinate system. */ /* -------------------------------------------------------------------- */ - GCPCoordTransformation *poGCPCoordTrans = nullptr; + std::unique_ptr poGCPCoordTrans; if (psOptions->oGCPs.nGCPCount > 0) { - poGCPCoordTrans = new GCPCoordTransformation( + poGCPCoordTrans = std::make_unique( psOptions->oGCPs.nGCPCount, psOptions->oGCPs.pasGCPs, psOptions->nTransformOrder, poSourceSRS ? poSourceSRS : oOutputSRSHolder.get()); if (!(poGCPCoordTrans->IsValid())) { - delete poGCPCoordTrans; - poGCPCoordTrans = nullptr; - if (hDstDS == nullptr) - GDALClose(poODS); return nullptr; } } @@ -2808,7 +2802,7 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, oTranslator.m_poOutputSRS = oOutputSRSHolder.get(); oTranslator.m_bNullifyOutputSRS = psOptions->bNullifyOutputSRS; oTranslator.m_poUserSourceSRS = poSourceSRS; - oTranslator.m_poGCPCoordTrans = poGCPCoordTrans; + oTranslator.m_poGCPCoordTrans = poGCPCoordTrans.get(); oTranslator.m_eGType = psOptions->eGType; oTranslator.m_eGeomTypeConversion = psOptions->eGeomTypeConversion; oTranslator.m_bMakeValid = psOptions->bMakeValid; @@ -2980,9 +2974,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, { CPLError(CE_Failure, CPLE_AppDefined, "-splitlistfields not supported in this mode"); - if (hDstDS == nullptr) - GDALClose(poODS); - delete poGCPCoordTrans; return nullptr; } @@ -2995,9 +2986,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, { CPLError(CE_Failure, CPLE_AppDefined, "Couldn't fetch requested layer %s!", pszLayer); - if (hDstDS == nullptr) - GDALClose(poODS); - delete poGCPCoordTrans; return nullptr; } } @@ -3047,9 +3035,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, { CPLError(CE_Failure, CPLE_AppDefined, "Couldn't fetch advertised layer %d!", iLayer); - if (hDstDS == nullptr) - GDALClose(poODS); - delete poGCPCoordTrans; return nullptr; } @@ -3088,9 +3073,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, { CPLError(CE_Failure, CPLE_AppDefined, "Couldn't fetch advertised layer %d!", iLayer); - if (hDstDS == nullptr) - GDALClose(poODS); - delete poGCPCoordTrans; return nullptr; } @@ -3109,9 +3091,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, poLayer->GetName()); if (!psOptions->bSkipFailures) { - if (hDstDS == nullptr) - GDALClose(poODS); - delete poGCPCoordTrans; return nullptr; } } @@ -3136,8 +3115,8 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, while (true) { OGRLayer *poFeatureLayer = nullptr; - OGRFeature *poFeature = poDS->GetNextFeature( - &poFeatureLayer, nullptr, pfnProgress, pProgressArg); + auto poFeature = std::unique_ptr(poDS->GetNextFeature( + &poFeatureLayer, nullptr, pfnProgress, pProgressArg)); if (poFeature == nullptr) break; std::map::const_iterator oIter = @@ -3145,7 +3124,7 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, if (oIter == oMapLayerToIdx.end()) { // Feature in a layer that is not a layer of interest. - OGRFeature::DestroyFeature(poFeature); + // nothing to do } else { @@ -3173,10 +3152,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, if (psInfo == nullptr && !psOptions->bSkipFailures) { - if (hDstDS == nullptr) - GDALClose(poODS); - delete poGCPCoordTrans; - OGRFeature::DestroyFeature(poFeature); return nullptr; } @@ -3189,9 +3164,9 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, int iLayer = oIter->second; TargetLayerInfo *psInfo = pasAssocLayers[iLayer].psInfo.get(); if ((psInfo == nullptr || - !oTranslator.Translate(poFeature, psInfo, 0, nullptr, - nTotalEventsDone, nullptr, nullptr, - psOptions.get())) && + !oTranslator.Translate(poFeature.release(), psInfo, 0, + nullptr, nTotalEventsDone, nullptr, + nullptr, psOptions.get())) && !psOptions->bSkipFailures) { CPLError( @@ -3204,8 +3179,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, nRetCode = 1; break; } - if (psInfo == nullptr) - OGRFeature::DestroyFeature(poFeature); } } // while true @@ -3233,9 +3206,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, if (psInfo == nullptr && !psOptions->bSkipFailures) { - if (hDstDS == nullptr) - GDALClose(poODS); - delete poGCPCoordTrans; return nullptr; } @@ -3265,9 +3235,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, { CPLError(CE_Failure, CPLE_AppDefined, "Couldn't fetch advertised layer %d!", iLayer); - if (hDstDS == nullptr) - GDALClose(poODS); - delete poGCPCoordTrans; return nullptr; } if (!poDS->IsLayerPrivate(iLayer)) @@ -3297,9 +3264,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, psOptions->aosLayers[iLayer]); if (!psOptions->bSkipFailures) { - if (hDstDS == nullptr) - GDALClose(poODS); - delete poGCPCoordTrans; return nullptr; } } @@ -3349,9 +3313,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, psOptions->osWHERE.c_str(), poLayer->GetName()); if (!psOptions->bSkipFailures) { - if (hDstDS == nullptr) - GDALClose(poODS); - delete poGCPCoordTrans; return nullptr; } } @@ -3505,8 +3466,6 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, } } - delete poGCPCoordTrans; - // Note: this guarantees that the file can be opened in a consistent state, // without requiring to close poODS, only if the driver declares // DCAP_FLUSHCACHE_CONSISTENT_STATE @@ -3514,10 +3473,13 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, nRetCode = 1; if (nRetCode == 0) - return GDALDataset::ToHandle(poODS); + { + if (hDstDS) + return hDstDS; + else + return GDALDataset::ToHandle(poODSUniquePtr.release()); + } - if (hDstDS == nullptr) - GDALClose(poODS); return nullptr; } From 002733b1e321b4a78af81f31593856ac7fcfecec Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 30 May 2024 18:23:43 +0200 Subject: [PATCH 0021/1119] GCPTransformer: accept only 2 points for order=1 transformer Fixes https://github.com/OSGeo/gdal/pull/10074 --- alg/gdal_crs.cpp | 16 ++++++++++++++++ autotest/utilities/test_ogr2ogr_lib.py | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/alg/gdal_crs.cpp b/alg/gdal_crs.cpp index 74ea4ceff9a9..9c134f0b69f5 100644 --- a/alg/gdal_crs.cpp +++ b/alg/gdal_crs.cpp @@ -220,6 +220,22 @@ static void *GDALCreateGCPTransformerEx(int nGCPCount, psInfo->nRefCount = 1; psInfo->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount); + if (nGCPCount == 2 && nReqOrder == 1 && + psInfo->asGCPs[0].X() != psInfo->asGCPs[1].X() && + psInfo->asGCPs[0].Y() != psInfo->asGCPs[1].Y()) + { + // Assumes that the 2 GCPs form opposite corners of a rectangle, + // and synthetize a 3rd corner + gdal::GCP newGCP; + newGCP.X() = psInfo->asGCPs[1].X(); + newGCP.Y() = psInfo->asGCPs[0].Y(); + newGCP.Pixel() = psInfo->asGCPs[1].Pixel(); + newGCP.Line() = psInfo->asGCPs[0].Line(); + psInfo->asGCPs.emplace_back(std::move(newGCP)); + + nGCPCount = 3; + pasGCPList = gdal::GCP::c_ptr(psInfo->asGCPs); + } memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE, strlen(GDAL_GTI2_SIGNATURE)); diff --git a/autotest/utilities/test_ogr2ogr_lib.py b/autotest/utilities/test_ogr2ogr_lib.py index 376dda667f6d..352564bd20f4 100755 --- a/autotest/utilities/test_ogr2ogr_lib.py +++ b/autotest/utilities/test_ogr2ogr_lib.py @@ -2724,3 +2724,23 @@ def test_ogr2ogr_lib_not_enough_gcp(): Exception, match="Failed to compute GCP transform: Not enough points available" ): gdal.VectorTranslate("", src_ds, options="-f Memory -gcp 0 0 0 0") + + +############################################################################### + + +def test_ogr2ogr_lib_two_gcps(): + + src_ds = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + src_lyr = src_ds.CreateLayer("test") + f = ogr.Feature(src_lyr.GetLayerDefn()) + f.SetGeometry(ogr.CreateGeometryFromWkt("POINT (2 3)")) + src_lyr.CreateFeature(f) + + out_ds = gdal.VectorTranslate( + "", src_ds, options="-f Memory -gcp 1 2 200 300 -gcp 3 4 300 400" + ) + out_lyr = out_ds.GetLayer(0) + f = out_lyr.GetNextFeature() + assert f.GetGeometryRef().GetX(0) == pytest.approx(250) + assert f.GetGeometryRef().GetY(0) == pytest.approx(350) From 3674ec7570f324ea43eef08b6f5ce3a29de2741d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 31 May 2024 02:31:27 +0200 Subject: [PATCH 0022/1119] ogr2ogr: do not call CreateLayer() with a geom field defn of type wkbNone Fixes #10071 --- apps/ogr2ogr_lib.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/ogr2ogr_lib.cpp b/apps/ogr2ogr_lib.cpp index c2f4305a9d10..2a97dd36963c 100644 --- a/apps/ogr2ogr_lib.cpp +++ b/apps/ogr2ogr_lib.cpp @@ -4550,8 +4550,10 @@ SetupTargetLayer::Setup(OGRLayer *poSrcLayer, const char *pszNewLayerName, oGeomFieldDefn.SetSpatialRef(poOutputSRS); oGeomFieldDefn.SetCoordinatePrecision(oCoordPrec); oGeomFieldDefn.SetNullable(bGeomFieldNullable); - poDstLayer = m_poDstDS->CreateLayer(pszNewLayerName, &oGeomFieldDefn, - papszLCOTemp); + poDstLayer = m_poDstDS->CreateLayer( + pszNewLayerName, + eGCreateLayerType == wkbNone ? nullptr : &oGeomFieldDefn, + papszLCOTemp); CSLDestroy(papszLCOTemp); if (poDstLayer == nullptr) From e2b64be975d1c2e99f2bb2a0fc6624a261ee1890 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 31 May 2024 02:32:26 +0200 Subject: [PATCH 0023/1119] FileGDB: be robust to be called with a geometry field definition of type wkbNone Fixes https://github.com/OSGeo/gdal/issues/10071 --- autotest/ogr/ogr_fgdb.py | 27 +++++++++++++++++++++++++++ ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp | 2 ++ 2 files changed, 29 insertions(+) diff --git a/autotest/ogr/ogr_fgdb.py b/autotest/ogr/ogr_fgdb.py index 24e7af30bf3d..12c47f51e3bb 100755 --- a/autotest/ogr/ogr_fgdb.py +++ b/autotest/ogr/ogr_fgdb.py @@ -3143,3 +3143,30 @@ def test_ogr_filegdb_write_geom_coord_precision(tmp_path): "HighPrecision": "true", } } + + +############################################################################### +# Test dummy use of CreateLayerFromGeomFieldDefn() with a geometry field +# definition of type wkbNone + + +def test_ogr_filegdb_CreateLayerFromGeomFieldDefn_geom_type_none(tmp_path): + + filename = str(tmp_path / "test.gdb") + ds = gdal.GetDriverByName("FileGDB").Create(filename, 0, 0, 0, gdal.GDT_Unknown) + geom_fld = ogr.GeomFieldDefn("geometry", ogr.wkbNone) + ds.CreateLayerFromGeomFieldDefn("test", geom_fld) + ds.Close() + + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + assert lyr.GetGeomType() == ogr.wkbNone + ds.Close() + + filename2 = str(tmp_path / "test2.gdb") + gdal.VectorTranslate(filename2, filename, format="FileGDB") + + ds = ogr.Open(filename2) + lyr = ds.GetLayer(0) + assert lyr.GetGeomType() == ogr.wkbNone + ds.Close() diff --git a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp index 4ed3c46cc4f3..7c7480f88b7a 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp @@ -2399,6 +2399,8 @@ bool FGdbLayer::Create(FGdbDataSource *pParentDataSource, const auto eType = poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone; + if (eType == wkbNone) + poSrcGeomFieldDefn = nullptr; #ifdef EXTENT_WORKAROUND m_bLayerJustCreated = true; From c61e4bb337c769f148f5eb9f9a983563afc891cd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 31 May 2024 17:20:02 +0200 Subject: [PATCH 0024/1119] ESRIJSON: fix paging when URL contains f=pjson instead of f=json Fixes #10094 --- ogr/ogrsf_frmts/geojson/ogresrijsonreader.cpp | 6 +- ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp | 133 ++++++++---------- ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp | 4 +- 3 files changed, 69 insertions(+), 74 deletions(-) diff --git a/ogr/ogrsf_frmts/geojson/ogresrijsonreader.cpp b/ogr/ogrsf_frmts/geojson/ogresrijsonreader.cpp index 464213b3a801..c3ca039e6106 100644 --- a/ogr/ogrsf_frmts/geojson/ogresrijsonreader.cpp +++ b/ogr/ogrsf_frmts/geojson/ogresrijsonreader.cpp @@ -235,7 +235,11 @@ bool OGRESRIJSONReader::ParseField(json_object *poObj) OGRFieldSubType eFieldSubType = OFSTNone; const char *pszObjName = json_object_get_string(poObjName); const char *pszObjType = json_object_get_string(poObjType); - if (EQUAL(pszObjType, "esriFieldTypeOID")) + if (EQUAL(pszObjType, "esriFieldTypeString")) + { + // do nothing + } + else if (EQUAL(pszObjType, "esriFieldTypeOID")) { eFieldType = OFTInteger; poLayer_->SetFIDColumn(pszObjName); diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp index 5c303f0195f1..74504d7d6edf 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp @@ -31,6 +31,7 @@ #include #include +#include #include "cpl_conv.h" #include "cpl_error.h" @@ -95,18 +96,20 @@ class OGRESRIFeatureServiceLayer final : public OGRLayer class OGRESRIFeatureServiceDataset final : public GDALDataset { - CPLString osURL; - GIntBig nFirstOffset; - GIntBig nLastOffset; - OGRGeoJSONDataSource *poCurrent; - OGRESRIFeatureServiceLayer *poLayer; + CPLString m_osURL{}; + GIntBig m_nFirstOffset = 0; + GIntBig m_nLastOffset = 0; + std::unique_ptr m_poCurrent{}; + std::unique_ptr m_poLayer{}; + GeoJSONSourceType m_nSrcType = eGeoJSONSourceUnknown; - int LoadPage(); + bool LoadPage(); public: - OGRESRIFeatureServiceDataset(const CPLString &osURL, - OGRGeoJSONDataSource *poFirst); - ~OGRESRIFeatureServiceDataset(); + OGRESRIFeatureServiceDataset( + const std::string &osURL, + std::unique_ptr &&poFirst, + GeoJSONSourceType nSrcType); int GetLayerCount() override { @@ -115,20 +118,20 @@ class OGRESRIFeatureServiceDataset final : public GDALDataset OGRLayer *GetLayer(int nLayer) override { - return (nLayer == 0) ? poLayer : nullptr; + return (nLayer == 0) ? m_poLayer.get() : nullptr; } OGRLayer *GetUnderlyingLayer() { - return poCurrent->GetLayer(0); + return m_poCurrent->GetLayer(0); } - int MyResetReading(); - int LoadNextPage(); + bool MyResetReading(); + bool LoadNextPage(); - const CPLString &GetURL() + const CPLString &GetURL() const { - return osURL; + return m_osURL; } }; @@ -350,58 +353,49 @@ OGRErr OGRESRIFeatureServiceLayer::GetExtent(OGREnvelope *psExtent, int bForce) /************************************************************************/ OGRESRIFeatureServiceDataset::OGRESRIFeatureServiceDataset( - const CPLString &osURLIn, OGRGeoJSONDataSource *poFirst) - : poCurrent(poFirst) + const std::string &osURL, std::unique_ptr &&poFirst, + GeoJSONSourceType nSrcType) + : m_osURL(osURL), m_poCurrent(std::move(poFirst)), m_nSrcType(nSrcType) { - poLayer = new OGRESRIFeatureServiceLayer(this); - osURL = osURLIn; - if (CPLURLGetValue(osURL, "resultRecordCount").empty()) + m_poLayer = std::make_unique(this); + if (CPLURLGetValue(m_osURL, "resultRecordCount").empty()) { // We assume that if the server sets the exceededTransferLimit, the // and resultRecordCount is not set, the number of features returned // in our first request is the maximum allowed by the server // So set it for following requests. - osURL = CPLURLAddKVP( - this->osURL, "resultRecordCount", + m_osURL = CPLURLAddKVP( + m_osURL, "resultRecordCount", CPLSPrintf("%d", static_cast( - poFirst->GetLayer(0)->GetFeatureCount()))); + m_poCurrent->GetLayer(0)->GetFeatureCount()))); } else { const int nUserSetRecordCount = - atoi(CPLURLGetValue(osURL, "resultRecordCount")); - if (nUserSetRecordCount > poFirst->GetLayer(0)->GetFeatureCount()) + atoi(CPLURLGetValue(m_osURL, "resultRecordCount")); + if (nUserSetRecordCount > m_poCurrent->GetLayer(0)->GetFeatureCount()) { - CPLError(CE_Warning, CPLE_AppDefined, - "Specified resultRecordCount=%d is greater than " - "the maximum %d supported by the server", - nUserSetRecordCount, - static_cast(poFirst->GetLayer(0)->GetFeatureCount())); + CPLError( + CE_Warning, CPLE_AppDefined, + "Specified resultRecordCount=%d is greater than " + "the maximum %d supported by the server", + nUserSetRecordCount, + static_cast(m_poCurrent->GetLayer(0)->GetFeatureCount())); } } - nFirstOffset = CPLAtoGIntBig(CPLURLGetValue(osURL, "resultOffset")); - nLastOffset = nFirstOffset; -} - -/************************************************************************/ -/* ~OGRESRIFeatureServiceDataset() */ -/************************************************************************/ - -OGRESRIFeatureServiceDataset::~OGRESRIFeatureServiceDataset() -{ - delete poCurrent; - delete poLayer; + m_nFirstOffset = CPLAtoGIntBig(CPLURLGetValue(m_osURL, "resultOffset")); + m_nLastOffset = m_nFirstOffset; } /************************************************************************/ /* MyResetReading() */ /************************************************************************/ -int OGRESRIFeatureServiceDataset::MyResetReading() +bool OGRESRIFeatureServiceDataset::MyResetReading() { - if (nLastOffset > nFirstOffset) + if (m_nLastOffset > m_nFirstOffset) { - nLastOffset = nFirstOffset; + m_nLastOffset = m_nFirstOffset; return LoadPage(); } @@ -409,29 +403,32 @@ int OGRESRIFeatureServiceDataset::MyResetReading() #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnull-dereference" #endif - poCurrent->GetLayer(0)->ResetReading(); + m_poCurrent->GetLayer(0)->ResetReading(); #if defined(__GNUC__) #pragma GCC diagnostic pop #endif - return TRUE; + return true; } /************************************************************************/ /* LoadNextPage() */ /************************************************************************/ -int OGRESRIFeatureServiceDataset::LoadNextPage() +bool OGRESRIFeatureServiceDataset::LoadNextPage() { - if (!poCurrent->HasOtherPages()) - return FALSE; + if (!m_poCurrent->HasOtherPages()) + return false; #if defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnull-dereference" #endif - nLastOffset += poCurrent->GetLayer(0)->GetFeatureCount(); + const auto nCurPageFC = m_poCurrent->GetLayer(0)->GetFeatureCount(); #if defined(__GNUC__) #pragma GCC diagnostic pop #endif + if (m_nLastOffset > std::numeric_limits::max() - nCurPageFC) + return false; + m_nLastOffset += nCurPageFC; return LoadPage(); } @@ -439,27 +436,19 @@ int OGRESRIFeatureServiceDataset::LoadNextPage() /* LoadPage() */ /************************************************************************/ -int OGRESRIFeatureServiceDataset::LoadPage() +bool OGRESRIFeatureServiceDataset::LoadPage() { - CPLString osNewURL = CPLURLAddKVP(osURL, "resultOffset", - CPLSPrintf(CPL_FRMT_GIB, nLastOffset)); - OGRGeoJSONDataSource *poDS = new OGRGeoJSONDataSource(); + CPLString osNewURL = CPLURLAddKVP(m_osURL, "resultOffset", + CPLSPrintf(CPL_FRMT_GIB, m_nLastOffset)); + auto poDS = std::make_unique(); GDALOpenInfo oOpenInfo(osNewURL, GA_ReadOnly); - GeoJSONSourceType nSrcType; - if (EQUAL(poCurrent->GetJSonFlavor(), "GeoJSON")) - nSrcType = GeoJSONGetSourceType(&oOpenInfo); - else - nSrcType = ESRIJSONDriverGetSourceType(&oOpenInfo); - if (!poDS->Open(&oOpenInfo, nSrcType, poCurrent->GetJSonFlavor()) || + if (!poDS->Open(&oOpenInfo, m_nSrcType, m_poCurrent->GetJSonFlavor()) || poDS->GetLayerCount() == 0) { - delete poDS; - poDS = nullptr; - return FALSE; + return false; } - delete poCurrent; - poCurrent = poDS; - return TRUE; + m_poCurrent = std::move(poDS); + return true; } /************************************************************************/ @@ -537,7 +526,7 @@ GDALDataset *OGRGeoJSONDriverOpenInternal(GDALOpenInfo *poOpenInfo, GeoJSONSourceType nSrcType, const char *pszJSonFlavor) { - OGRGeoJSONDataSource *poDS = new OGRGeoJSONDataSource(); + auto poDS = std::make_unique(); /* -------------------------------------------------------------------- */ /* Processing configuration options. */ @@ -567,8 +556,7 @@ GDALDataset *OGRGeoJSONDriverOpenInternal(GDALOpenInfo *poOpenInfo, /* -------------------------------------------------------------------- */ if (!poDS->Open(poOpenInfo, nSrcType, pszJSonFlavor)) { - delete poDS; - poDS = nullptr; + poDS.reset(); } if (poDS != nullptr && poDS->HasOtherPages()) @@ -587,12 +575,13 @@ GDALDataset *OGRGeoJSONDriverOpenInternal(GDALOpenInfo *poOpenInfo, (pszFSP == nullptr || CPLTestBool(pszFSP))) || (bHasResultOffset && pszFSP != nullptr && CPLTestBool(pszFSP))) { - return new OGRESRIFeatureServiceDataset(pszFilename, poDS); + return new OGRESRIFeatureServiceDataset( + pszFilename, std::move(poDS), nSrcType); } } } - return poDS; + return poDS.release(); } /************************************************************************/ diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp index 4ec6ba2b3ae6..7923bc3d5892 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp @@ -628,7 +628,9 @@ static bool IsLikelyESRIJSONURL(const char *pszURL) { // URLs with f=json are strong candidates for ESRI JSON services // except if they have "/items?", in which case they are likely OAPIF - return strstr(pszURL, "f=json") != nullptr && + return (strstr(pszURL, "f=json") != nullptr || + strstr(pszURL, "f=pjson") != nullptr || + strstr(pszURL, "resultRecordCount=") != nullptr) && strstr(pszURL, "/items?") == nullptr; } From 6ab0684bc749e67442c03448ad9fdb90ce577835 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 25 May 2024 02:07:44 +0200 Subject: [PATCH 0025/1119] PG: avoid errors related to ogr_system_tables.metadata when user has not enough permissions Fixes #9994 --- autotest/ogr/ogr_pg.py | 68 +++++++++- doc/source/drivers/vector/pg.rst | 9 ++ ogr/ogrsf_frmts/pg/ogr_pg.h | 9 +- ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp | 171 ++++++++++++++++++++++++- ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp | 52 ++++---- 5 files changed, 279 insertions(+), 30 deletions(-) diff --git a/autotest/ogr/ogr_pg.py b/autotest/ogr/ogr_pg.py index b3700c8ab97f..3d139fdda891 100755 --- a/autotest/ogr/ogr_pg.py +++ b/autotest/ogr/ogr_pg.py @@ -4855,7 +4855,8 @@ def test_ogr_pg_84(pg_ds): @only_without_postgis -def test_ogr_pg_metadata(pg_ds): +@pytest.mark.parametrize("run_number", [1, 2]) +def test_ogr_pg_metadata(pg_ds, run_number): pg_ds = reconnect(pg_ds, update=1) pg_ds.StartTransaction() @@ -4889,6 +4890,71 @@ def test_ogr_pg_metadata(pg_ds): assert lyr.GetMetadata_Dict() == {} +############################################################################### +# Test reading/writing metadata with a user with limited rights + + +@only_without_postgis +def test_ogr_pg_metadata_restricted_user(pg_ds): + + lyr = pg_ds.CreateLayer( + "test_ogr_pg_metadata_restricted_user", + geom_type=ogr.wkbPoint, + options=["OVERWRITE=YES"], + ) + lyr.SetMetadata({"foo": "bar"}) + + pg_ds = reconnect(pg_ds, update=1) + + try: + + pg_ds.ExecuteSQL("CREATE ROLE test_ogr_pg_metadata_restricted_user") + with pg_ds.ExecuteSQL("SELECT current_schema()") as lyr: + f = lyr.GetNextFeature() + current_schema = f.GetField(0) + pg_ds.ExecuteSQL( + f"GRANT ALL PRIVILEGES ON SCHEMA {current_schema} TO test_ogr_pg_metadata_restricted_user" + ) + pg_ds.ExecuteSQL("SET ROLE test_ogr_pg_metadata_restricted_user") + + lyr = pg_ds.GetLayerByName("test_ogr_pg_metadata_restricted_user") + gdal.ErrorReset() + with gdal.quiet_errors(): + assert lyr.GetMetadata() == {} + assert ( + gdal.GetLastErrorMsg() + == "Table ogr_system_tables.metadata exists but user lacks USAGE privilege on ogr_system_tables schema" + ) + + pg_ds = reconnect(pg_ds, update=1) + pg_ds.ExecuteSQL("SET ROLE test_ogr_pg_metadata_restricted_user") + + lyr = pg_ds.CreateLayer( + "test_ogr_pg_metadata_restricted_user_bis", + geom_type=ogr.wkbPoint, + options=["OVERWRITE=YES"], + ) + with gdal.quiet_errors(): + lyr.SetMetadata({"foo": "bar"}) + + gdal.ErrorReset() + pg_ds = reconnect(pg_ds, update=1) + assert gdal.GetLastErrorMsg() == "" + + finally: + pg_ds = reconnect(pg_ds, update=1) + pg_ds.ExecuteSQL("DELLAYER:test_ogr_pg_metadata_restricted_user") + pg_ds.ExecuteSQL("DELLAYER:test_ogr_pg_metadata_restricted_user_bis") + with pg_ds.ExecuteSQL("SELECT CURRENT_USER") as lyr: + f = lyr.GetNextFeature() + current_user = f.GetField(0) + pg_ds.ExecuteSQL( + f"REASSIGN OWNED BY test_ogr_pg_metadata_restricted_user TO {current_user}" + ) + pg_ds.ExecuteSQL("DROP OWNED BY test_ogr_pg_metadata_restricted_user") + pg_ds.ExecuteSQL("DROP ROLE test_ogr_pg_metadata_restricted_user") + + ############################################################################### # Test append of several layers in PG_USE_COPY mode (#6411) diff --git a/doc/source/drivers/vector/pg.rst b/doc/source/drivers/vector/pg.rst index b5b2f44b703b..7c709f76e475 100644 --- a/doc/source/drivers/vector/pg.rst +++ b/doc/source/drivers/vector/pg.rst @@ -455,6 +455,15 @@ The following configuration options are available: be destroyed. Typical use case: ``ogr2ogr -append PG:dbname=foo abc.shp --config OGR_TRUNCATE YES``. +- .. config:: OGR_PG_ENABLE_METADATA + :choices: YES, NO + :default: YES + :since: 3.9 + + If set to "YES" (the default), the driver will try to use (and potentially + create) the ``ogr_system_tables.metadata`` table to retrieve and store + layer metadata. + Examples ~~~~~~~~ diff --git a/ogr/ogrsf_frmts/pg/ogr_pg.h b/ogr/ogrsf_frmts/pg/ogr_pg.h index 014f0826602c..393afb5f9dec 100644 --- a/ogr/ogrsf_frmts/pg/ogr_pg.h +++ b/ogr/ogrsf_frmts/pg/ogr_pg.h @@ -641,6 +641,12 @@ class OGRPGDataSource final : public OGRDataSource bool m_bOgrSystemTablesMetadataTableExistenceTested = false; bool m_bOgrSystemTablesMetadataTableFound = false; + bool m_bCreateMetadataTableIfNeededRun = false; + bool m_bCreateMetadataTableIfNeededSuccess = false; + + bool m_bHasWritePermissionsOnMetadataTableRun = false; + bool m_bHasWritePermissionsOnMetadataTableSuccess = false; + void LoadTables(); CPLString osDebugLastTransactionCommand{}; @@ -749,8 +755,9 @@ class OGRPGDataSource final : public OGRDataSource return bUserTransactionActive; } - void CreateOgrSystemTablesMetadataTableIfNeeded(); + bool CreateMetadataTableIfNeeded(); bool HasOgrSystemTablesMetadataTable(); + bool HasWritePermissionsOnMetadataTable(); }; #endif /* ndef OGR_PG_H_INCLUDED */ diff --git a/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp b/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp index dbb0014bc71f..62415a574683 100644 --- a/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp +++ b/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp @@ -3161,13 +3161,69 @@ OGRErr OGRPGDataSource::EndCopy() } /************************************************************************/ -/* CreateOgrSystemTablesMetadataTableIfNeeded() */ +/* CreateMetadataTableIfNeeded() */ /************************************************************************/ -void OGRPGDataSource::CreateOgrSystemTablesMetadataTableIfNeeded() +bool OGRPGDataSource::CreateMetadataTableIfNeeded() { - PGresult *hResult = + if (m_bCreateMetadataTableIfNeededRun) + return m_bCreateMetadataTableIfNeededSuccess; + + m_bCreateMetadataTableIfNeededRun = true; + + PGresult *hResult; + + hResult = OGRPG_PQexec( + hPGConn, + "SELECT c.oid FROM pg_class c " + "JOIN pg_namespace n ON c.relnamespace=n.oid " + "WHERE c.relname = 'metadata' AND n.nspname = 'ogr_system_tables'"); + const bool bFound = + (hResult && PQntuples(hResult) == 1 && !PQgetisnull(hResult, 0, 0)); + OGRPGClearResult(hResult); + + hResult = OGRPG_PQexec( + hPGConn, + "SELECT has_database_privilege((select current_database()), 'CREATE')"); + const bool bCanCreateSchema = + (hResult && PQntuples(hResult) == 1 && !PQgetisnull(hResult, 0, 0) && + strcmp(PQgetvalue(hResult, 0, 0), "t") == 0); + OGRPGClearResult(hResult); + + if (!bFound) + { + if (!bCanCreateSchema) + { + CPLError(CE_Warning, CPLE_AppDefined, + "User lacks CREATE SCHEMA privilege to be able to create " + "ogr_system_tables.metadata table"); + return false; + } + } + else + { + if (!HasWritePermissionsOnMetadataTable()) + { + return false; + } + if (!bCanCreateSchema) + { + CPLError(CE_Warning, CPLE_AppDefined, + "User lacks CREATE SCHEMA privilege. Assuming " + "ogr_system_tables.metadata table has correct structure"); + m_bCreateMetadataTableIfNeededSuccess = true; + return true; + } + } + + hResult = OGRPG_PQexec(hPGConn, "CREATE SCHEMA IF NOT EXISTS ogr_system_tables"); + if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK && + PQresultStatus(hResult) != PGRES_TUPLES_OK)) + { + OGRPGClearResult(hResult); + return false; + } OGRPGClearResult(hResult); hResult = OGRPG_PQexec( @@ -3177,12 +3233,24 @@ void OGRPGDataSource::CreateOgrSystemTablesMetadataTableIfNeeded() "table_name TEXT NOT NULL, " "metadata TEXT," "UNIQUE(schema_name, table_name))"); + if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK && + PQresultStatus(hResult) != PGRES_TUPLES_OK)) + { + OGRPGClearResult(hResult); + return false; + } OGRPGClearResult(hResult); hResult = OGRPG_PQexec( hPGConn, "DROP FUNCTION IF EXISTS " "ogr_system_tables.event_trigger_function_for_metadata() CASCADE"); + if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK && + PQresultStatus(hResult) != PGRES_TUPLES_OK)) + { + OGRPGClearResult(hResult); + return false; + } OGRPGClearResult(hResult); hResult = OGRPG_PQexec( @@ -3193,6 +3261,9 @@ void OGRPGDataSource::CreateOgrSystemTablesMetadataTableIfNeeded() "DECLARE\n" " obj record;\n" "BEGIN\n" + " IF has_schema_privilege('ogr_system_tables', 'USAGE') THEN\n" + " IF has_table_privilege('ogr_system_tables.metadata', 'DELETE') " + "THEN\n" " FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()\n" " LOOP\n" " IF obj.object_type = 'table' THEN\n" @@ -3201,13 +3272,27 @@ void OGRPGDataSource::CreateOgrSystemTablesMetadataTableIfNeeded() "obj.object_name;\n" " END IF;\n" " END LOOP;\n" + " END IF;\n" + " END IF;\n" "END;\n" "$$;"); + if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK && + PQresultStatus(hResult) != PGRES_TUPLES_OK)) + { + OGRPGClearResult(hResult); + return false; + } OGRPGClearResult(hResult); hResult = OGRPG_PQexec(hPGConn, "DROP EVENT TRIGGER IF EXISTS " "ogr_system_tables_event_trigger_for_metadata"); + if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK && + PQresultStatus(hResult) != PGRES_TUPLES_OK)) + { + OGRPGClearResult(hResult); + return false; + } OGRPGClearResult(hResult); hResult = OGRPG_PQexec( @@ -3216,7 +3301,18 @@ void OGRPGDataSource::CreateOgrSystemTablesMetadataTableIfNeeded() "ON sql_drop " "EXECUTE FUNCTION " "ogr_system_tables.event_trigger_function_for_metadata()"); + if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK && + PQresultStatus(hResult) != PGRES_TUPLES_OK)) + { + OGRPGClearResult(hResult); + return false; + } OGRPGClearResult(hResult); + + m_bCreateMetadataTableIfNeededSuccess = true; + m_bOgrSystemTablesMetadataTableExistenceTested = true; + m_bOgrSystemTablesMetadataTableFound = true; + return true; } /************************************************************************/ @@ -3236,9 +3332,76 @@ bool OGRPGDataSource::HasOgrSystemTablesMetadataTable() "SELECT c.oid FROM pg_class c " "JOIN pg_namespace n ON c.relnamespace=n.oid " "WHERE c.relname = 'metadata' AND n.nspname = 'ogr_system_tables'"); - m_bOgrSystemTablesMetadataTableFound = + const bool bFound = (hResult && PQntuples(hResult) == 1 && !PQgetisnull(hResult, 0, 0)); OGRPGClearResult(hResult); + if (!bFound) + return false; + + hResult = OGRPG_PQexec( + hPGConn, + "SELECT has_schema_privilege('ogr_system_tables', 'USAGE')"); + const bool bHasSchemaPrivilege = + (hResult && PQntuples(hResult) == 1 && + !PQgetisnull(hResult, 0, 0) && + strcmp(PQgetvalue(hResult, 0, 0), "t") == 0); + OGRPGClearResult(hResult); + if (!bHasSchemaPrivilege) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Table ogr_system_tables.metadata exists but user lacks " + "USAGE privilege on ogr_system_tables schema"); + return false; + } + + hResult = OGRPG_PQexec( + hPGConn, "SELECT has_table_privilege('ogr_system_tables.metadata', " + "'SELECT')"); + m_bOgrSystemTablesMetadataTableFound = + (hResult && PQntuples(hResult) == 1 && + !PQgetisnull(hResult, 0, 0) && + strcmp(PQgetvalue(hResult, 0, 0), "t") == 0); + OGRPGClearResult(hResult); + if (!m_bOgrSystemTablesMetadataTableFound) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Table ogr_system_tables.metadata exists but user lacks " + "SELECT privilege on it"); + } } return m_bOgrSystemTablesMetadataTableFound; } + +/************************************************************************/ +/* HasWritePermissionsOnMetadataTable() */ +/************************************************************************/ + +bool OGRPGDataSource::HasWritePermissionsOnMetadataTable() +{ + if (!m_bHasWritePermissionsOnMetadataTableRun) + { + m_bHasWritePermissionsOnMetadataTableRun = true; + + if (HasOgrSystemTablesMetadataTable()) + { + PGresult *hResult = OGRPG_PQexec( + hPGConn, + "SELECT has_table_privilege('ogr_system_tables.metadata', " + "'INSERT') " + "AND has_table_privilege('ogr_system_tables.metadata', " + "'DELETE')"); + m_bHasWritePermissionsOnMetadataTableSuccess = + (hResult && PQntuples(hResult) == 1 && + !PQgetisnull(hResult, 0, 0) && + strcmp(PQgetvalue(hResult, 0, 0), "t") == 0); + OGRPGClearResult(hResult); + if (!m_bHasWritePermissionsOnMetadataTableSuccess) + { + CPLError(CE_Warning, CPLE_AppDefined, + "User lacks INSERT and/OR DELETE privilege on " + "ogr_system_tables.metadata table"); + } + } + } + return m_bHasWritePermissionsOnMetadataTableSuccess; +} diff --git a/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp b/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp index 2ac569c03c3d..da4a8ff1533a 100644 --- a/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp +++ b/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp @@ -355,34 +355,38 @@ void OGRPGTableLayer::SerializeMetadata() if (psMD) { - poDS->CreateOgrSystemTablesMetadataTableIfNeeded(); - - CPLString osCommand; - osCommand.Printf("DELETE FROM ogr_system_tables.metadata WHERE " - "schema_name = %s AND table_name = %s", - OGRPGEscapeString(hPGConn, pszSchemaName).c_str(), - OGRPGEscapeString(hPGConn, pszTableName).c_str()); - PGresult *hResult = OGRPG_PQexec(hPGConn, osCommand.c_str()); - OGRPGClearResult(hResult); + if (poDS->CreateMetadataTableIfNeeded() && + poDS->HasWritePermissionsOnMetadataTable()) + { + CPLString osCommand; + osCommand.Printf("DELETE FROM ogr_system_tables.metadata WHERE " + "schema_name = %s AND table_name = %s", + OGRPGEscapeString(hPGConn, pszSchemaName).c_str(), + OGRPGEscapeString(hPGConn, pszTableName).c_str()); + PGresult *hResult = OGRPG_PQexec(hPGConn, osCommand.c_str()); + OGRPGClearResult(hResult); - CPLXMLNode *psRoot = - CPLCreateXMLNode(nullptr, CXT_Element, "GDALMetadata"); - CPLAddXMLChild(psRoot, psMD); - char *pszXML = CPLSerializeXMLTree(psRoot); - // CPLDebug("PG", "Serializing %s", pszXML); + CPLXMLNode *psRoot = + CPLCreateXMLNode(nullptr, CXT_Element, "GDALMetadata"); + CPLAddXMLChild(psRoot, psMD); + char *pszXML = CPLSerializeXMLTree(psRoot); + // CPLDebug("PG", "Serializing %s", pszXML); - osCommand.Printf("INSERT INTO ogr_system_tables.metadata (schema_name, " - "table_name, metadata) VALUES (%s, %s, %s)", - OGRPGEscapeString(hPGConn, pszSchemaName).c_str(), - OGRPGEscapeString(hPGConn, pszTableName).c_str(), - OGRPGEscapeString(hPGConn, pszXML).c_str()); - hResult = OGRPG_PQexec(hPGConn, osCommand.c_str()); - OGRPGClearResult(hResult); + osCommand.Printf( + "INSERT INTO ogr_system_tables.metadata (schema_name, " + "table_name, metadata) VALUES (%s, %s, %s)", + OGRPGEscapeString(hPGConn, pszSchemaName).c_str(), + OGRPGEscapeString(hPGConn, pszTableName).c_str(), + OGRPGEscapeString(hPGConn, pszXML).c_str()); + hResult = OGRPG_PQexec(hPGConn, osCommand.c_str()); + OGRPGClearResult(hResult); - CPLDestroyXMLNode(psRoot); - CPLFree(pszXML); + CPLDestroyXMLNode(psRoot); + CPLFree(pszXML); + } } - else if (poDS->HasOgrSystemTablesMetadataTable()) + else if (poDS->HasOgrSystemTablesMetadataTable() && + poDS->HasWritePermissionsOnMetadataTable()) { CPLString osCommand; osCommand.Printf("DELETE FROM ogr_system_tables.metadata WHERE " From e46e6eaa567b362e0276f360784abe0d239935a9 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 31 May 2024 20:55:58 +0200 Subject: [PATCH 0026/1119] PG: really honor OGR_PG_ENABLE_METADATA=NO in SerializeMetadata() --- autotest/ogr/ogr_pg.py | 28 ++++++++++++++++++++++++++ ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp | 4 ++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/autotest/ogr/ogr_pg.py b/autotest/ogr/ogr_pg.py index 3d139fdda891..37d03c4a95b0 100755 --- a/autotest/ogr/ogr_pg.py +++ b/autotest/ogr/ogr_pg.py @@ -4868,6 +4868,12 @@ def test_ogr_pg_metadata(pg_ds, run_number): lyr.SetMetadataItem("DESCRIPTION", "my_desc") pg_ds.CommitTransaction() + pg_ds = reconnect(pg_ds, update=1) + + with gdal.config_option("OGR_PG_ENABLE_METADATA", "NO"): + lyr = pg_ds.GetLayerByName("test_ogr_pg_metadata") + assert lyr.GetMetadata_Dict() == {"DESCRIPTION": "my_desc"} + pg_ds = reconnect(pg_ds, update=1) with pg_ds.ExecuteSQL( "SELECT * FROM ogr_system_tables.metadata WHERE table_name = 'test_ogr_pg_metadata'" @@ -4955,6 +4961,28 @@ def test_ogr_pg_metadata_restricted_user(pg_ds): pg_ds.ExecuteSQL("DROP ROLE test_ogr_pg_metadata_restricted_user") +############################################################################### +# Test disabling writing metadata + + +@only_without_postgis +def test_ogr_pg_write_metadata_disabled(pg_ds): + + with gdal.config_option("OGR_PG_ENABLE_METADATA", "NO"): + + pg_ds = reconnect(pg_ds, update=1) + lyr = pg_ds.CreateLayer( + "test_ogr_pg_metadata", geom_type=ogr.wkbPoint, options=["OVERWRITE=YES"] + ) + lyr.SetMetadata({"foo": "bar"}) + lyr.SetMetadataItem("bar", "baz") + + pg_ds = reconnect(pg_ds, update=1) + + lyr = pg_ds.GetLayerByName("test_ogr_pg_metadata") + assert lyr.GetMetadata_Dict() == {} + + ############################################################################### # Test append of several layers in PG_USE_COPY mode (#6411) diff --git a/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp b/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp index da4a8ff1533a..f46f8b393c8e 100644 --- a/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp +++ b/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp @@ -289,8 +289,8 @@ void OGRPGTableLayer::LoadMetadata() void OGRPGTableLayer::SerializeMetadata() { - if (!m_bMetadataModified && - CPLTestBool(CPLGetConfigOption("OGR_PG_ENABLE_METADATA", "YES"))) + if (!m_bMetadataModified || + !CPLTestBool(CPLGetConfigOption("OGR_PG_ENABLE_METADATA", "YES"))) { return; } From 436df26145d79f1055ad13be1036a8eab90b8afa Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 31 May 2024 23:43:05 +0200 Subject: [PATCH 0027/1119] Doc: multithreading: explicit why GDALDataset is not thread-safe (#10097) Co-authored-by: Ryan Co-authored-by: Sean Gillies --- doc/source/user/multithreading.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/source/user/multithreading.rst b/doc/source/user/multithreading.rst index feb16b6f01a1..a4263362a679 100644 --- a/doc/source/user/multithreading.rst +++ b/doc/source/user/multithreading.rst @@ -31,14 +31,22 @@ on different :cpp:class:`GDALRasterBand` instances owned by the same :cpp:class:`GDALDataset` instance (each thread should instead manipulate a distinct GDALDataset). Similarly for a GDALDataset owning several :cpp:class:`OGRLayer`. +The reason is that most implementations of GDALDataset or GDALRasterBand +are stateful. A GDALDataset typically owns a file handle, +and performs seek/read operations on it, thus not allowing concurrent access. +Block cache related structures for a given GDALDataset are not thread-safe. +Drivers also often implement lazy initialization strategies to access various +metadata which are resolved only the first time the method to access them is +invoked. Drivers may also rely on third-party libraries that expose objects +that are not thread-safe. + Those restrictions apply to the C and C++ ABI, and all languages bindings (unless they would take special precautions to serialize calls) GDAL block cache and multi-threading ------------------------------------ -The current design of the GDAL raster block cache make it appropriate to -read several datasets from several threads. However performance issues may +The current design of the GDAL raster block cache allows concurrent reads of several datasets. However performance issues may arise when writing several datasets from several threads, due to lock contention in the global structures of the block cache mechanism. From bf0c17df39813c9b7279720e9467ce95f40d8f82 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 1 Jun 2024 01:45:14 +0200 Subject: [PATCH 0028/1119] TABView::OpenForRead(): avoid potential double-free Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=69334 --- ogr/ogrsf_frmts/mitab/mitab_tabview.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ogr/ogrsf_frmts/mitab/mitab_tabview.cpp b/ogr/ogrsf_frmts/mitab/mitab_tabview.cpp index ba194d8d054a..466e067165d3 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_tabview.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_tabview.cpp @@ -190,6 +190,7 @@ int TABView::OpenForRead(const char *pszFname, } CPLFree(m_pszFname); + m_pszFname = nullptr; return -1; } @@ -219,6 +220,7 @@ int TABView::OpenForRead(const char *pszFname, CPLErrorReset(); CPLFree(m_pszFname); + m_pszFname = nullptr; return -1; } From cc8c29bc4adffcd9f211187137a16324aef97983 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 02:17:59 +0000 Subject: [PATCH 0029/1119] Bump docker/login-action from 3.1.0 to 3.2.0 Bumps [docker/login-action](https://github.com/docker/login-action) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/e92390c5fb421da1463c202d546fed0ec5c39f20...0d4c9c5ea7693da7b068278f7b52bda2a190a446) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/linux_build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux_build.yml b/.github/workflows/linux_build.yml index ea2ee24b305d..9c40e200b6cb 100644 --- a/.github/workflows/linux_build.yml +++ b/.github/workflows/linux_build.yml @@ -178,7 +178,7 @@ jobs: - name: Login to Docker Hub if: env.CONTAINER_REGISTRY == 'docker.io' - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} From b69a90b25f04822481f805a8d9996ca7826aa5f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 02:18:02 +0000 Subject: [PATCH 0030/1119] Bump msys2/setup-msys2 from 2.22.0 to 2.23.0 Bumps [msys2/setup-msys2](https://github.com/msys2/setup-msys2) from 2.22.0 to 2.23.0. - [Release notes](https://github.com/msys2/setup-msys2/releases) - [Changelog](https://github.com/msys2/setup-msys2/blob/main/CHANGELOG.md) - [Commits](https://github.com/msys2/setup-msys2/compare/cc11e9188b693c2b100158c3322424c4cc1dadea...d0e80f58dffbc64f6a3a1f43527d469b4fc7b6c8) --- updated-dependencies: - dependency-name: msys2/setup-msys2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/cmake_builds.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index 6d6aaf1606e4..a9642c17d784 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -314,7 +314,7 @@ jobs: - name: Checkout GDAL uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Install development packages - uses: msys2/setup-msys2@cc11e9188b693c2b100158c3322424c4cc1dadea # v2.22.0 + uses: msys2/setup-msys2@d0e80f58dffbc64f6a3a1f43527d469b4fc7b6c8 # v2.23.0 with: msystem: MINGW64 update: true From 08bea7748c97f1dd7fa39a93084732d375a946e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 02:18:10 +0000 Subject: [PATCH 0031/1119] Bump github/codeql-action from 3.25.3 to 3.25.7 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.25.3 to 3.25.7. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/d39d31e687223d841ef683f52467bd88e9b21c14...f079b8493333aace61c81488f8bd40919487bd9f) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f40914536e7b..8801799dc6f6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -107,7 +107,7 @@ jobs: # We do that after running CMake to avoid CodeQL to trigger during CMake time, # in particular during HDF5 detection which is terribly slow (https://github.com/OSGeo/gdal/issues/9549) - name: Initialize CodeQL - uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 + uses: github/codeql-action/init@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -127,6 +127,6 @@ jobs: cmake --build build -j$(nproc) - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 + uses: github/codeql-action/analyze@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 53caa8f567a3..8f54868255da 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -71,6 +71,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 + uses: github/codeql-action/upload-sarif@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 with: sarif_file: results.sarif From 1d999198eec86de2eec5ee74c5ea9f44503f832c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 02:18:13 +0000 Subject: [PATCH 0032/1119] Bump ossf/scorecard-action from 2.3.1 to 2.3.3 Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.3.1 to 2.3.3. - [Release notes](https://github.com/ossf/scorecard-action/releases) - [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md) - [Commits](https://github.com/ossf/scorecard-action/compare/0864cf19026789058feabb7e87baa5f140aac736...dc50aa9510b46c811795eb24b2f1ba02a914e534) --- updated-dependencies: - dependency-name: ossf/scorecard-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 53caa8f567a3..1e0be7ea4a2e 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -41,7 +41,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 with: results_file: results.sarif results_format: sarif From eb1b1138fa94cb607d1f336fa5aeb712f05ba5e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 02:18:21 +0000 Subject: [PATCH 0033/1119] Bump actions/checkout from 4.1.4 to 4.1.6 Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/0ad4b8fadaa221de15dcec353f45205ec38ea70b...a5ac7e51b41094c92402da3b24376905380afc29) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/android_cmake.yml | 2 +- .github/workflows/clang_static_analyzer.yml | 2 +- .github/workflows/cmake_builds.yml | 12 ++++++------ .github/workflows/code_checks.yml | 16 ++++++++-------- .github/workflows/codeql.yml | 2 +- .github/workflows/conda.yml | 2 +- .github/workflows/coverity_scan.yml | 2 +- .github/workflows/doc_build.yml | 2 +- .github/workflows/linux_build.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/scorecard.yml | 2 +- .github/workflows/slow_tests.yml | 2 +- .github/workflows/windows_build.yml | 2 +- 13 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/android_cmake.yml b/.github/workflows/android_cmake.yml index eaccf59776d0..d81b50ff90dd 100644 --- a/.github/workflows/android_cmake.yml +++ b/.github/workflows/android_cmake.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Cache uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 diff --git a/.github/workflows/clang_static_analyzer.yml b/.github/workflows/clang_static_analyzer.yml index 7a18e1912dbe..f1cddfbfb00f 100644 --- a/.github/workflows/clang_static_analyzer.yml +++ b/.github/workflows/clang_static_analyzer.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Run run: docker run --rm -v $PWD:$PWD ubuntu:22.04 sh -c "cd $PWD && apt update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends sudo software-properties-common && DEBIAN_FRONTEND=noninteractive sh ./ci/travis/csa_common/before_install.sh && sh ./ci/travis/csa_common/install.sh && sh ./ci/travis/csa_common/script.sh" diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index 6d6aaf1606e4..13dff7420ac3 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -32,7 +32,7 @@ jobs: cache-name: cmake-ubuntu-focal steps: - name: Checkout GDAL - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Setup cache uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache @@ -312,7 +312,7 @@ jobs: run: | git config --global core.autocrlf false - name: Checkout GDAL - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Install development packages uses: msys2/setup-msys2@cc11e9188b693c2b100158c3322424c4cc1dadea # v2.22.0 with: @@ -405,7 +405,7 @@ jobs: run: | git config --global core.autocrlf false - name: Checkout GDAL - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0 - name: populate JAVA_HOME shell: pwsh @@ -507,7 +507,7 @@ jobs: run: | git config --global core.autocrlf false - name: Checkout GDAL - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: conda-incubator/setup-miniconda@a4260408e20b96e80095f42ff7f1a15b27dd94ca # v3.0.4 with: activate-environment: gdalenv @@ -574,7 +574,7 @@ jobs: with: xcode-version: 14.3 - name: Checkout GDAL - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Setup cache uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 id: cache @@ -654,7 +654,7 @@ jobs: run: | git config --global core.autocrlf false - name: Checkout GDAL - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: conda-incubator/setup-miniconda@a4260408e20b96e80095f42ff7f1a15b27dd94ca # v3.0.4 with: activate-environment: gdalenv diff --git a/.github/workflows/code_checks.yml b/.github/workflows/code_checks.yml index 674bae403803..321cd50a897e 100644 --- a/.github/workflows/code_checks.yml +++ b/.github/workflows/code_checks.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Install Requirements run: | @@ -47,7 +47,7 @@ jobs: container: ubuntu:24.04 steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Install Requirements run: | @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Detect tabulations run: ./scripts/detect_tabulations.sh @@ -104,7 +104,7 @@ jobs: linting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 @@ -113,7 +113,7 @@ jobs: container: ghcr.io/osgeo/proj-docs steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Run doxygen run: | @@ -124,7 +124,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Install Requirements run: | @@ -143,7 +143,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: @@ -159,7 +159,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Install requirements run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f40914536e7b..105270d0e81c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Install dependencies run: | diff --git a/.github/workflows/conda.yml b/.github/workflows/conda.yml index aa2dd18bcc31..eaba06ddd159 100644 --- a/.github/workflows/conda.yml +++ b/.github/workflows/conda.yml @@ -37,7 +37,7 @@ jobs: CACHE_NUMBER: 0 steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Support longpaths run: git config --system core.longpaths true diff --git a/.github/workflows/coverity_scan.yml b/.github/workflows/coverity_scan.yml index b1980c77bcfa..7b560539f657 100644 --- a/.github/workflows/coverity_scan.yml +++ b/.github/workflows/coverity_scan.yml @@ -43,7 +43,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Login to GHCR if: env.CONTAINER_REGISTRY == 'ghcr.io' diff --git a/.github/workflows/doc_build.yml b/.github/workflows/doc_build.yml index 0d7259f45767..ee75f953b0c0 100644 --- a/.github/workflows/doc_build.yml +++ b/.github/workflows/doc_build.yml @@ -24,7 +24,7 @@ jobs: container: ghcr.io/osgeo/proj-docs steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Setup environment shell: bash -l {0} run: | diff --git a/.github/workflows/linux_build.yml b/.github/workflows/linux_build.yml index ea2ee24b305d..7196c3ac44db 100644 --- a/.github/workflows/linux_build.yml +++ b/.github/workflows/linux_build.yml @@ -174,7 +174,7 @@ jobs: sudo sysctl vm.mmap_rnd_bits=28 - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Login to Docker Hub if: env.CONTAINER_REGISTRY == 'docker.io' diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index dc446c51ce00..715058dfc41a 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -25,7 +25,7 @@ jobs: runs-on: macos-14 steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: conda-incubator/setup-miniconda@a4260408e20b96e80095f42ff7f1a15b27dd94ca # v3.0.4 with: diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 53caa8f567a3..1799c4eaee84 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -36,7 +36,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: persist-credentials: false diff --git a/.github/workflows/slow_tests.yml b/.github/workflows/slow_tests.yml index 40713a1f0bd0..c7fc3b084495 100644 --- a/.github/workflows/slow_tests.yml +++ b/.github/workflows/slow_tests.yml @@ -47,7 +47,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Login to GHCR if: env.CONTAINER_REGISTRY == 'ghcr.io' diff --git a/.github/workflows/windows_build.yml b/.github/workflows/windows_build.yml index b80dd1cdad58..a75a70f5878c 100644 --- a/.github/workflows/windows_build.yml +++ b/.github/workflows/windows_build.yml @@ -56,7 +56,7 @@ jobs: git config --global core.autocrlf false - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Set environment shell: pwsh From 01a08fe38039f73919cc09e3fa14421b6dc41d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A9=8D=E4=B8=B9=E5=B0=BC=20Dan=20Jacobson?= Date: Sat, 1 Jun 2024 22:17:51 +0800 Subject: [PATCH 0034/1119] Update gdal_contour.rst clarifying "-off" (#10104) --- doc/source/programs/gdal_contour.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/source/programs/gdal_contour.rst b/doc/source/programs/gdal_contour.rst index f07443565ddc..b2accc4cf4f2 100644 --- a/doc/source/programs/gdal_contour.rst +++ b/doc/source/programs/gdal_contour.rst @@ -100,6 +100,9 @@ be on the right, i.e. a line string goes clockwise around a top. Offset from zero relative to which to interpret intervals. Ignored if -fl is used. + For example, `-i 100` requests contours at ...-100, 0, 100... + Further adding `-off 25` makes that request instead ...-75, 25, 125... + .. option:: -fl Name one or more "fixed levels" to extract. From ffaf4a46bb37c29c424704d3fb56ccb05225f9a1 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 2 Jun 2024 00:00:43 +0200 Subject: [PATCH 0035/1119] netCDFAttribute::IWrite(): fix writing an array of (NC4) strings, from a non-string array --- autotest/gdrivers/netcdf_multidim.py | 32 ++++++++++++---------------- frmts/netcdf/netcdfmultidim.cpp | 25 +++++++++++----------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/autotest/gdrivers/netcdf_multidim.py b/autotest/gdrivers/netcdf_multidim.py index 21d2df20d439..7fd4616b1f85 100755 --- a/autotest/gdrivers/netcdf_multidim.py +++ b/autotest/gdrivers/netcdf_multidim.py @@ -33,7 +33,6 @@ import shutil import stat import struct -import sys import time import gdaltest @@ -1115,23 +1114,20 @@ def dims_from_non_netcdf(rg): ) assert att.Read() == "bar" - # There is an issue on 32-bit platforms, likely in libnetcdf or libhdf5 itself, - # with writing more than one string - if sys.maxsize > 0x7FFFFFFF: - att = rg.CreateAttribute( - "att_two_strings", [2], gdal.ExtendedDataType.CreateString() - ) - assert att - with gdal.quiet_errors(): - assert att.Write(["not_enough_elements"]) != gdal.CE_None - assert att.Write([1, 2]) == gdal.CE_None - assert att.Read() == ["1", "2"] - assert att.Write(["foo", "barbaz"]) == gdal.CE_None - assert att.Read() == ["foo", "barbaz"] - att = next( - (x for x in rg.GetAttributes() if x.GetName() == att.GetName()), None - ) - assert att.Read() == ["foo", "barbaz"] + att = rg.CreateAttribute( + "att_two_strings", [2], gdal.ExtendedDataType.CreateString() + ) + assert att + with gdal.quiet_errors(): + assert att.Write(["not_enough_elements"]) != gdal.CE_None + assert att.Write([1, 2]) == gdal.CE_None + assert att.Read() == ["1", "2"] + assert att.Write(["foo", "barbaz"]) == gdal.CE_None + assert att.Read() == ["foo", "barbaz"] + att = next( + (x for x in rg.GetAttributes() if x.GetName() == att.GetName()), None + ) + assert att.Read() == ["foo", "barbaz"] att = rg.CreateAttribute( "att_double", [], gdal.ExtendedDataType.Create(gdal.GDT_Float64) diff --git a/frmts/netcdf/netcdfmultidim.cpp b/frmts/netcdf/netcdfmultidim.cpp index 5b0f26f274fe..d05d1d1bc948 100644 --- a/frmts/netcdf/netcdfmultidim.cpp +++ b/frmts/netcdf/netcdfmultidim.cpp @@ -4671,17 +4671,18 @@ bool netCDFAttribute::IWrite(const GUInt64 *arrayStartIdx, const size_t *count, m_poShared->SetDefineMode(true); + const auto &dt(GetDataType()); if (m_nAttType == NC_STRING) { - CPLAssert(GetDataType().GetClass() == GEDTC_STRING); + CPLAssert(dt.GetClass() == GEDTC_STRING); if (m_dims.empty()) { char *pszStr = nullptr; const char *pszStrConst; - if (bufferDataType != GetDataType()) + if (bufferDataType != dt) { GDALExtendedDataType::CopyValue(pSrcBuffer, bufferDataType, - &pszStr, GetDataType()); + &pszStr, dt); pszStrConst = pszStr; } else @@ -4698,15 +4699,16 @@ bool netCDFAttribute::IWrite(const GUInt64 *arrayStartIdx, const size_t *count, } int ret; - if (bufferDataType != GetDataType()) + if (bufferDataType != dt) { std::vector apszStrings(count[0]); - const char **ppszStr; - memcpy(&ppszStr, &pSrcBuffer, sizeof(const char **)); + const auto nInputDTSize = bufferDataType.GetSize(); + const GByte *pabySrcBuffer = static_cast(pSrcBuffer); for (size_t i = 0; i < count[0]; i++) { - GDALExtendedDataType::CopyValue(&ppszStr[i], bufferDataType, - &apszStrings[i], GetDataType()); + GDALExtendedDataType::CopyValue(pabySrcBuffer, bufferDataType, + &apszStrings[i], dt); + pabySrcBuffer += nInputDTSize * bufferStride[0]; } ret = nc_put_att_string(m_gid, m_varid, GetName().c_str(), count[0], const_cast(&apszStrings[0])); @@ -4730,14 +4732,14 @@ bool netCDFAttribute::IWrite(const GUInt64 *arrayStartIdx, const size_t *count, if (m_nAttType == NC_CHAR) { - CPLAssert(GetDataType().GetClass() == GEDTC_STRING); + CPLAssert(dt.GetClass() == GEDTC_STRING); CPLAssert(m_dims.empty()); char *pszStr = nullptr; const char *pszStrConst; - if (bufferDataType != GetDataType()) + if (bufferDataType != dt) { GDALExtendedDataType::CopyValue(pSrcBuffer, bufferDataType, &pszStr, - GetDataType()); + dt); pszStrConst = pszStr; } else @@ -4754,7 +4756,6 @@ bool netCDFAttribute::IWrite(const GUInt64 *arrayStartIdx, const size_t *count, return true; } - const auto &dt(GetDataType()); if (dt.GetClass() == GEDTC_NUMERIC && dt.GetNumericDataType() == GDT_Unknown) { From fcc0e447389137a7da8fb6845c8bafcd15ba7d1d Mon Sep 17 00:00:00 2001 From: Erik Schnetter Date: Sat, 1 Jun 2024 18:24:33 -0400 Subject: [PATCH 0036/1119] gdalmultidim.cpp: Remove superfluous semicolon --- gcore/gdalmultidim.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcore/gdalmultidim.cpp b/gcore/gdalmultidim.cpp index 3f9aa4275608..cfb3b3592374 100644 --- a/gcore/gdalmultidim.cpp +++ b/gcore/gdalmultidim.cpp @@ -1782,7 +1782,7 @@ bool GDALExtendedDataType::CopyValue(const void *pSrc, pabyDst + dstComp->GetOffset(), dstComp->GetType())) { return false; - }; + } } return true; } From f8995e496bfec3e9aa981a8dfd7b85bdf9869f83 Mon Sep 17 00:00:00 2001 From: Erik Schnetter Date: Sun, 2 Jun 2024 13:13:23 -0400 Subject: [PATCH 0037/1119] Zarr: Allow int64 attributes (#10065) --- autotest/gdrivers/zarr_driver.py | 73 +++++- frmts/zarr/zarr_attribute.cpp | 336 ++++++++++++++++---------- gcore/gdal.h | 9 + gcore/gdal_priv.h | 6 + gcore/gdalmultidim.cpp | 277 ++++++++++++++++++++- swig/include/MultiDimensional.i | 29 +++ swig/include/python/gdal_python.i | 50 ++-- swig/include/python/typemaps_python.i | 138 +++++++++-- 8 files changed, 745 insertions(+), 173 deletions(-) diff --git a/autotest/gdrivers/zarr_driver.py b/autotest/gdrivers/zarr_driver.py index ec4b1e1689d2..3056cd1b0ce7 100644 --- a/autotest/gdrivers/zarr_driver.py +++ b/autotest/gdrivers/zarr_driver.py @@ -723,9 +723,9 @@ def test_zarr_read_array_attributes(): "double": 1.5, "doublearray": [1.5, 2.5], "int": 1, + "intarray": [1, 2], "int64": 1234567890123, "int64array": [1234567890123, -1234567890123], - "intarray": [1, 2], "intdoublearray": [1, 2.5], "mixedstrintarray": ["foo", 1], "null": "", @@ -1437,12 +1437,46 @@ def create(): assert attr assert attr.Write(4000000000) == gdal.CE_None + attr = rg.CreateAttribute( + "int64_attr", [], gdal.ExtendedDataType.Create(gdal.GDT_Int64) + ) + assert attr + assert attr.Write(12345678901234) == gdal.CE_None + + attr = rg.CreateAttribute( + "uint64_attr", [], gdal.ExtendedDataType.Create(gdal.GDT_UInt64) + ) + assert attr + # We cannot write UINT64_MAX + # assert attr.Write(18000000000000000000) == gdal.CE_None + assert attr.Write(9000000000000000000) == gdal.CE_None + attr = rg.CreateAttribute( "int_array_attr", [2], gdal.ExtendedDataType.Create(gdal.GDT_Int32) ) assert attr assert attr.Write([12345678, -12345678]) == gdal.CE_None + attr = rg.CreateAttribute( + "uint_array_attr", [2], gdal.ExtendedDataType.Create(gdal.GDT_UInt32) + ) + assert attr + assert attr.Write([12345678, 4000000000]) == gdal.CE_None + + attr = rg.CreateAttribute( + "int64_array_attr", [2], gdal.ExtendedDataType.Create(gdal.GDT_Int64) + ) + assert attr + assert attr.Write([12345678091234, -12345678091234]) == gdal.CE_None + + attr = rg.CreateAttribute( + "uint64_array_attr", [2], gdal.ExtendedDataType.Create(gdal.GDT_UInt64) + ) + assert attr + # We cannot write UINT64_MAX + # assert attr.Write([12345678091234, 18000000000000000000]) == gdal.CE_None + assert attr.Write([12345678091234, 9000000000000000000]) == gdal.CE_None + attr = rg.CreateAttribute( "double_attr", [], gdal.ExtendedDataType.Create(gdal.GDT_Float64) ) @@ -1519,17 +1553,52 @@ def update(): attr = rg.GetAttribute("int_attr") assert attr assert attr.GetDataType().GetNumericDataType() == gdal.GDT_Int32 + assert attr.ReadAsInt() == 12345678 + assert attr.ReadAsInt64() == 12345678 assert attr.ReadAsDouble() == 12345678 attr = rg.GetAttribute("uint_attr") assert attr - assert attr.GetDataType().GetNumericDataType() == gdal.GDT_Float64 + assert attr.GetDataType().GetNumericDataType() == gdal.GDT_Int64 + assert attr.ReadAsInt64() == 4000000000 assert attr.ReadAsDouble() == 4000000000 + attr = rg.GetAttribute("int64_attr") + assert attr + assert attr.GetDataType().GetNumericDataType() == gdal.GDT_Int64 + assert attr.ReadAsInt64() == 12345678901234 + assert attr.ReadAsDouble() == 12345678901234 + + attr = rg.GetAttribute("uint64_attr") + assert attr + assert attr.GetDataType().GetNumericDataType() == gdal.GDT_Int64 + assert attr.ReadAsInt64() == 9000000000000000000 + assert attr.ReadAsDouble() == 9000000000000000000 + attr = rg.GetAttribute("int_array_attr") assert attr assert attr.GetDataType().GetNumericDataType() == gdal.GDT_Int32 assert attr.ReadAsIntArray() == (12345678, -12345678) + assert attr.ReadAsInt64Array() == (12345678, -12345678) + assert attr.ReadAsDoubleArray() == (12345678, -12345678) + + attr = rg.GetAttribute("uint_array_attr") + assert attr + assert attr.GetDataType().GetNumericDataType() == gdal.GDT_Int64 + assert attr.ReadAsInt64Array() == (12345678, 4000000000) + assert attr.ReadAsDoubleArray() == (12345678, 4000000000) + + attr = rg.GetAttribute("int64_array_attr") + assert attr + assert attr.GetDataType().GetNumericDataType() == gdal.GDT_Int64 + assert attr.ReadAsInt64Array() == (12345678091234, -12345678091234) + assert attr.ReadAsDoubleArray() == (12345678091234, -12345678091234) + + attr = rg.GetAttribute("uint64_array_attr") + assert attr + assert attr.GetDataType().GetNumericDataType() == gdal.GDT_Int64 + assert attr.ReadAsInt64Array() == (12345678091234, 9000000000000000000) + assert attr.ReadAsDoubleArray() == (12345678091234, 9000000000000000000) attr = rg.GetAttribute("double_attr") assert attr diff --git a/frmts/zarr/zarr_attribute.cpp b/frmts/zarr/zarr_attribute.cpp index bdde645b8ad4..5f95161792a1 100644 --- a/frmts/zarr/zarr_attribute.cpp +++ b/frmts/zarr/zarr_attribute.cpp @@ -64,142 +64,213 @@ void ZarrAttributeGroup::Init(const CPLJSONObject &obj, bool bUpdatable) const auto itemType = item.GetType(); bool bDone = false; std::shared_ptr poAttr; - if (itemType == CPLJSONObject::Type::String) + switch (itemType) { - bDone = true; - poAttr = m_poGroup->CreateAttribute( - item.GetName(), {}, GDALExtendedDataType::CreateString(), - nullptr); - if (poAttr) + case CPLJSONObject::Type::String: { - const GUInt64 arrayStartIdx = 0; - const size_t count = 1; - const GInt64 arrayStep = 0; - const GPtrDiff_t bufferStride = 0; - const std::string str = item.ToString(); - const char *c_str = str.c_str(); - poAttr->Write(&arrayStartIdx, &count, &arrayStep, &bufferStride, - poAttr->GetDataType(), &c_str); + bDone = true; + poAttr = m_poGroup->CreateAttribute( + item.GetName(), {}, GDALExtendedDataType::CreateString(), + nullptr); + if (poAttr) + { + const GUInt64 arrayStartIdx = 0; + const size_t count = 1; + const GInt64 arrayStep = 0; + const GPtrDiff_t bufferStride = 0; + const std::string str = item.ToString(); + const char *c_str = str.c_str(); + poAttr->Write(&arrayStartIdx, &count, &arrayStep, + &bufferStride, poAttr->GetDataType(), &c_str); + } + break; } - } - else if (itemType == CPLJSONObject::Type::Integer || - itemType == CPLJSONObject::Type::Long || - itemType == CPLJSONObject::Type::Double) - { - bDone = true; - poAttr = m_poGroup->CreateAttribute( - item.GetName(), {}, - GDALExtendedDataType::Create( - itemType == CPLJSONObject::Type::Integer ? GDT_Int32 - : GDT_Float64), - nullptr); - if (poAttr) + case CPLJSONObject::Type::Integer: { - const GUInt64 arrayStartIdx = 0; - const size_t count = 1; - const GInt64 arrayStep = 0; - const GPtrDiff_t bufferStride = 0; - const double val = item.ToDouble(); - poAttr->Write(&arrayStartIdx, &count, &arrayStep, &bufferStride, - GDALExtendedDataType::Create(GDT_Float64), &val); + bDone = true; + poAttr = m_poGroup->CreateAttribute( + item.GetName(), {}, GDALExtendedDataType::Create(GDT_Int32), + nullptr); + if (poAttr) + { + const GUInt64 arrayStartIdx = 0; + const size_t count = 1; + const GInt64 arrayStep = 0; + const GPtrDiff_t bufferStride = 0; + const int val = item.ToInteger(); + poAttr->Write( + &arrayStartIdx, &count, &arrayStep, &bufferStride, + GDALExtendedDataType::Create(GDT_Int32), &val); + } + break; } - } - else if (itemType == CPLJSONObject::Type::Array) - { - const auto array = item.ToArray(); - bool isFirst = true; - bool isString = false; - bool isNumeric = false; - bool foundInt64 = false; - bool foundDouble = false; - bool mixedType = false; - size_t countItems = 0; - for (const auto &subItem : array) + case CPLJSONObject::Type::Long: { - const auto subItemType = subItem.GetType(); - if (subItemType == CPLJSONObject::Type::String) + bDone = true; + poAttr = m_poGroup->CreateAttribute( + item.GetName(), {}, GDALExtendedDataType::Create(GDT_Int64), + nullptr); + if (poAttr) { - if (isFirst) - { - isString = true; - } - else if (!isString) - { - mixedType = true; - break; - } - countItems++; + const GUInt64 arrayStartIdx = 0; + const size_t count = 1; + const GInt64 arrayStep = 0; + const GPtrDiff_t bufferStride = 0; + const int64_t val = item.ToLong(); + poAttr->Write( + &arrayStartIdx, &count, &arrayStep, &bufferStride, + GDALExtendedDataType::Create(GDT_Int64), &val); } - else if (subItemType == CPLJSONObject::Type::Integer || - subItemType == CPLJSONObject::Type::Long || - subItemType == CPLJSONObject::Type::Double) + break; + } + case CPLJSONObject::Type::Double: + { + bDone = true; + poAttr = m_poGroup->CreateAttribute( + item.GetName(), {}, + GDALExtendedDataType::Create(GDT_Float64), nullptr); + if (poAttr) { - if (isFirst) + const GUInt64 arrayStartIdx = 0; + const size_t count = 1; + const GInt64 arrayStep = 0; + const GPtrDiff_t bufferStride = 0; + const double val = item.ToDouble(); + poAttr->Write( + &arrayStartIdx, &count, &arrayStep, &bufferStride, + GDALExtendedDataType::Create(GDT_Float64), &val); + } + break; + } + case CPLJSONObject::Type::Array: + { + const auto array = item.ToArray(); + bool isFirst = true; + bool isString = false; + bool isNumeric = false; + bool foundInt64 = false; + bool foundDouble = false; + bool mixedType = false; + size_t countItems = 0; + for (const auto &subItem : array) + { + const auto subItemType = subItem.GetType(); + if (subItemType == CPLJSONObject::Type::String) { - isNumeric = true; + if (isFirst) + { + isString = true; + } + else if (!isString) + { + mixedType = true; + break; + } + countItems++; + } + else if (subItemType == CPLJSONObject::Type::Integer || + subItemType == CPLJSONObject::Type::Long || + subItemType == CPLJSONObject::Type::Double) + { + if (isFirst) + { + isNumeric = true; + } + else if (!isNumeric) + { + mixedType = true; + break; + } + if (subItemType == CPLJSONObject::Type::Double) + foundDouble = true; + else if (subItemType == CPLJSONObject::Type::Long) + foundInt64 = true; + countItems++; } - else if (!isNumeric) + else { mixedType = true; break; } - if (subItemType == CPLJSONObject::Type::Double) - foundDouble = true; - else if (subItemType == CPLJSONObject::Type::Long) - foundInt64 = true; - countItems++; + isFirst = false; } - else - { - mixedType = true; - break; - } - isFirst = false; - } - if (!mixedType && !isFirst) - { - bDone = true; - poAttr = m_poGroup->CreateAttribute( - item.GetName(), {countItems}, - isString ? GDALExtendedDataType::CreateString() - : GDALExtendedDataType::Create( - (foundDouble || foundInt64) ? GDT_Float64 - : GDT_Int32), - nullptr); - if (poAttr) + if (!mixedType && !isFirst) { - size_t idx = 0; - for (const auto &subItem : array) + bDone = true; + poAttr = m_poGroup->CreateAttribute( + item.GetName(), {countItems}, + isString ? GDALExtendedDataType::CreateString() + : GDALExtendedDataType::Create( + foundDouble ? GDT_Float64 + : foundInt64 ? GDT_Int64 + : GDT_Int32), + nullptr); + if (poAttr) { - const GUInt64 arrayStartIdx = idx; - const size_t count = 1; - const GInt64 arrayStep = 0; - const GPtrDiff_t bufferStride = 0; - const auto subItemType = subItem.GetType(); - if (subItemType == CPLJSONObject::Type::String) - { - const std::string str = subItem.ToString(); - const char *c_str = str.c_str(); - poAttr->Write(&arrayStartIdx, &count, &arrayStep, - &bufferStride, poAttr->GetDataType(), - &c_str); - } - else if (subItemType == CPLJSONObject::Type::Integer || - subItemType == CPLJSONObject::Type::Long || - subItemType == CPLJSONObject::Type::Double) + size_t idx = 0; + for (const auto &subItem : array) { - const double val = subItem.ToDouble(); - poAttr->Write( - &arrayStartIdx, &count, &arrayStep, - &bufferStride, - GDALExtendedDataType::Create(GDT_Float64), - &val); + const GUInt64 arrayStartIdx = idx; + const size_t count = 1; + const GInt64 arrayStep = 0; + const GPtrDiff_t bufferStride = 0; + const auto subItemType = subItem.GetType(); + switch (subItemType) + { + case CPLJSONObject::Type::String: + { + const std::string str = subItem.ToString(); + const char *c_str = str.c_str(); + poAttr->Write(&arrayStartIdx, &count, + &arrayStep, &bufferStride, + poAttr->GetDataType(), + &c_str); + break; + } + case CPLJSONObject::Type::Integer: + { + const int val = subItem.ToInteger(); + poAttr->Write( + &arrayStartIdx, &count, &arrayStep, + &bufferStride, + GDALExtendedDataType::Create(GDT_Int32), + &val); + break; + } + case CPLJSONObject::Type::Long: + { + const int64_t val = subItem.ToLong(); + poAttr->Write( + &arrayStartIdx, &count, &arrayStep, + &bufferStride, + GDALExtendedDataType::Create(GDT_Int64), + &val); + break; + } + case CPLJSONObject::Type::Double: + { + const double val = subItem.ToDouble(); + poAttr->Write(&arrayStartIdx, &count, + &arrayStep, &bufferStride, + GDALExtendedDataType::Create( + GDT_Float64), + &val); + break; + } + default: + // Ignore other JSON object types + break; + } + ++idx; } - ++idx; } } + break; } + default: + // Ignore other JSON object types + break; } if (!bDone) @@ -288,30 +359,49 @@ CPLJSONObject ZarrAttributeGroup::Serialize() const const auto eDT = oType.GetNumericDataType(); if (anDims.size() == 0) { - const double dfVal = attr->ReadAsDouble(); - if (eDT == GDT_Byte || eDT == GDT_UInt16 || eDT == GDT_UInt32 || - eDT == GDT_Int16 || eDT == GDT_Int32) + if (eDT == GDT_Int8 || eDT == GDT_Int16 || eDT == GDT_Int32 || + eDT == GDT_Int64) { - o.Add(attr->GetName(), static_cast(dfVal)); + const int64_t nVal = attr->ReadAsInt64(); + o.Add(attr->GetName(), static_cast(nVal)); + } + else if (eDT == GDT_Byte || eDT == GDT_UInt16 || + eDT == GDT_UInt32 || eDT == GDT_UInt64) + { + const int64_t nVal = attr->ReadAsInt64(); + o.Add(attr->GetName(), static_cast(nVal)); } else { + const double dfVal = attr->ReadAsDouble(); o.Add(attr->GetName(), dfVal); } } else if (anDims.size() == 1) { - const auto list = attr->ReadAsDoubleArray(); CPLJSONArray arr; - for (const auto dfVal : list) + if (eDT == GDT_Int8 || eDT == GDT_Int16 || eDT == GDT_Int32 || + eDT == GDT_Int64) { - if (eDT == GDT_Byte || eDT == GDT_UInt16 || - eDT == GDT_UInt32 || eDT == GDT_Int16 || - eDT == GDT_Int32) + const auto list = attr->ReadAsInt64Array(); + for (const auto nVal : list) { - arr.Add(static_cast(dfVal)); + arr.Add(static_cast(nVal)); } - else + } + else if (eDT == GDT_Byte || eDT == GDT_UInt16 || + eDT == GDT_UInt32 || eDT == GDT_UInt64) + { + const auto list = attr->ReadAsInt64Array(); + for (const auto nVal : list) + { + arr.Add(static_cast(nVal)); + } + } + else + { + const auto list = attr->ReadAsDoubleArray(); + for (const auto dfVal : list) { arr.Add(dfVal); } diff --git a/gcore/gdal.h b/gcore/gdal.h index 12d67ef53964..d81530c69906 100644 --- a/gcore/gdal.h +++ b/gcore/gdal.h @@ -2513,11 +2513,15 @@ void CPL_DLL GDALAttributeFreeRawResult(GDALAttributeH hAttr, GByte *raw, size_t nSize); const char CPL_DLL *GDALAttributeReadAsString(GDALAttributeH hAttr); int CPL_DLL GDALAttributeReadAsInt(GDALAttributeH hAttr); +int64_t CPL_DLL GDALAttributeReadAsInt64(GDALAttributeH hAttr); double CPL_DLL GDALAttributeReadAsDouble(GDALAttributeH hAttr); char CPL_DLL ** GDALAttributeReadAsStringArray(GDALAttributeH hAttr) CPL_WARN_UNUSED_RESULT; int CPL_DLL *GDALAttributeReadAsIntArray(GDALAttributeH hAttr, size_t *pnCount) CPL_WARN_UNUSED_RESULT; +int64_t CPL_DLL * +GDALAttributeReadAsInt64Array(GDALAttributeH hAttr, + size_t *pnCount) CPL_WARN_UNUSED_RESULT; double CPL_DLL * GDALAttributeReadAsDoubleArray(GDALAttributeH hAttr, size_t *pnCount) CPL_WARN_UNUSED_RESULT; @@ -2525,6 +2529,11 @@ int CPL_DLL GDALAttributeWriteRaw(GDALAttributeH hAttr, const void *, size_t); int CPL_DLL GDALAttributeWriteString(GDALAttributeH hAttr, const char *); int CPL_DLL GDALAttributeWriteStringArray(GDALAttributeH hAttr, CSLConstList); int CPL_DLL GDALAttributeWriteInt(GDALAttributeH hAttr, int); +int CPL_DLL GDALAttributeWriteIntArray(GDALAttributeH hAttr, const int *, + size_t); +int CPL_DLL GDALAttributeWriteInt64(GDALAttributeH hAttr, int64_t); +int CPL_DLL GDALAttributeWriteInt64Array(GDALAttributeH hAttr, const int64_t *, + size_t); int CPL_DLL GDALAttributeWriteDouble(GDALAttributeH hAttr, double); int CPL_DLL GDALAttributeWriteDoubleArray(GDALAttributeH hAttr, const double *, size_t); diff --git a/gcore/gdal_priv.h b/gcore/gdal_priv.h index c8349917cc82..e277c3f440c6 100644 --- a/gcore/gdal_priv.h +++ b/gcore/gdal_priv.h @@ -2272,6 +2272,7 @@ class CPL_DLL GDALDriver : public GDALMajorObject * @since 3.9 */ // clang-format on + class GDALPluginDriverProxy : public GDALDriver { const std::string m_osPluginFileName; @@ -3234,17 +3235,22 @@ class CPL_DLL GDALAttribute : virtual public GDALAbstractMDArray GDALRawResult ReadAsRaw() const; const char *ReadAsString() const; int ReadAsInt() const; + int64_t ReadAsInt64() const; double ReadAsDouble() const; CPLStringList ReadAsStringArray() const; std::vector ReadAsIntArray() const; + std::vector ReadAsInt64Array() const; std::vector ReadAsDoubleArray() const; using GDALAbstractMDArray::Write; bool Write(const void *pabyValue, size_t nLen); bool Write(const char *); bool WriteInt(int); + bool WriteInt64(int64_t); bool Write(double); bool Write(CSLConstList); + bool Write(const int *, size_t); + bool Write(const int64_t *, size_t); bool Write(const double *, size_t); //! @cond Doxygen_Suppress diff --git a/gcore/gdalmultidim.cpp b/gcore/gdalmultidim.cpp index cfb3b3592374..2e380faaafd1 100644 --- a/gcore/gdalmultidim.cpp +++ b/gcore/gdalmultidim.cpp @@ -3335,7 +3335,7 @@ const char *GDALAttribute::ReadAsString() const * * This function will only return the first element if there are several. * - * It can fail if its value can be converted to integer. + * It can fail if its value can not be converted to integer. * * This is the same as the C function GDALAttributeReadAsInt() * @@ -3352,6 +3352,31 @@ int GDALAttribute::ReadAsInt() const return nRet; } +/************************************************************************/ +/* ReadAsInt64() */ +/************************************************************************/ + +/** Return the value of an attribute as an int64_t. + * + * This function will only return the first element if there are several. + * + * It can fail if its value can not be converted to long. + * + * This is the same as the C function GDALAttributeReadAsInt64() + * + * @return an int64_t, or INT64_MIN in case of error. + */ +int64_t GDALAttribute::ReadAsInt64() const +{ + const auto nDims = GetDimensionCount(); + std::vector startIdx(1 + nDims, 0); + std::vector count(1 + nDims, 1); + int64_t nRet = INT64_MIN; + Read(startIdx.data(), count.data(), nullptr, nullptr, + GDALExtendedDataType::Create(GDT_Int64), &nRet, &nRet, sizeof(nRet)); + return nRet; +} + /************************************************************************/ /* ReadAsDouble() */ /************************************************************************/ @@ -3360,7 +3385,7 @@ int GDALAttribute::ReadAsInt() const * * This function will only return the first element if there are several. * - * It can fail if its value can be converted to double. + * It can fail if its value can not be converted to double. * * This is the same as the C function GDALAttributeReadAsInt() * @@ -3442,6 +3467,36 @@ std::vector GDALAttribute::ReadAsIntArray() const return res; } +/************************************************************************/ +/* ReadAsInt64Array() */ +/************************************************************************/ + +/** Return the value of an attribute as an array of int64_t. + * + * This is the same as the C function GDALAttributeReadAsInt64Array(). + */ +std::vector GDALAttribute::ReadAsInt64Array() const +{ + const auto nElts = GetTotalElementsCount(); +#if SIZEOF_VOIDP == 4 + if (nElts > static_cast(nElts)) + return {}; +#endif + std::vector res(static_cast(nElts)); + const auto &dims = GetDimensions(); + const auto nDims = GetDimensionCount(); + std::vector startIdx(1 + nDims, 0); + std::vector count(1 + nDims); + for (size_t i = 0; i < nDims; i++) + { + count[i] = static_cast(dims[i]->GetSize()); + } + Read(startIdx.data(), count.data(), nullptr, nullptr, + GDALExtendedDataType::Create(GDT_Int64), &res[0], res.data(), + res.size() * sizeof(res[0])); + return res; +} + /************************************************************************/ /* ReadAsDoubleArray() */ /************************************************************************/ @@ -3557,6 +3612,30 @@ bool GDALAttribute::WriteInt(int nVal) sizeof(nVal)); } +/************************************************************************/ +/* WriteInt64() */ +/************************************************************************/ + +/** Write an attribute from an int64_t value. + * + * Type conversion will be performed if needed. If the attribute contains + * multiple values, only the first one will be updated. + * + * This is the same as the C function GDALAttributeWriteInt(). + * + * @param nVal Value. + * @return true in case of success. + */ +bool GDALAttribute::WriteInt64(int64_t nVal) +{ + const auto nDims = GetDimensionCount(); + std::vector startIdx(1 + nDims, 0); + std::vector count(1 + nDims, 1); + return Write(startIdx.data(), count.data(), nullptr, nullptr, + GDALExtendedDataType::Create(GDT_Int64), &nVal, &nVal, + sizeof(nVal)); +} + /************************************************************************/ /* Write() */ /************************************************************************/ @@ -3618,6 +3697,75 @@ bool GDALAttribute::Write(CSLConstList vals) /* Write() */ /************************************************************************/ +/** Write an attribute from an array of int. + * + * Type conversion will be performed if needed. + * + * Exactly GetTotalElementsCount() strings must be provided + * + * This is the same as the C function GDALAttributeWriteIntArray() + * + * @param vals Array of int. + * @param nVals Should be equal to GetTotalElementsCount(). + * @return true in case of success. + */ +bool GDALAttribute::Write(const int *vals, size_t nVals) +{ + if (nVals != GetTotalElementsCount()) + { + CPLError(CE_Failure, CPLE_AppDefined, "Invalid number of input values"); + return false; + } + const auto nDims = GetDimensionCount(); + std::vector startIdx(1 + nDims, 0); + std::vector count(1 + nDims); + const auto &dims = GetDimensions(); + for (size_t i = 0; i < nDims; i++) + count[i] = static_cast(dims[i]->GetSize()); + return Write(startIdx.data(), count.data(), nullptr, nullptr, + GDALExtendedDataType::Create(GDT_Int32), vals, vals, + static_cast(GetTotalElementsCount()) * sizeof(GInt32)); +} + +/************************************************************************/ +/* Write() */ +/************************************************************************/ + +/** Write an attribute from an array of int64_t. + * + * Type conversion will be performed if needed. + * + * Exactly GetTotalElementsCount() strings must be provided + * + * This is the same as the C function GDALAttributeWriteLongArray() + * + * @param vals Array of int64_t. + * @param nVals Should be equal to GetTotalElementsCount(). + * @return true in case of success. + */ +bool GDALAttribute::Write(const int64_t *vals, size_t nVals) +{ + if (nVals != GetTotalElementsCount()) + { + CPLError(CE_Failure, CPLE_AppDefined, "Invalid number of input values"); + return false; + } + const auto nDims = GetDimensionCount(); + std::vector startIdx(1 + nDims, 0); + std::vector count(1 + nDims); + const auto &dims = GetDimensions(); + for (size_t i = 0; i < nDims; i++) + count[i] = static_cast(dims[i]->GetSize()); + return Write(startIdx.data(), count.data(), nullptr, nullptr, + GDALExtendedDataType::Create(GDT_Int64), vals, vals, + static_cast(GetTotalElementsCount()) * + sizeof(int64_t)); +} + +/************************************************************************/ +/* Write() */ +/************************************************************************/ + /** Write an attribute from an array of double. * * Type conversion will be performed if needed. @@ -5572,6 +5720,7 @@ CreateFieldNameExtractArray(const std::shared_ptr &self, * a view of it (not a copy), or nullptr in case of error. */ // clang-format on + std::shared_ptr GDALMDArray::GetView(const std::string &viewExpr) const { @@ -12721,7 +12870,7 @@ const char *GDALAttributeReadAsString(GDALAttributeH hAttr) * * This function will only return the first element if there are several. * - * It can fail if its value can be converted to integer. + * It can fail if its value can not be converted to integer. * * This is the same as the C++ method GDALAttribute::ReadAsInt() * @@ -12733,6 +12882,26 @@ int GDALAttributeReadAsInt(GDALAttributeH hAttr) return hAttr->m_poImpl->ReadAsInt(); } +/************************************************************************/ +/* GDALAttributeReadAsInt64() */ +/************************************************************************/ + +/** Return the value of an attribute as a int64_t. + * + * This function will only return the first element if there are several. + * + * It can fail if its value can not be converted to integer. + * + * This is the same as the C++ method GDALAttribute::ReadAsInt64() + * + * @return an int64_t, or INT64_MIN in case of error. + */ +int64_t GDALAttributeReadAsInt64(GDALAttributeH hAttr) +{ + VALIDATE_POINTER1(hAttr, __func__, 0); + return hAttr->m_poImpl->ReadAsInt64(); +} + /************************************************************************/ /* GDALAttributeReadAsDouble() */ /************************************************************************/ @@ -12741,7 +12910,7 @@ int GDALAttributeReadAsInt(GDALAttributeH hAttr) * * This function will only return the first element if there are several. * - * It can fail if its value can be converted to double. + * It can fail if its value can not be converted to double. * * This is the same as the C++ method GDALAttribute::ReadAsDouble() * @@ -12797,6 +12966,35 @@ int *GDALAttributeReadAsIntArray(GDALAttributeH hAttr, size_t *pnCount) return ret; } +/************************************************************************/ +/* GDALAttributeReadAsInt64Array() */ +/************************************************************************/ + +/** Return the value of an attribute as an array of int64_t. + * + * This is the same as the C++ method GDALAttribute::ReadAsInt64Array() + * + * @param hAttr Attribute + * @param pnCount Pointer to the number of values returned. Must NOT be NULL. + * @return array to be freed with CPLFree(), or nullptr. + */ +int64_t *GDALAttributeReadAsInt64Array(GDALAttributeH hAttr, size_t *pnCount) +{ + VALIDATE_POINTER1(hAttr, __func__, nullptr); + VALIDATE_POINTER1(pnCount, __func__, nullptr); + *pnCount = 0; + auto tmp(hAttr->m_poImpl->ReadAsInt64Array()); + if (tmp.empty()) + return nullptr; + auto ret = static_cast( + VSI_MALLOC2_VERBOSE(tmp.size(), sizeof(int64_t))); + if (!ret) + return nullptr; + memcpy(ret, tmp.data(), tmp.size() * sizeof(int64_t)); + *pnCount = tmp.size(); + return ret; +} + /************************************************************************/ /* GDALAttributeReadAsDoubleArray() */ /************************************************************************/ @@ -12893,6 +13091,27 @@ int GDALAttributeWriteInt(GDALAttributeH hAttr, int nVal) return hAttr->m_poImpl->WriteInt(nVal); } +/************************************************************************/ +/* GDALAttributeWriteInt64() */ +/************************************************************************/ + +/** Write an attribute from an int64_t value. + * + * Type conversion will be performed if needed. If the attribute contains + * multiple values, only the first one will be updated. + * + * This is the same as the C++ method GDALAttribute::WriteLong() + * + * @param hAttr Attribute + * @param nVal Value. + * @return TRUE in case of success. + */ +int GDALAttributeWriteInt64(GDALAttributeH hAttr, int64_t nVal) +{ + VALIDATE_POINTER1(hAttr, __func__, FALSE); + return hAttr->m_poImpl->WriteInt64(nVal); +} + /************************************************************************/ /* GDALAttributeWriteDouble() */ /************************************************************************/ @@ -12938,6 +13157,56 @@ int GDALAttributeWriteStringArray(GDALAttributeH hAttr, return hAttr->m_poImpl->Write(papszValues); } +/************************************************************************/ +/* GDALAttributeWriteIntArray() */ +/************************************************************************/ + +/** Write an attribute from an array of int. + * + * Type conversion will be performed if needed. + * + * Exactly GetTotalElementsCount() strings must be provided + * + * This is the same as the C++ method GDALAttribute::Write(const int *, + * size_t) + * + * @param hAttr Attribute + * @param panValues Array of int. + * @param nCount Should be equal to GetTotalElementsCount(). + * @return TRUE in case of success. + */ +int GDALAttributeWriteIntArray(GDALAttributeH hAttr, const int *panValues, + size_t nCount) +{ + VALIDATE_POINTER1(hAttr, __func__, FALSE); + return hAttr->m_poImpl->Write(panValues, nCount); +} + +/************************************************************************/ +/* GDALAttributeWriteInt64Array() */ +/************************************************************************/ + +/** Write an attribute from an array of int64_t. + * + * Type conversion will be performed if needed. + * + * Exactly GetTotalElementsCount() strings must be provided + * + * This is the same as the C++ method GDALAttribute::Write(const int64_t *, + * size_t) + * + * @param hAttr Attribute + * @param panValues Array of int64_t. + * @param nCount Should be equal to GetTotalElementsCount(). + * @return TRUE in case of success. + */ +int GDALAttributeWriteInt64Array(GDALAttributeH hAttr, const int64_t *panValues, + size_t nCount) +{ + VALIDATE_POINTER1(hAttr, __func__, FALSE); + return hAttr->m_poImpl->Write(panValues, nCount); +} + /************************************************************************/ /* GDALAttributeWriteDoubleArray() */ /************************************************************************/ diff --git a/swig/include/MultiDimensional.i b/swig/include/MultiDimensional.i index 39a38a6f4093..cdcd08550fc9 100644 --- a/swig/include/MultiDimensional.i +++ b/swig/include/MultiDimensional.i @@ -1272,6 +1272,10 @@ public: return GDALAttributeReadAsInt(self); } + long long ReadAsInt64() { + return GDALAttributeReadAsInt64(self); + } + double ReadAsDouble() { return GDALAttributeReadAsDouble(self); } @@ -1288,6 +1292,12 @@ public: } #endif +#if defined(SWIGPYTHON) + void ReadAsInt64Array( long long** pvals, size_t* pnCount ) { + *pvals = (long long*)GDALAttributeReadAsInt64Array(self, pnCount); + } +#endif + #if defined(SWIGPYTHON) void ReadAsDoubleArray( double** pvals, size_t* pnCount ) { *pvals = GDALAttributeReadAsDoubleArray(self, pnCount); @@ -1327,11 +1337,30 @@ public: return GDALAttributeWriteInt(self, val) ? CE_None : CE_Failure; } + CPLErr WriteInt64(long long val) + { + return GDALAttributeWriteInt64(self, val) ? CE_None : CE_Failure; + } + CPLErr WriteDouble(double val) { return GDALAttributeWriteDouble(self, val) ? CE_None : CE_Failure; } +#if defined(SWIGPYTHON) + CPLErr WriteIntArray(int nList, int* pList) + { + return GDALAttributeWriteIntArray(self, pList, nList) ? CE_None : CE_Failure; + } +#endif + +#if defined(SWIGPYTHON) + CPLErr WriteInt64Array(int nList, long long* pList) + { + return GDALAttributeWriteInt64Array(self, (int64_t*)pList, nList) ? CE_None : CE_Failure; + } +#endif + #if defined(SWIGPYTHON) CPLErr WriteDoubleArray(int nList, double* pList) { diff --git a/swig/include/python/gdal_python.i b/swig/include/python/gdal_python.i index 086d8089bf5a..d26dbd535c2e 100644 --- a/swig/include/python/gdal_python.i +++ b/swig/include/python/gdal_python.i @@ -2032,39 +2032,49 @@ def GetMDArrayNames(self, options = []) -> "list[str]": return s return self.ReadAsStringArray() if dt_class == GEDTC_NUMERIC: - if dt.GetNumericDataType() in (GDT_Byte, GDT_Int8, GDT_Int16, GDT_UInt16, GDT_Int32): + if dt.GetNumericDataType() in (GDT_Byte, GDT_UInt16, + GDT_Int8, GDT_Int16, GDT_Int32): if self.GetTotalElementsCount() == 1: return self.ReadAsInt() - else: - return self.ReadAsIntArray() - else: + return self.ReadAsIntArray() + if dt.GetNumericDataType() in (GDT_UInt32, GDT_Int64): if self.GetTotalElementsCount() == 1: - return self.ReadAsDouble() - else: - return self.ReadAsDoubleArray() + return self.ReadAsInt64() + return self.ReadAsInt64Array() + if self.GetTotalElementsCount() == 1: + return self.ReadAsDouble() + return self.ReadAsDoubleArray() return self.ReadAsRaw() def Write(self, val): if isinstance(val, (int, type(12345678901234))): if val >= -0x80000000 and val <= 0x7FFFFFFF: return self.WriteInt(val) - else: - return self.WriteDouble(val) + if val >= -0x8000000000000000 and val <= 0x7FFFFFFFFFFFFFFF: + return self.WriteInt64(val) + return self.WriteDouble(val) if isinstance(val, float): - return self.WriteDouble(val) + return self.WriteDouble(val) if isinstance(val, str) and self.GetDataType().GetClass() != GEDTC_COMPOUND: - return self.WriteString(val) + return self.WriteString(val) if isinstance(val, list): - if len(val) == 0: - if self.GetDataType().GetClass() == GEDTC_STRING: - return self.WriteStringArray(val) - else: + if len(val) == 0: + if self.GetDataType().GetClass() == GEDTC_STRING: + return self.WriteStringArray(val) + return self.WriteDoubleArray(val) + if isinstance(val[0], (int, type(12345678901234))): + if all(v >= -0x80000000 and v <= 0x7FFFFFFF for v in val): + return self.WriteIntArray(val) + if all(v >= -0x8000000000000000 and v <= 0x7FFFFFFFFFFFFFFF + for v in val): + return self.WriteInt64Array(val) return self.WriteDoubleArray(val) - if isinstance(val[0], (int, type(12345678901234), float)): - return self.WriteDoubleArray(val) - if isinstance(val[0], str): - return self.WriteStringArray(val) - if isinstance(val, dict) and self.GetDataType().GetSubType() == GEDTST_JSON: + if isinstance(val[0], float): + return self.WriteDoubleArray(val) + if isinstance(val[0], str): + return self.WriteStringArray(val) + if (isinstance(val, dict) and + self.GetDataType().GetSubType() == GEDTST_JSON): import json return self.WriteString(json.dumps(val)) return self.WriteRaw(val) diff --git a/swig/include/python/typemaps_python.i b/swig/include/python/typemaps_python.i index 2531c60f39d8..ffab7c912bbe 100644 --- a/swig/include/python/typemaps_python.i +++ b/swig/include/python/typemaps_python.i @@ -151,7 +151,7 @@ %enddef %define TYPEMAP_ARGOUT_ARGOUT_ARRAY_IS_VALID(num_values) -%typemap(argout) (double argout[num_values], int* isvalid) +%typemap(argout, fragment="t_output_helper,CreateTupleFromDoubleArray") (double argout[num_values], int* isvalid) { /* %typemap(argout) (double argout[num_values], int* isvalid) */ PyObject *r; @@ -236,6 +236,32 @@ TYPEMAP_ARGOUT_ARGOUT_ARRAY_IS_VALID(6) } } +%fragment("CreateTupleFromIntArray","header") %{ +static PyObject * +CreateTupleFromIntArray( const int *first, size_t size ) { + PyObject *out = PyTuple_New( size ); + for( unsigned int i=0; i (Py_ssize_t)INT_MAX ) { + PyErr_SetString(PyExc_RuntimeError, "too big sequence"); + *pnSize = -1; + return NULL; + } + if( (size_t)size > SIZE_MAX / sizeof(long long) ) { + PyErr_SetString(PyExc_RuntimeError, "too big sequence"); + *pnSize = -1; + return NULL; + } + *pnSize = (int)size; + long long* ret = (long long*) malloc((*pnSize)*sizeof(long long)); + if( !ret ) { + PyErr_SetString(PyExc_MemoryError, "cannot allocate temporary buffer"); + *pnSize = -1; + return NULL; + } + for( int i = 0; i<*pnSize; i++ ) { + PyObject *o = PySequence_GetItem(pySeq,i); + if ( !PyArg_Parse(o,"L",&ret[i]) ) { + PyErr_SetString(PyExc_TypeError, "not an integer"); + Py_DECREF(o); + free(ret); + *pnSize = -1; + return NULL; + } + Py_DECREF(o); + } + return ret; +} +%} + +/* + * Typemap for counted arrays of int64s <- PySequence + */ +%typemap(in,numinputs=1,fragment="CreateCInt64ListFromSequence") (int nList, long long* pList) +{ + /* %typemap(in,numinputs=1) (int nList, long long* pList)*/ + $2 = CreateCInt64ListFromSequence($input, &$1); + if( $1 < 0 ) { + SWIG_fail; + } +} + +%typemap(freearg) (int nList, long long* pList) +{ + /* %typemap(freearg) (int nList, long long* pList) */ + free($2); +} + %fragment("CreateCGIntBigListFromSequence","header") %{ static GIntBig* CreateCGIntBigListFromSequence( PyObject* pySeq, int* pnSize ) { @@ -545,19 +631,6 @@ CreateCDoubleListFromSequence( PyObject* pySeq, int* pnSize ) { free($2); } -%fragment("CreateTupleFromIntegerArray","header") %{ -static PyObject * -CreateTupleFromDoubleArray( int *first, unsigned int size ) { - PyObject *out = PyTuple_New( size ); - for( unsigned int i=0; i Date: Sun, 2 Jun 2024 19:37:24 +0200 Subject: [PATCH 0038/1119] VICAR: avoid harmless unsigned integer overflow Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=69389 --- frmts/pds/vicardataset.cpp | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/frmts/pds/vicardataset.cpp b/frmts/pds/vicardataset.cpp index 62dc0cbffa29..77d11af8b636 100644 --- a/frmts/pds/vicardataset.cpp +++ b/frmts/pds/vicardataset.cpp @@ -945,27 +945,38 @@ CPLErr VICARBASICRasterBand::IReadBlock(int /*nXBlock*/, int nYBlock, CPLAssert(poGDS->m_anRecordOffsets[poGDS->m_nLastRecordOffset + 1] == 0); + int nRet; if (poGDS->m_eCompress == VICARDataset::COMPRESS_BASIC) { - VSIFSeekL(poGDS->fpImage, - poGDS->m_anRecordOffsets[poGDS->m_nLastRecordOffset] - - sizeof(GUInt32), - SEEK_SET); + nRet = + VSIFSeekL(poGDS->fpImage, + poGDS->m_anRecordOffsets[poGDS->m_nLastRecordOffset] - + sizeof(GUInt32), + SEEK_SET); } else { - VSIFSeekL(poGDS->fpImage, - poGDS->m_nImageOffsetWithoutNBB + - static_cast(sizeof(GUInt32)) * - poGDS->m_nLastRecordOffset, - SEEK_SET); + nRet = VSIFSeekL(poGDS->fpImage, + poGDS->m_nImageOffsetWithoutNBB + + static_cast(sizeof(GUInt32)) * + poGDS->m_nLastRecordOffset, + SEEK_SET); } GUInt32 nSize; - VSIFReadL(&nSize, 1, sizeof(nSize), poGDS->fpImage); + if (nRet != 0 || + VSIFReadL(&nSize, sizeof(nSize), 1, poGDS->fpImage) != 1) + { + CPLError(CE_Failure, CPLE_AppDefined, "Cannot read record %d size", + poGDS->m_nLastRecordOffset); + return CE_Failure; + } CPL_LSBPTR32(&nSize); if ((poGDS->m_eCompress == VICARDataset::COMPRESS_BASIC && nSize <= sizeof(GUInt32)) || - (poGDS->m_eCompress == VICARDataset::COMPRESS_BASIC2 && nSize == 0)) + (poGDS->m_eCompress == VICARDataset::COMPRESS_BASIC2 && + nSize == 0) || + poGDS->m_anRecordOffsets[poGDS->m_nLastRecordOffset] > + std::numeric_limits::max() - nSize) { CPLError(CE_Failure, CPLE_AppDefined, "Wrong size at record %d", poGDS->m_nLastRecordOffset); From c11cb59b508d6116df1bd1c863808e01a6f34e7a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 2 Jun 2024 00:59:59 +0200 Subject: [PATCH 0039/1119] netCDF: try to better round geotransform values when read from single-precsion lon/lat float arrays Related to https://lists.osgeo.org/pipermail/gdal-dev/2024-May/059050.html --- autotest/gdrivers/netcdf.py | 14 ++------ frmts/netcdf/netcdfdataset.cpp | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/autotest/gdrivers/netcdf.py b/autotest/gdrivers/netcdf.py index 07e6ca616fb4..ff21d67235b5 100755 --- a/autotest/gdrivers/netcdf.py +++ b/autotest/gdrivers/netcdf.py @@ -1359,18 +1359,8 @@ def test_netcdf_36(): gt = ds.GetGeoTransform() assert gt is not None, "got no GeoTransform" - gt_expected = ( - -3.498749944898817, - 0.0025000042385525173, - 0.0, - 46.61749818589952, - 0.0, - -0.001666598849826389, - ) - assert gt == gt_expected, "got GeoTransform %s, expected %s" % ( - str(gt), - str(gt_expected), - ) + gt_expected = (-3.49875, 0.0025, 0.0, 46.61749818589952, 0.0, -0.001666598849826389) + assert gt == pytest.approx(gt_expected, rel=1e-8) ############################################################################### diff --git a/frmts/netcdf/netcdfdataset.cpp b/frmts/netcdf/netcdfdataset.cpp index 69e030de649c..6ee312b5213f 100644 --- a/frmts/netcdf/netcdfdataset.cpp +++ b/frmts/netcdf/netcdfdataset.cpp @@ -3901,6 +3901,56 @@ void netCDFDataset::SetProjectionFromVar( double xMinMax[2] = {0.0, 0.0}; double yMinMax[2] = {0.0, 0.0}; + const auto RoundMinMaxForFloatVals = + [](double &dfMin, double &dfMax, int nIntervals) + { + // Helps for a case where longitudes range from + // -179.99 to 180.0 with a 0.01 degree spacing. + // However as this is encoded in a float array, + // -179.99 is actually read as -179.99000549316406 as + // a double. Try to detect that and correct the rounding + + const auto IsAlmostInteger = [](double dfVal) + { + constexpr double THRESHOLD_INTEGER = 1e-3; + return std::fabs(dfVal - std::round(dfVal)) <= + THRESHOLD_INTEGER; + }; + + const double dfSpacing = (dfMax - dfMin) / nIntervals; + if (dfSpacing > 0) + { + const double dfInvSpacing = 1.0 / dfSpacing; + if (IsAlmostInteger(dfInvSpacing)) + { + const double dfRoundedSpacing = + 1.0 / std::round(dfInvSpacing); + const double dfMinDivRoundedSpacing = + dfMin / dfRoundedSpacing; + const double dfMaxDivRoundedSpacing = + dfMax / dfRoundedSpacing; + if (IsAlmostInteger(dfMinDivRoundedSpacing) && + IsAlmostInteger(dfMaxDivRoundedSpacing)) + { + const double dfRoundedMin = + std::round(dfMinDivRoundedSpacing) * + dfRoundedSpacing; + const double dfRoundedMax = + std::round(dfMaxDivRoundedSpacing) * + dfRoundedSpacing; + if (static_cast(dfMin) == + static_cast(dfRoundedMin) && + static_cast(dfMax) == + static_cast(dfRoundedMax)) + { + dfMin = dfRoundedMin; + dfMax = dfRoundedMax; + } + } + } + } + }; + if (!nc_get_att_double(nGroupDimXID, nVarDimXID, "actual_range", adfActualRange)) { @@ -3920,6 +3970,12 @@ void netCDFDataset::SetProjectionFromVar( xMinMax[0] = pdfXCoord[0]; xMinMax[1] = pdfXCoord[xdim - 1]; node_offset = 0; + + if (nc_var_dimx_datatype == NC_FLOAT) + { + RoundMinMaxForFloatVals(xMinMax[0], xMinMax[1], + poDS->nRasterXSize - 1); + } } if (!nc_get_att_double(nGroupDimYID, nVarDimYID, "actual_range", @@ -3941,6 +3997,12 @@ void netCDFDataset::SetProjectionFromVar( yMinMax[0] = pdfYCoord[0]; yMinMax[1] = pdfYCoord[ydim - 1]; node_offset = 0; + + if (nc_var_dimy_datatype == NC_FLOAT) + { + RoundMinMaxForFloatVals(yMinMax[0], yMinMax[1], + poDS->nRasterYSize - 1); + } } double dfCoordOffset = 0.0; From 0420e5ab85c7260b9be8338174a02724c9765c5e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 2 Jun 2024 01:32:36 +0200 Subject: [PATCH 0040/1119] test_gdalwarp_lib.py: add test for warping an image with [-180,180] longitude to [180 - X, 180 + X] --- autotest/utilities/test_gdalwarp_lib.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/autotest/utilities/test_gdalwarp_lib.py b/autotest/utilities/test_gdalwarp_lib.py index fd3aeb2985a4..7deca9eb621b 100755 --- a/autotest/utilities/test_gdalwarp_lib.py +++ b/autotest/utilities/test_gdalwarp_lib.py @@ -4186,3 +4186,23 @@ def test_target_extent_consistent_size(): assert ds.RasterXSize == 4793 assert ds.RasterYSize == 4143 + + +############################################################################### +# Test warping an image with [-180,180] longitude to [180 - X, 180 + X] + + +def test_gdalwarp_lib_minus_180_plus_180_to_span_over_180(tmp_vsimem): + + dst_filename = str(tmp_vsimem / "out.tif") + src_ds = gdal.Open("../gdrivers/data/small_world.tif") + out_ds = gdal.Warp(dst_filename, src_ds, outputBounds=[0, -90, 360, 90]) + # Check that east/west hemispheres have been switched + assert out_ds.ReadRaster( + 0, 0, src_ds.RasterXSize // 2, src_ds.RasterYSize + ) == src_ds.ReadRaster( + src_ds.RasterXSize // 2, 0, src_ds.RasterXSize // 2, src_ds.RasterYSize + ) + assert out_ds.ReadRaster( + src_ds.RasterXSize // 2, 0, src_ds.RasterXSize // 2, src_ds.RasterYSize + ) == src_ds.ReadRaster(0, 0, src_ds.RasterXSize // 2, src_ds.RasterYSize) From 501fda81026adbcae282da8008fffe360327ec90 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 2 Jun 2024 02:07:47 +0200 Subject: [PATCH 0041/1119] Warper: relax longitude extend check to decide whether we can insert a CENTER_LONG wrapping longitude Related to https://lists.osgeo.org/pipermail/gdal-dev/2024-May/059050.html --- alg/gdaltransformer.cpp | 7 ++++- autotest/utilities/test_gdalwarp_lib.py | 34 +++++++++++++++++++++---- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/alg/gdaltransformer.cpp b/alg/gdaltransformer.cpp index bff7c93cc3bc..263fbc2fa08d 100644 --- a/alg/gdaltransformer.cpp +++ b/alg/gdaltransformer.cpp @@ -1482,7 +1482,12 @@ static void InsertCenterLong(GDALDatasetH hDS, OGRSpatialReference *poSRS, adfGeoTransform[0] + nXSize * adfGeoTransform[1] + nYSize * adfGeoTransform[2])); - if (dfMaxLong - dfMinLong > 360.0) + const double dfEpsilon = + std::max(std::fabs(adfGeoTransform[1]), std::fabs(adfGeoTransform[2])); + // If the raster covers more than 360 degree (allow an extra pixel), + // give up + constexpr double RELATIVE_EPSILON = 0.05; // for numeric precision issues + if (dfMaxLong - dfMinLong > 360.0 + dfEpsilon * (1 + RELATIVE_EPSILON)) return; /* -------------------------------------------------------------------- */ diff --git a/autotest/utilities/test_gdalwarp_lib.py b/autotest/utilities/test_gdalwarp_lib.py index 7deca9eb621b..a3f7b300a64d 100755 --- a/autotest/utilities/test_gdalwarp_lib.py +++ b/autotest/utilities/test_gdalwarp_lib.py @@ -4192,17 +4192,41 @@ def test_target_extent_consistent_size(): # Test warping an image with [-180,180] longitude to [180 - X, 180 + X] -def test_gdalwarp_lib_minus_180_plus_180_to_span_over_180(tmp_vsimem): +@pytest.mark.parametrize("extra_column", [False, True]) +def test_gdalwarp_lib_minus_180_plus_180_to_span_over_180(tmp_vsimem, extra_column): dst_filename = str(tmp_vsimem / "out.tif") src_ds = gdal.Open("../gdrivers/data/small_world.tif") + if extra_column: + tmp_ds = gdal.GetDriverByName("MEM").Create( + "", src_ds.RasterXSize + 1, src_ds.RasterYSize + ) + tmp_ds.SetGeoTransform(src_ds.GetGeoTransform()) + tmp_ds.SetSpatialRef(src_ds.GetSpatialRef()) + tmp_ds.WriteRaster( + 0, + 0, + src_ds.RasterXSize, + src_ds.RasterYSize, + src_ds.GetRasterBand(1).ReadRaster(), + ) + tmp_ds.WriteRaster( + src_ds.RasterXSize, + 0, + 1, + src_ds.RasterYSize, + src_ds.GetRasterBand(1).ReadRaster(0, 0, 1, src_ds.RasterYSize), + ) + src_ds = tmp_ds out_ds = gdal.Warp(dst_filename, src_ds, outputBounds=[0, -90, 360, 90]) # Check that east/west hemispheres have been switched - assert out_ds.ReadRaster( + assert out_ds.GetRasterBand(1).ReadRaster( 0, 0, src_ds.RasterXSize // 2, src_ds.RasterYSize - ) == src_ds.ReadRaster( + ) == src_ds.GetRasterBand(1).ReadRaster( src_ds.RasterXSize // 2, 0, src_ds.RasterXSize // 2, src_ds.RasterYSize ) - assert out_ds.ReadRaster( + assert out_ds.GetRasterBand(1).ReadRaster( src_ds.RasterXSize // 2, 0, src_ds.RasterXSize // 2, src_ds.RasterYSize - ) == src_ds.ReadRaster(0, 0, src_ds.RasterXSize // 2, src_ds.RasterYSize) + ) == src_ds.GetRasterBand(1).ReadRaster( + 0, 0, src_ds.RasterXSize // 2, src_ds.RasterYSize + ) From c05c764756d2a2249a32dd8aa3febea59c08baeb Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 3 Jun 2024 00:06:50 +0200 Subject: [PATCH 0042/1119] Fix various Coverity warnings (master only) --- frmts/tiledb/tiledbdense.cpp | 2 +- .../parquet/ogrparquetdatasetlayer.cpp | 31 ++++++++++++------- ogr/ogrsf_frmts/parquet/ogrparquetlayer.cpp | 2 +- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/frmts/tiledb/tiledbdense.cpp b/frmts/tiledb/tiledbdense.cpp index 8960ede77598..2f29e0fa1344 100644 --- a/frmts/tiledb/tiledbdense.cpp +++ b/frmts/tiledb/tiledbdense.cpp @@ -2946,7 +2946,7 @@ CPLErr TileDBRasterDataset::IBuildOverviews( { try { - poODS->Close(); + CPL_IGNORE_RET_VAL(poODS->Close()); tiledb::Array::delete_array(*m_ctx, poODS->GetDescription()); vfs.remove_dir(poODS->GetDescription()); } diff --git a/ogr/ogrsf_frmts/parquet/ogrparquetdatasetlayer.cpp b/ogr/ogrsf_frmts/parquet/ogrparquetdatasetlayer.cpp index d6c55f63cced..e0f76cf999ed 100644 --- a/ogr/ogrsf_frmts/parquet/ogrparquetdatasetlayer.cpp +++ b/ogr/ogrsf_frmts/parquet/ogrparquetdatasetlayer.cpp @@ -566,7 +566,7 @@ void OGRParquetDatasetLayer::BuildScanner() { auto fieldRefX(fieldRefs); fieldRefX.emplace_back("x"); - auto fieldRefY(fieldRefs); + auto fieldRefY(std::move(fieldRefs)); fieldRefY.emplace_back("y"); expression = cp::and_( {cp::less_equal( @@ -605,6 +605,8 @@ void OGRParquetDatasetLayer::BuildScanner() abyFilterGeomWkb.resize(m_poFilterGeom->WkbSize()); m_poFilterGeom->exportToWkb(wkbNDR, abyFilterGeomWkb.data(), wkbVariantIso); + // Silence 'Using uninitialized value oFieldRef. Field oFieldRef.impl_._M_u is uninitialized when calling FieldRef.' + // coverity[uninit_use_in_call] expression = cp::call("OGRWKBIntersects", {cp::field_ref(oFieldRef)}, WKBGeometryOptions(abyFilterGeomWkb)); @@ -656,7 +658,8 @@ void OGRParquetDatasetLayer::BuildScanner() } if (expression.is_valid()) - expression = cp::and_(expression, expressionFilter); + expression = + cp::and_(expression, std::move(expressionFilter)); else expression = std::move(expressionFilter); } @@ -702,8 +705,9 @@ OGRParquetDatasetLayer::BuildArrowFilter(const swq_expr_node *poNode, if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_AND && poNode->nSubExprCount == 2) { - auto sLeft = BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); - auto sRight = + const auto sLeft = + BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); + const auto sRight = BuildArrowFilter(poNode->papoSubExpr[1], bFullyTranslated); if (sLeft.is_valid() && sRight.is_valid()) return cp::and_(sLeft, sRight); @@ -716,8 +720,9 @@ OGRParquetDatasetLayer::BuildArrowFilter(const swq_expr_node *poNode, else if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_OR && poNode->nSubExprCount == 2) { - auto sLeft = BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); - auto sRight = + const auto sLeft = + BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); + const auto sRight = BuildArrowFilter(poNode->papoSubExpr[1], bFullyTranslated); if (sLeft.is_valid() && sRight.is_valid()) return cp::or_(sLeft, sRight); @@ -726,7 +731,8 @@ OGRParquetDatasetLayer::BuildArrowFilter(const swq_expr_node *poNode, else if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_NOT && poNode->nSubExprCount == 1) { - auto expr = BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); + const auto expr = + BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); if (expr.is_valid()) return cp::not_(expr); } @@ -839,8 +845,9 @@ OGRParquetDatasetLayer::BuildArrowFilter(const swq_expr_node *poNode, else if (poNode->eNodeType == SNT_OPERATION && poNode->nSubExprCount == 2 && IsComparisonOp(poNode->nOperation)) { - auto sLeft = BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); - auto sRight = + const auto sLeft = + BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); + const auto sRight = BuildArrowFilter(poNode->papoSubExpr[1], bFullyTranslated); if (sLeft.is_valid() && sRight.is_valid()) { @@ -865,7 +872,8 @@ OGRParquetDatasetLayer::BuildArrowFilter(const swq_expr_node *poNode, poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT && poNode->papoSubExpr[1]->field_type == SWQ_STRING) { - auto sLeft = BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); + const auto sLeft = + BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); if (sLeft.is_valid()) { if (cp::GetFunctionRegistry() @@ -885,7 +893,8 @@ OGRParquetDatasetLayer::BuildArrowFilter(const swq_expr_node *poNode, else if (poNode->eNodeType == SNT_OPERATION && poNode->nOperation == SWQ_ISNULL && poNode->nSubExprCount == 1) { - auto expr = BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); + const auto expr = + BuildArrowFilter(poNode->papoSubExpr[0], bFullyTranslated); if (expr.is_valid()) return cp::is_null(expr); } diff --git a/ogr/ogrsf_frmts/parquet/ogrparquetlayer.cpp b/ogr/ogrsf_frmts/parquet/ogrparquetlayer.cpp index f09eb7989b61..e0c33f578788 100644 --- a/ogr/ogrsf_frmts/parquet/ogrparquetlayer.cpp +++ b/ogr/ogrsf_frmts/parquet/ogrparquetlayer.cpp @@ -536,7 +536,7 @@ OGRParquetLayer::OGRParquetLayer( { pszUseThreads = "YES"; } - if (CPLTestBool(pszUseThreads)) + if (pszUseThreads && CPLTestBool(pszUseThreads)) { m_poArrowReader->set_use_threads(true); } From 2bec058a6321312fe2972306e21d9ec410d2408d Mon Sep 17 00:00:00 2001 From: AbelPau Date: Mon, 3 Jun 2024 09:47:39 +0200 Subject: [PATCH 0043/1119] MiraMonVector: fixing accepted metadata versions/subversions --- ogr/ogrsf_frmts/miramon/mm_gdal_driver_structs.h | 2 ++ ogr/ogrsf_frmts/miramon/mm_wrlayr.c | 16 ++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ogr/ogrsf_frmts/miramon/mm_gdal_driver_structs.h b/ogr/ogrsf_frmts/miramon/mm_gdal_driver_structs.h index 2e9320df42d5..84548492f338 100644 --- a/ogr/ogrsf_frmts/miramon/mm_gdal_driver_structs.h +++ b/ogr/ogrsf_frmts/miramon/mm_gdal_driver_structs.h @@ -20,9 +20,11 @@ CPL_C_START // Necessary for compiling in GDAL project #define KEY_Vers "Vers" #define KEY_SubVers "SubVers" #define MM_VERS 4 +#define MM_SUBVERS_ACCEPTED 0 #define MM_SUBVERS 3 #define KEY_VersMetaDades "VersMetaDades" #define KEY_SubVersMetaDades "SubVersMetaDades" +#define MM_VERS_METADADES_ACCEPTED 4 #define MM_VERS_METADADES 5 #define MM_SUBVERS_METADADES 0 #define SECTION_METADADES "METADADES" diff --git a/ogr/ogrsf_frmts/miramon/mm_wrlayr.c b/ogr/ogrsf_frmts/miramon/mm_wrlayr.c index e96bdba99a21..5acc9cde92c8 100644 --- a/ogr/ogrsf_frmts/miramon/mm_wrlayr.c +++ b/ogr/ogrsf_frmts/miramon/mm_wrlayr.c @@ -6102,16 +6102,16 @@ int MMCheck_REL_FILE(const char *szREL_file) return 1; } - // SubVers>=3? + // SubVers>=0? pszLine = MMReturnValueFromSectionINIFile(szREL_file, SECTION_VERSIO, KEY_SubVers); if (pszLine) { - if (*pszLine == '\0' || atoi(pszLine) < (int)MM_SUBVERS) + if (*pszLine == '\0' || atoi(pszLine) < (int)MM_SUBVERS_ACCEPTED) { MMCPLError(CE_Failure, CPLE_OpenFailed, "The file \"%s\" must have %s>=%d.", szREL_file, - KEY_SubVers, MM_SUBVERS); + KEY_SubVers, MM_SUBVERS_ACCEPTED); free_function(pszLine); return 1; @@ -6122,20 +6122,20 @@ int MMCheck_REL_FILE(const char *szREL_file) { MMCPLError(CE_Failure, CPLE_OpenFailed, "The file \"%s\" must have %s>=%d.", szREL_file, KEY_SubVers, - MM_SUBVERS); + MM_SUBVERS_ACCEPTED); return 1; } - // VersMetaDades>=5? + // VersMetaDades>=4? pszLine = MMReturnValueFromSectionINIFile(szREL_file, SECTION_VERSIO, KEY_VersMetaDades); if (pszLine) { - if (*pszLine == '\0' || atoi(pszLine) < (int)MM_VERS_METADADES) + if (*pszLine == '\0' || atoi(pszLine) < (int)MM_VERS_METADADES_ACCEPTED) { MMCPLError(CE_Failure, CPLE_OpenFailed, "The file \"%s\" must have %s>=%d.", szREL_file, - KEY_VersMetaDades, MM_VERS_METADADES); + KEY_VersMetaDades, MM_VERS_METADADES_ACCEPTED); free_function(pszLine); return 1; } @@ -6145,7 +6145,7 @@ int MMCheck_REL_FILE(const char *szREL_file) { MMCPLError(CE_Failure, CPLE_OpenFailed, "The file \"%s\" must have %s>=%d.", szREL_file, - KEY_VersMetaDades, MM_VERS_METADADES); + KEY_VersMetaDades, MM_VERS_METADADES_ACCEPTED); return 1; } From 58d7081e7f98c8cd69c89af5efcaa7268e385922 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 3 Jun 2024 10:47:26 +0200 Subject: [PATCH 0044/1119] [argparse] update library Update to PR https://github.com/p-ranav/argparse/pull/356 Also fixes the incompatibility between store_into and scan and action: when the three methods above were called, being all based on the (unique) action, the last one would overwrite the previous ones. This issue was making the parser strictly dependant on the order of the scan/store_into/action calls making them mutually exclusive. --- apps/argparse/README.TXT | 3 ++- apps/argparse/argparse.hpp | 46 +++++++++++++++++--------------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/apps/argparse/README.TXT b/apps/argparse/README.TXT index 395f64ad669b..d2f296a7f1c8 100644 --- a/apps/argparse/README.TXT +++ b/apps/argparse/README.TXT @@ -1,3 +1,4 @@ Provenance: https://github.com/p-ranav/argparse -Retrieved from https://github.com/p-ranav/argparse/blob/b85a0a414d415fccdf6d77072421b1d3887c8f79/include/argparse/argparse.hpp +Retrieved: https://github.com/p-ranav/argparse/blob/8dead89026466b3818e9c6b6b1d938600db39d8f/include/argparse/argparse.hpp + diff --git a/apps/argparse/argparse.hpp b/apps/argparse/argparse.hpp index d472ba933023..4767b9e125af 100644 --- a/apps/argparse/argparse.hpp +++ b/apps/argparse/argparse.hpp @@ -678,9 +678,9 @@ class Argument { std::is_void_v>, void_action, valued_action>; if constexpr (sizeof...(Args) == 0) { - m_action.emplace(std::forward(callable)); + m_actions.emplace_back(std::forward(callable)); } else { - m_action.emplace( + m_actions.emplace_back( [f = std::forward(callable), tup = std::make_tuple(std::forward(bound_args)...)]( std::string const &opt) mutable { @@ -702,14 +702,7 @@ class Argument { template ::value>::type * = nullptr> auto &store_into(T &var) { if (m_default_value.has_value()) { - try - { - var = std::any_cast(m_default_value); - } - catch (...) - { - var = static_cast(std::any_cast(m_default_value)); - } + var = std::any_cast(m_default_value); } action([&var](const auto &s) { var = details::parse_number()(s); @@ -719,14 +712,7 @@ class Argument { auto &store_into(double &var) { if (m_default_value.has_value()) { - try - { - var = std::any_cast(m_default_value); - } - catch (...) - { - var = std::any_cast(m_default_value); - } + var = std::any_cast(m_default_value); } action([&var](const auto &s) { var = details::parse_number()(s); @@ -994,7 +980,12 @@ class Argument { if (num_args_max == 0) { if (!dry_run) { m_values.emplace_back(m_implicit_value); - std::visit([](const auto &f) { f({}); }, m_action); + for(auto &action: m_actions) { + std::visit([&](const auto &f) { f({}); }, action); + } + if(m_actions.empty()){ + std::visit([&](const auto &f) { f({}); }, m_default_action); + } m_is_used = true; } return start; @@ -1011,8 +1002,7 @@ class Argument { std::bind(is_optional, std::placeholders::_1, m_prefix_chars)); dist = static_cast(std::distance(start, end)); if (dist < num_args_min) { - throw std::runtime_error("Too few arguments for '" + - std::string(m_used_name) + "'."); + throw std::runtime_error("Too few arguments"); } } @@ -1035,7 +1025,12 @@ class Argument { Argument &self; }; if (!dry_run) { - std::visit(ActionApply{start, end, *this}, m_action); + for(auto &action: m_actions) { + std::visit(ActionApply{start, end, *this}, action); + } + if(m_actions.empty()){ + std::visit(ActionApply{start, end, *this}, m_default_action); + } m_is_used = true; } return end; @@ -1585,9 +1580,10 @@ class Argument { std::optional> m_choices{std::nullopt}; using valued_action = std::function; using void_action = std::function; - std::variant m_action{ - std::in_place_type, - [](const std::string &value) { return value; }}; + std::vector> m_actions; + std::variant m_default_action{ + std::in_place_type, + [](const std::string &value) { return value; }}; std::vector m_values; NArgsRange m_num_args_range{1, 1}; // Bit field of bool values. Set default value in ctor. From 0af71522237ac9a99ce76b668fdbe4238b6fc77a Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 3 Jun 2024 10:58:09 +0200 Subject: [PATCH 0045/1119] Apply https://github.com/p-ranav/argparse/pull/360 Add argument name after 'Too few arguments' error --- apps/argparse/argparse.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/argparse/argparse.hpp b/apps/argparse/argparse.hpp index 4767b9e125af..0ff318b74615 100644 --- a/apps/argparse/argparse.hpp +++ b/apps/argparse/argparse.hpp @@ -1002,7 +1002,8 @@ class Argument { std::bind(is_optional, std::placeholders::_1, m_prefix_chars)); dist = static_cast(std::distance(start, end)); if (dist < num_args_min) { - throw std::runtime_error("Too few arguments"); + throw std::runtime_error("Too few arguments for '" + + std::string(m_used_name) + "'."); } } From 03a0ee915117383228f3c9dd79f4cce7518f6059 Mon Sep 17 00:00:00 2001 From: Thomas Jurk Date: Mon, 3 Jun 2024 12:17:11 +0200 Subject: [PATCH 0046/1119] Update OAPIF Samples The OAPIF examples contain outdated URLs --- doc/source/drivers/vector/oapif.rst | 180 ++++++++++++++++------------ 1 file changed, 102 insertions(+), 78 deletions(-) diff --git a/doc/source/drivers/vector/oapif.rst b/doc/source/drivers/vector/oapif.rst index a5c98b3243c7..370f90559f22 100644 --- a/doc/source/drivers/vector/oapif.rst +++ b/doc/source/drivers/vector/oapif.rst @@ -143,106 +143,130 @@ Examples :: - $ ogrinfo OAPIF:https://www.ldproxy.nrw.de/rest/services/kataster - - INFO: Open of `OAPIF:https://www.ldproxy.nrw.de/rest/services/kataster' + $ ogrinfo OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr + + INFO: Open of `OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr' using driver `OAPIF' successful. - 1: flurstueck (Multi Polygon) - 2: gebaeudebauwerk (Multi Polygon) - 3: verwaltungseinheit (Multi Polygon) + 1: governmentalservice (title: Feuerwehrleitstellen) (Point) - Listing the summary information of a OGC API - Features layer : :: - $ ogrinfo -al -so OAPIF:https://www.ldproxy.nrw.de/rest/services/kataster flurstueck - - Layer name: flurstueck + $ ogrinfo OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr governmentalservice -al -so + + INFO: Open of `OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr' + using driver `OAPIF' successful. + + Layer name: governmentalservice Metadata: - TITLE=Flurstück - Geometry: Multi Polygon - Feature Count: 9308456 - Extent: (5.612726, 50.237351) - (9.589634, 52.528630) + DESCRIPTION=Staatliche Verwaltungs- und Sozialdienste wie öffentliche Verwaltung, Katastrophenschutz, Schulen und Krankenhäuser, die von öffentlichen oder privaten Einrichtungen erbracht werden, soweit sie in den Anwendungsbereich der Richtlinie 2007/2/EG fallen. Dieser Datensatz enthält Informationen zu Feuerwehrleitstellen. + TITLE=Feuerwehrleitstellen + Geometry: Point + Feature Count: 52 + Extent: (6.020720, 50.654901) - (9.199363, 52.300806) Layer SRS WKT: - GEOGCS["WGS 84", - DATUM["WGS_1984", - SPHEROID["WGS 84",6378137,298.257223563, - AUTHORITY["EPSG","7030"]], - AUTHORITY["EPSG","6326"]], + GEOGCRS["WGS 84", + DATUM["World Geodetic System 1984", + ELLIPSOID["WGS 84",6378137,298.257223563, + LENGTHUNIT["metre",1]]], PRIMEM["Greenwich",0, - AUTHORITY["EPSG","8901"]], - UNIT["degree",0.0174532925199433, - AUTHORITY["EPSG","9122"]], - AUTHORITY["EPSG","4326"]] + ANGLEUNIT["degree",0.0174532925199433]], + CS[ellipsoidal,2], + AXIS["geodetic latitude (Lat)",north, + ORDER[1], + ANGLEUNIT["degree",0.0174532925199433]], + AXIS["geodetic longitude (Lon)",east, + ORDER[2], + ANGLEUNIT["degree",0.0174532925199433]], + ID["EPSG",4326]] + Data axis to CRS axis mapping: 2,1 id: String (0.0) - aktualit: Date (0.0) - flaeche: Real (0.0) - flstkennz: String (0.0) - land: String (0.0) - gemarkung: String (0.0) - flur: String (0.0) - flurstnr: String (0.0) - gmdschl: String (0.0) - regbezirk: String (0.0) - kreis: String (0.0) - gemeinde: String (0.0) - lagebeztxt: String (0.0) - tntxt: String (0.0) + name: String (0.0) + inspireId: String (0.0) + serviceType.title: String (0.0) + serviceType.href: String (0.0) + areaOfResponsibility.1.title: String (0.0) + areaOfResponsibility.1.href: String (0.0) + pointOfContact.address.thoroughfare: String (0.0) + pointOfContact.address.locatorDesignator: String (0.0) + pointOfContact.address.postCode: String (0.0) + pointOfContact.address.adminUnit: String (0.0) + pointOfContact.address.text: String (0.0) + pointOfContact.telephoneVoice: String (0.0) + pointOfContact.telephoneFacsimile: String (0.0) + pointOfContact.telephoneFacsimileEmergency: String (0.0) + inDistrict.title: String (0.0) + inDistrict.href: String (0.0) + inDistrictFreeTown.title: String (0.0) + inDistrictFreeTown.href: String (0.0) + inGovernmentalDistrict.title: String (0.0) + inGovernmentalDistrict.href: String (0.0) - Filtering on a property (depending on if the server exposes filtering capabilities of the properties, part or totally of the filter might be evaluated on client side) :: - - $ ogrinfo OAPIF:https://www.ldproxy.nrw.de/rest/services/kataster flurstueck -al -q -where "flur = '028'" - Layer name: flurstueck + $ ogrinfo OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr governmentalservice -al -q -where "name = 'Schwelm'" + + Layer name: governmentalservice Metadata: - TITLE=Flurstück - OGRFeature(flurstueck):1 - id (String) = DENW19AL0000geMFFL - aktualit (Date) = 2017/04/26 - flaeche (Real) = 1739 - flstkennz (String) = 05297001600193______ - land (String) = Nordrhein-Westfalen - gemarkung (String) = Wünnenberg - flur (String) = 016 - flurstnr (String) = 193 - gmdschl (String) = 05774040 - regbezirk (String) = Detmold - kreis (String) = Paderborn - gemeinde (String) = Bad Wünnenberg - lagebeztxt (String) = Bleiwäscher Straße - tntxt (String) = Platz / Parkplatz;1739 - MULTIPOLYGON (((8.71191 51.491084,8.7123 51.491067,8.712385 51.491645,8.712014 51.491666,8.711993 51.491603,8.71196 51.491396,8.711953 51.491352,8.71191 51.491084))) - - [...] + DESCRIPTION=Staatliche Verwaltungs- und Sozialdienste wie öffentliche Verwaltung, Katastrophenschutz, Schulen und Krankenhäuser, die von öffentlichen oder privaten Einrichtungen erbracht werden, soweit sie in den Anwendungsbereich der Richtlinie 2007/2/EG fallen. Dieser Datensatz enthält Informationen zu Feuerwehrleitstellen. + TITLE=Feuerwehrleitstellen + OGRFeature(governmentalservice):1 + id (String) = LtS01 + name (String) = Schwelm + inspireId (String) = https://geodaten.nrw.de/id/inspire-us-feuerwehr/governmentalservice/LtS01 + serviceType.title (String) = Brandschutzdienst + serviceType.href (String) = http://inspire.ec.europa.eu/codelist/ServiceTypeValue/fireProtectionService + areaOfResponsibility.1.title (String) = Breckerfeld + areaOfResponsibility.1.href (String) = https://registry.gdi-de.org/id/de.nw.inspire.au.basis-dlm/AdministrativeUnit_05954004 + pointOfContact.address.thoroughfare (String) = Hauptstr. + pointOfContact.address.locatorDesignator (String) = 92 + pointOfContact.address.postCode (String) = 58332 + pointOfContact.address.adminUnit (String) = Schwelm + pointOfContact.address.text (String) = Hauptstr. 92, 58332 Schwelm + pointOfContact.telephoneVoice (String) = +49233644400 + pointOfContact.telephoneFacsimile (String) = +4923364440400 + pointOfContact.telephoneFacsimileEmergency (String) = +49233644407100 + inDistrict.title (String) = Ennepe-Ruhr + inDistrict.href (String) = Ennepe-Ruhr + inGovernmentalDistrict.title (String) = Arnsberg + inGovernmentalDistrict.href (String) = https://registry.gdi-de.org/id/de.nw.inspire.au.basis-dlm/AdministrativeUnit_059 + POINT (7.29854802787082 51.2855116825595) + - Spatial filtering :: - $ ogrinfo OAPIF:https://www.ldproxy.nrw.de/rest/services/kataster flurstueck -al -q -spat 8.7 51.4 8.8 51.5 - - Layer name: flurstueck + $ ogrinfo OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr governmentalservice -al -q -spat 7.1 51.2 7.2 51.5 + + Layer name: governmentalservice Metadata: - TITLE=Flurstück - OGRFeature(flurstueck):1 - id (String) = DENW19AL0000ht7LFL - aktualit (Date) = 2013/02/19 - flaeche (Real) = 18 - flstkennz (String) = 05292602900206______ - land (String) = Nordrhein-Westfalen - gemarkung (String) = Fürstenberg - flur (String) = 029 - flurstnr (String) = 206 - gmdschl (String) = 05774040 - regbezirk (String) = Detmold - kreis (String) = Paderborn - gemeinde (String) = Bad Wünnenberg - lagebeztxt (String) = Karpke - tntxt (String) = Fließgewässer / Bach;18 - MULTIPOLYGON (((8.768521 51.494915,8.768535 51.494882,8.768569 51.494908,8.768563 51.494925,8.768521 51.494915))) - [...] + DESCRIPTION=Staatliche Verwaltungs- und Sozialdienste wie öffentliche Verwaltung, Katastrophenschutz, Schulen und Krankenhäuser, die von öffentlichen oder privaten Einrichtungen erbracht werden, soweit sie in den Anwendungsbereich der Richtlinie 2007/2/EG fallen. Dieser Datensatz enthält Informationen zu Feuerwehrleitstellen. + TITLE=Feuerwehrleitstellen + OGRFeature(governmentalservice):1 + id (String) = LtS33 + name (String) = Wuppertal-Solingen + inspireId (String) = https://geodaten.nrw.de/id/inspire-us-feuerwehr/governmentalservice/LtS33 + serviceType.title (String) = Brandschutzdienst + serviceType.href (String) = http://inspire.ec.europa.eu/codelist/ServiceTypeValue/fireProtectionService + areaOfResponsibility.1.title (String) = Wuppertal + areaOfResponsibility.1.href (String) = https://registry.gdi-de.org/id/de.nw.inspire.au.basis-dlm/AdministrativeUnit_05124000 + pointOfContact.address.thoroughfare (String) = August-Bebel-Str. + pointOfContact.address.locatorDesignator (String) = 55 + pointOfContact.address.postCode (String) = 42109 + pointOfContact.address.adminUnit (String) = Wuppertal + pointOfContact.address.text (String) = August-Bebel-Str. 55, 42109 Wuppertal + pointOfContact.telephoneVoice (String) = +492025631111 + pointOfContact.telephoneFacsimile (String) = +49202445331 + pointOfContact.telephoneFacsimileEmergency (String) = 112 + inDistrictFreeTown.title (String) = Wuppertal + inDistrictFreeTown.href (String) = Wuppertal + inGovernmentalDistrict.title (String) = Düsseldorf + inGovernmentalDistrict.href (String) = https://registry.gdi-de.org/id/de.nw.inspire.au.basis-dlm/AdministrativeUnit_051 + POINT (7.13806554104892 51.2674471939457) See Also -------- From 9ebbe545b5bb73554444b2a0ac3fabcc19e884b2 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Mon, 3 Jun 2024 14:10:12 +0200 Subject: [PATCH 0047/1119] Remove scan now that argparse supports multiple actions --- apps/gdal_create.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/gdal_create.cpp b/apps/gdal_create.cpp index d7c43b77f323..4984a0956579 100644 --- a/apps/gdal_create.cpp +++ b/apps/gdal_create.cpp @@ -127,7 +127,6 @@ GDALCreateAppOptionsGetParser(GDALCreateOptions *psOptions) .metavar("") .nargs(nargs_pattern::at_least_one) .append() - .scan<'g', double>() .action( [psOptions](const std::string &s) { From 99ed79f69161bcf6adf8eb89c3c52a88d8c89f80 Mon Sep 17 00:00:00 2001 From: Andrew Bell Date: Mon, 3 Jun 2024 13:51:37 -0400 Subject: [PATCH 0048/1119] gdal_viewshed: multi-threaded implementation (#9991) --- alg/viewshed.cpp | 1107 +++++++++++----------- alg/viewshed.h | 89 +- autotest/utilities/test_gdal_viewshed.py | 94 +- 3 files changed, 697 insertions(+), 593 deletions(-) diff --git a/alg/viewshed.cpp b/alg/viewshed.cpp index e5f81db19815..22f322bb1a3b 100644 --- a/alg/viewshed.cpp +++ b/alg/viewshed.cpp @@ -27,10 +27,10 @@ #include #include -#include +#include +#include #include "gdal_alg.h" -#include "gdal_priv.h" #include "gdal_priv_templates.hpp" #include "viewshed.h" @@ -197,13 +197,12 @@ GDALDatasetH GDALViewshedGenerate( "dfOutOfRangeVal out of range. Must be [0, 255]."); return nullptr; } - oOpts.visibleVal = static_cast(dfVisibleVal); - oOpts.invisibleVal = static_cast(dfInvisibleVal); - oOpts.outOfRangeVal = static_cast(dfOutOfRangeVal); + oOpts.visibleVal = dfVisibleVal; + oOpts.invisibleVal = dfInvisibleVal; + oOpts.outOfRangeVal = dfOutOfRangeVal; gdal::Viewshed v(oOpts); - //ABELL - Make a function for progress that captures the progress argument. v.run(hBand, pfnProgress, pProgressArg); return GDALDataset::FromHandle(v.output().release()); @@ -215,198 +214,253 @@ namespace gdal namespace { -bool AdjustHeightInRange(const double *adfGeoTransform, int iPixel, int iLine, - double &dfHeight, double dfDistance2, - double dfCurvCoeff, double dfSphereDiameter) +// Calculate the height adjustment factor. +double CalcHeightAdjFactor(const GDALDataset *poDataset, double dfCurveCoeff) { - if (dfDistance2 <= 0 && dfCurvCoeff == 0) - return true; + const OGRSpatialReference *poDstSRS = poDataset->GetSpatialRef(); - double dfX = adfGeoTransform[1] * iPixel + adfGeoTransform[2] * iLine; - double dfY = adfGeoTransform[4] * iPixel + adfGeoTransform[5] * iLine; - double dfR2 = dfX * dfX + dfY * dfY; + if (poDstSRS) + { + OGRErr eSRSerr; - /* calc adjustment */ - if (dfCurvCoeff != 0 && - dfSphereDiameter != std::numeric_limits::infinity()) - dfHeight -= dfCurvCoeff * dfR2 / dfSphereDiameter; + // If we can't get a SemiMajor axis from the SRS, it will be SRS_WGS84_SEMIMAJOR + double dfSemiMajor = poDstSRS->GetSemiMajor(&eSRSerr); - if (dfDistance2 > 0 && dfR2 > dfDistance2) - return false; + /* If we fetched the axis from the SRS, use it */ + if (eSRSerr != OGRERR_FAILURE) + return dfCurveCoeff / (dfSemiMajor * 2.0); - return true; + CPLDebug("GDALViewshedGenerate", + "Unable to fetch SemiMajor axis from spatial reference"); + } + return 0; } -double CalcHeightLine(int i, double Za, double Zo) +// Calculate the height at nDistance units along a line through the origin given the height +// at nDistance - 1 units along the line. +double CalcHeightLine(int nDistance, double Za) { - if (i == 1) + nDistance = std::abs(nDistance); + if (nDistance == 1) return Za; else - return (Za - Zo) / (i - 1) + Za; + return Za * nDistance / (nDistance - 1); } -double CalcHeightDiagonal(int i, int j, double Za, double Zb, double Zo) +// Calulate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0) +// and passing through the line connecting (i - 1, j, Za) and (i, j - 1, Zb). +// In other words, the origin and the two points form a plane and we're calculating Zc +// of the point (i, j, Zc), also on the plane. +double CalcHeightDiagonal(int i, int j, double Za, double Zb) { - return ((Za - Zo) * i + (Zb - Zo) * j) / (i + j - 1) + Zo; + return (Za * i + Zb * j) / (i + j - 1); } -double CalcHeightEdge(int i, int j, double Za, double Zb, double Zo) +// Calculate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0) +// and through the line connecting (i -1, j - 1, Za) and (i - 1, j, Zb). In other words, +// the origin and the other two points form a plane and we're calculating Zc of the +// point (i, j, Zc), also on the plane. +double CalcHeightEdge(int i, int j, double Za, double Zb) { if (i == j) - return CalcHeightLine(i, Za, Zo); + return CalcHeightLine(i, Za); else - return ((Za - Zo) * i + (Zb - Zo) * (j - i)) / (j - 1) + Zo; + return (Za * i + Zb * (j - i)) / (j - 1); } } // unnamed namespace -void Viewshed::setVisibility(int iPixel, double dfZ, double *padfZVal, - std::vector &vResult) -{ - if (padfZVal[iPixel] + oOpts.targetHeight < dfZ) - vResult[iPixel] = oOpts.invisibleVal; - else - vResult[iPixel] = oOpts.visibleVal; - - if (padfZVal[iPixel] < dfZ) - padfZVal[iPixel] = dfZ; -} - -double Viewshed::calcHeight(double dfDiagZ, double dfEdgeZ) +/// Calculate the output extent of the output raster in terms of the input raster. +/// +/// @param nX observer X position in the input raster +/// @param nY observer Y position in the input raster +/// @return false on error, true otherwise +bool Viewshed::calcOutputExtent(int nX, int nY) { - double dfHeight = dfEdgeZ; + // We start with the assumption that the output size matches the input. + oOutExtent.xStop = GDALGetRasterBandXSize(pSrcBand); + oOutExtent.yStop = GDALGetRasterBandYSize(pSrcBand); - switch (oOpts.cellMode) + if (nX < 0 || nX >= oOutExtent.xStop || nY < 0 || nY >= oOutExtent.yStop) { - case Viewshed::CellMode::Max: - dfHeight = std::max(dfDiagZ, dfEdgeZ); - break; - case Viewshed::CellMode::Min: - dfHeight = std::min(dfDiagZ, dfEdgeZ); - break; - case Viewshed::CellMode::Diagonal: - dfHeight = dfDiagZ; - break; - default: // Edge case set in initialization. - break; + CPLError(CE_Failure, CPLE_AppDefined, + "The observer location falls outside of the DEM area"); + return false; } - return dfHeight; -} -bool Viewshed::run(GDALRasterBandH hBand, GDALProgressFunc pfnProgress, - void *pProgressArg) -{ - if (!pfnProgress) - pfnProgress = GDALDummyProgress; - - if (!pfnProgress(0.0, "", pProgressArg)) + constexpr double EPSILON = 1e-8; + if (oOpts.maxDistance > 0) { - CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); - return false; - } + //ABELL - This assumes that the transformation is only a scaling. Should be fixed. + // Find the distance in the direction of the transformed unit vector in the X and Y + // directions and use those factors to determine the limiting values in the raster space. + int nXStart = static_cast( + std::floor(nX - adfInvTransform[1] * oOpts.maxDistance + EPSILON)); + int nXStop = static_cast( + std::ceil(nX + adfInvTransform[1] * oOpts.maxDistance - EPSILON) + + 1); + int nYStart = + static_cast(std::floor( + nY - std::fabs(adfInvTransform[5]) * oOpts.maxDistance + + EPSILON)) - + (adfInvTransform[5] > 0 ? 1 : 0); + int nYStop = static_cast( + std::ceil(nY + std::fabs(adfInvTransform[5]) * oOpts.maxDistance - + EPSILON) + + (adfInvTransform[5] < 0 ? 1 : 0)); - /* set up geotransformation */ - std::array adfGeoTransform{{0.0, 1.0, 0.0, 0.0, 0.0, 1.0}}; - GDALDatasetH hSrcDS = GDALGetBandDataset(hBand); - if (hSrcDS != nullptr) - GDALGetGeoTransform(hSrcDS, adfGeoTransform.data()); + oOutExtent.xStart = std::max(nXStart, 0); + oOutExtent.yStart = std::max(nYStart, 0); + oOutExtent.xStop = std::min(nXStop, oOutExtent.xStop); + oOutExtent.yStop = std::min(nYStop, oOutExtent.yStop); + } - double adfInvGeoTransform[6]; - if (!GDALInvGeoTransform(adfGeoTransform.data(), adfInvGeoTransform)) + if (oOutExtent.xSize() == 0 || oOutExtent.ySize() == 0) { - CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform"); + CPLError(CE_Failure, CPLE_AppDefined, "Invalid target raster size"); return false; } + return true; +} - /* calculate observer position */ - double dfX, dfY; - GDALApplyGeoTransform(adfInvGeoTransform, oOpts.observer.x, - oOpts.observer.y, &dfX, &dfY); - int nX = static_cast(dfX); - int nY = static_cast(dfY); - - int nXSize = GDALGetRasterBandXSize(hBand); - int nYSize = GDALGetRasterBandYSize(hBand); +/// Read a line of raster data. +/// +/// @param nLine Line number to read. +/// @param data Pointer to location in which to store data. +/// @return Success or failure. +bool Viewshed::readLine(int nLine, double *data) +{ + std::lock_guard g(iMutex); - if (nX < 0 || nX > nXSize || nY < 0 || nY > nYSize) + if (GDALRasterIO(pSrcBand, GF_Read, oOutExtent.xStart, nLine, + oOutExtent.xSize(), 1, data, oOutExtent.xSize(), 1, + GDT_Float64, 0, 0)) { CPLError(CE_Failure, CPLE_AppDefined, - "The observer location falls outside of the DEM area"); + "RasterIO error when reading DEM at position (%d,%d), " + "size (%d,%d)", + oOutExtent.xStart, nLine, oOutExtent.xSize(), 1); return false; } + return true; +} - /* calculate the area of interest */ - constexpr double EPSILON = 1e-8; +/// Write an output line of either visibility or height data. +/// +/// @param nLine Line number being written. +/// @param vResult Result line to write. +/// @return True on success, false otherwise. +bool Viewshed::writeLine(int nLine, std::vector &vResult) +{ + // GDALRasterIO isn't thread-safe. + std::lock_guard g(oMutex); - int nXStart = 0; - int nYStart = 0; - int nXStop = nXSize; - int nYStop = nYSize; - if (oOpts.maxDistance > 0) + if (GDALRasterIO(pDstBand, GF_Write, 0, nLine - oOutExtent.yStart, + oOutExtent.xSize(), 1, vResult.data(), oOutExtent.xSize(), + 1, GDT_Float64, 0, 0)) { - nXStart = static_cast(std::floor( - nX - adfInvGeoTransform[1] * oOpts.maxDistance + EPSILON)); - nXStop = static_cast( - std::ceil(nX + adfInvGeoTransform[1] * oOpts.maxDistance - - EPSILON) + - 1); - nYStart = - static_cast(std::floor( - nY - std::fabs(adfInvGeoTransform[5]) * oOpts.maxDistance + - EPSILON)) - - (adfInvGeoTransform[5] > 0 ? 1 : 0); - nYStop = static_cast( - std::ceil(nY + - std::fabs(adfInvGeoTransform[5]) * oOpts.maxDistance - - EPSILON) + - (adfInvGeoTransform[5] < 0 ? 1 : 0)); + CPLError(CE_Failure, CPLE_AppDefined, + "RasterIO error when writing target raster at position " + "(%d,%d), size (%d,%d)", + 0, nLine - oOutExtent.yStart, oOutExtent.xSize(), 1); + return false; } - nXStart = std::max(nXStart, 0); - nYStart = std::max(nYStart, 0); - nXStop = std::min(nXStop, nXSize); - nYStop = std::min(nYStop, nYSize); - - /* normalize horizontal index (0 - nXSize) */ - nXSize = nXStop - nXStart; - nX -= nXStart; + return true; +} - nYSize = nYStop - nYStart; +/// Emit progress information saying that a line has been written to output. +/// +/// @return True on success, false otherwise. +bool Viewshed::lineProgress() +{ + if (nLineCount < oOutExtent.ySize()) + nLineCount++; + return emitProgress(nLineCount / static_cast(oOutExtent.ySize())); +} - if (nXSize == 0 || nYSize == 0) +/// Emit progress information saying that a fraction of work has been completed. +/// +/// @return True on success, false otherwise. +bool Viewshed::emitProgress(double fraction) +{ + // Call the progress function. + if (!oProgress(fraction, "")) { - CPLError(CE_Failure, CPLE_AppDefined, "Invalid target raster size"); + CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); return false; } + return true; +} - std::vector vFirstLineVal; - std::vector vLastLineVal; - std::vector vThisLineVal; - std::vector vResult; - std::vector vHeightResult; +/// Adjust the height of the line of data by the observer height and the curvature of the +/// earth. +/// +/// @param nYOffset Y offset of the line being adjusted. +/// @param nX X location of the observer. +/// @param pdfNx Pointer to the data at the nX location of the line being adjusted +/// @return [left, right) Leftmost and one past the rightmost cell in the line within +/// the max distance +std::pair Viewshed::adjustHeight(int nYOffset, int nX, + double *const pdfNx) +{ + int nLeft = 0; + int nRight = oOutExtent.xSize(); - try + // If there is a height adjustment factor other than zero or a max distance, + // calculate the adjusted height of the cell, stopping if we've exceeded the max + // distance. + if (static_cast(dfHeightAdjFactor) || dfMaxDistance2 > 0) { - vFirstLineVal.resize(nXSize); - vLastLineVal.resize(nXSize); - vThisLineVal.resize(nXSize); - vResult.resize(nXSize); + // Hoist invariants from the loops. + const double dfLineX = adfTransform[2] * nYOffset; + const double dfLineY = adfTransform[5] * nYOffset; - if (oOpts.outputMode != OutputMode::Normal) - vHeightResult.resize(nXSize); + double *pdfHeight = pdfNx; + for (int nXOffset = 0; nXOffset >= -nX; nXOffset--, pdfHeight--) + { + double dfX = adfTransform[1] * nXOffset + dfLineX; + double dfY = adfTransform[4] * nXOffset + dfLineY; + double dfR2 = dfX * dfX + dfY * dfY; + if (dfR2 > dfMaxDistance2) + { + nLeft = nXOffset + nX + 1; + break; + } + *pdfHeight -= dfHeightAdjFactor * dfR2 + dfZObserver; + } + + pdfHeight = pdfNx + 1; + for (int nXOffset = 1; nXOffset < oOutExtent.xSize() - nX; + nXOffset++, pdfHeight++) + { + double dfX = adfTransform[1] * nXOffset + dfLineX; + double dfY = adfTransform[4] * nXOffset + dfLineY; + double dfR2 = dfX * dfX + dfY * dfY; + if (dfR2 > dfMaxDistance2) + { + nRight = nXOffset + nX; + break; + } + *pdfHeight -= dfHeightAdjFactor * dfR2 + dfZObserver; + } } - catch (...) + else { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot allocate vectors for viewshed"); - return false; + double *pdfHeight = pdfNx - nX; + for (int i = 0; i < oOutExtent.xSize(); ++i) + { + *pdfHeight -= dfZObserver; + pdfHeight++; + } } + return {nLeft, nRight}; +} - double *padfFirstLineVal = vFirstLineVal.data(); - double *padfLastLineVal = vLastLineVal.data(); - double *padfThisLineVal = vThisLineVal.data(); - GByte *pabyResult = vResult.data(); - double *dfHeightResult = vHeightResult.data(); - +/// Create the output dataset. +/// +/// @return True on success, false otherwise. +bool Viewshed::createOutputDataset() +{ GDALDriverManager *hMgr = GetGDALDriverManager(); GDALDriver *hDriver = hMgr->GetDriverByName(oOpts.outputFormat.c_str()); if (!hDriver) @@ -417,7 +471,7 @@ bool Viewshed::run(GDALRasterBandH hBand, GDALProgressFunc pfnProgress, /* create output raster */ poDstDS.reset(hDriver->Create( - oOpts.outputFilename.c_str(), nXSize, nYSize, 1, + oOpts.outputFilename.c_str(), oOutExtent.xSize(), oOutExtent.ySize(), 1, oOpts.outputMode == OutputMode::Normal ? GDT_Byte : GDT_Float64, const_cast(oOpts.creationOpts.List()))); if (!poDstDS) @@ -426,24 +480,26 @@ bool Viewshed::run(GDALRasterBandH hBand, GDALProgressFunc pfnProgress, oOpts.outputFilename.c_str()); return false; } + /* copy srs */ + GDALDatasetH hSrcDS = GDALGetBandDataset(pSrcBand); if (hSrcDS) poDstDS->SetSpatialRef( GDALDataset::FromHandle(hSrcDS)->GetSpatialRef()); - std::array adfDstGeoTransform; - adfDstGeoTransform[0] = adfGeoTransform[0] + adfGeoTransform[1] * nXStart + - adfGeoTransform[2] * nYStart; - adfDstGeoTransform[1] = adfGeoTransform[1]; - adfDstGeoTransform[2] = adfGeoTransform[2]; - adfDstGeoTransform[3] = adfGeoTransform[3] + adfGeoTransform[4] * nXStart + - adfGeoTransform[5] * nYStart; - adfDstGeoTransform[4] = adfGeoTransform[4]; - adfDstGeoTransform[5] = adfGeoTransform[5]; - poDstDS->SetGeoTransform(adfDstGeoTransform.data()); - - auto hTargetBand = poDstDS->GetRasterBand(1); - if (hTargetBand == nullptr) + std::array adfDstTransform; + adfDstTransform[0] = adfTransform[0] + adfTransform[1] * oOutExtent.xStart + + adfTransform[2] * oOutExtent.yStart; + adfDstTransform[1] = adfTransform[1]; + adfDstTransform[2] = adfTransform[2]; + adfDstTransform[3] = adfTransform[3] + adfTransform[4] * oOutExtent.xStart + + adfTransform[5] * oOutExtent.yStart; + adfDstTransform[4] = adfTransform[4]; + adfDstTransform[5] = adfTransform[5]; + poDstDS->SetGeoTransform(adfDstTransform.data()); + + pDstBand = poDstDS->GetRasterBand(1); + if (!pDstBand) { CPLError(CE_Failure, CPLE_AppDefined, "Cannot get band for %s", oOpts.outputFilename.c_str()); @@ -451,474 +507,367 @@ bool Viewshed::run(GDALRasterBandH hBand, GDALProgressFunc pfnProgress, } if (oOpts.nodataVal >= 0) - GDALSetRasterNoDataValue(hTargetBand, oOpts.nodataVal); + GDALSetRasterNoDataValue(pDstBand, oOpts.nodataVal); + return true; +} - /* process first line */ - if (GDALRasterIO(hBand, GF_Read, nXStart, nY, nXSize, 1, padfFirstLineVal, - nXSize, 1, GDT_Float64, 0, 0)) - { - CPLError( - CE_Failure, CPLE_AppDefined, - "RasterIO error when reading DEM at position(%d, %d), size(%d, %d)", - nXStart, nY, nXSize, 1); - return false; - } +namespace +{ - const double dfZObserver = oOpts.observer.z + padfFirstLineVal[nX]; - const double dfDistance2 = oOpts.maxDistance * oOpts.maxDistance; +double doDiagonal(int nXOffset, [[maybe_unused]] int nYOffset, + double dfThisPrev, double dfLast, + [[maybe_unused]] double dfLastPrev) +{ + return CalcHeightDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast); +} - /* If we can't get a SemiMajor axis from the SRS, it will be - * SRS_WGS84_SEMIMAJOR - */ - double dfSphereDiameter(std::numeric_limits::infinity()); - const OGRSpatialReference *poDstSRS = poDstDS->GetSpatialRef(); - if (poDstSRS) - { - OGRErr eSRSerr; - double dfSemiMajor = poDstSRS->GetSemiMajor(&eSRSerr); +double doEdge(int nXOffset, int nYOffset, double dfThisPrev, double dfLast, + double dfLastPrev) +{ + if (nXOffset >= nYOffset) + return CalcHeightEdge(nYOffset, nXOffset, dfLastPrev, dfThisPrev); + else + return CalcHeightEdge(nXOffset, nYOffset, dfLastPrev, dfLast); +} - /* If we fetched the axis from the SRS, use it */ - if (eSRSerr != OGRERR_FAILURE) - dfSphereDiameter = dfSemiMajor * 2.0; - else - CPLDebug("GDALViewshedGenerate", - "Unable to fetch SemiMajor axis from spatial reference"); - } +double doMin(int nXOffset, int nYOffset, double dfThisPrev, double dfLast, + double dfLastPrev) +{ + double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev); + double dfDiagonal = + doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev); + return std::min(dfEdge, dfDiagonal); +} - /* mark the observer point as visible */ - double dfGroundLevel = 0; - if (oOpts.outputMode == OutputMode::DEM) - dfGroundLevel = padfFirstLineVal[nX]; +double doMax(int nXOffset, int nYOffset, double dfThisPrev, double dfLast, + double dfLastPrev) +{ + double dfEdge = doEdge(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev); + double dfDiagonal = + doDiagonal(nXOffset, nYOffset, dfThisPrev, dfLast, dfLastPrev); + return std::max(dfEdge, dfDiagonal); +} - pabyResult[nX] = oOpts.visibleVal; +double doLine(int nXOffset, [[maybe_unused]] int nYOffset, double dfThisPrev, + [[maybe_unused]] double dfLast, + [[maybe_unused]] double dfLastPrev) +{ + return CalcHeightLine(nXOffset, dfThisPrev); +} - //ABELL - Do we care about this conditional? - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[nX] = dfGroundLevel; +} // unnamed namespace - dfGroundLevel = 0; - if (nX > 0) - { - if (oOpts.outputMode == OutputMode::DEM) - dfGroundLevel = padfFirstLineVal[nX - 1]; - CPL_IGNORE_RET_VAL(AdjustHeightInRange( - adfGeoTransform.data(), 1, 0, padfFirstLineVal[nX - 1], dfDistance2, - oOpts.curveCoeff, dfSphereDiameter)); - pabyResult[nX - 1] = oOpts.visibleVal; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[nX - 1] = dfGroundLevel; - } - if (nX < nXSize - 1) +/// Process a line to the left of the observer. +/// +/// @param nX X coordinate of the observer. +/// @param nYOffset Offset of the line being processed from the observer +/// @param iStart X coordinate of the first cell to the left of the observer to be procssed. +/// @param iEnd X coordinate one past the last cell to be processed. +/// @param vResult Vector in which to store the visibility/height results. +/// @param vThisLineVal Height of each cell in the line being processed. +/// @param vLastLineVal Observable height of each cell in the previous line processed. +void Viewshed::processLineLeft(int nX, int nYOffset, int iStart, int iEnd, + std::vector &vResult, + std::vector &vThisLineVal, + std::vector &vLastLineVal) +{ + double *pThis = vThisLineVal.data() + iStart; + double *pLast = vLastLineVal.data() + iStart; + + nYOffset = std::abs(nYOffset); + + // Go from the observer to the left, calculating Z as we go. + for (int iPixel = iStart; iPixel > iEnd; iPixel--, pThis--, pLast--) { - if (oOpts.outputMode == OutputMode::DEM) - dfGroundLevel = padfFirstLineVal[nX + 1]; - CPL_IGNORE_RET_VAL(AdjustHeightInRange( - adfGeoTransform.data(), 1, 0, padfFirstLineVal[nX + 1], dfDistance2, - oOpts.curveCoeff, dfSphereDiameter)); - pabyResult[nX + 1] = oOpts.visibleVal; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[nX + 1] = dfGroundLevel; + int nXOffset = std::abs(iPixel - nX); + double dfZ = + oZcalc(nXOffset, nYOffset, *(pThis + 1), *pLast, *(pLast + 1)); + setOutput(vResult[iPixel], *pThis, dfZ); } - /* process left direction */ - for (int iPixel = nX - 2; iPixel >= 0; iPixel--) - { - dfGroundLevel = 0; - if (oOpts.outputMode == OutputMode::DEM) - dfGroundLevel = padfFirstLineVal[iPixel]; + // For cells outside of the [start, end) range, set the outOfRange value. + std::fill(vResult.begin(), vResult.begin() + iEnd + 1, oOpts.outOfRangeVal); +} - if (AdjustHeightInRange(adfGeoTransform.data(), nX - iPixel, 0, - padfFirstLineVal[iPixel], dfDistance2, - oOpts.curveCoeff, dfSphereDiameter)) - { - double dfZ = CalcHeightLine( - nX - iPixel, padfFirstLineVal[iPixel + 1], dfZObserver); +/// Process a line to the right of the observer. +/// +/// @param nX X coordinate of the observer. +/// @param nYOffset Offset of the line being processed from the observer +/// @param iStart X coordinate of the first cell to the right of the observer to be procssed. +/// @param iEnd X coordinate one past the last cell to be processed. +/// @param vResult Vector in which to store the visibility/height results. +/// @param vThisLineVal Height of each cell in the line being processed. +/// @param vLastLineVal Observable height of each cell in the previous line processed. +void Viewshed::processLineRight(int nX, int nYOffset, int iStart, int iEnd, + std::vector &vResult, + std::vector &vThisLineVal, + std::vector &vLastLineVal) +{ + double *pThis = vThisLineVal.data() + iStart; + double *pLast = vLastLineVal.data() + iStart; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = std::max( - 0.0, (dfZ - padfFirstLineVal[iPixel] + dfGroundLevel)); + nYOffset = std::abs(nYOffset); - setVisibility(iPixel, dfZ, padfFirstLineVal, vResult); - } - else - { - for (; iPixel >= 0; iPixel--) - { - pabyResult[iPixel] = oOpts.outOfRangeVal; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = oOpts.outOfRangeVal; - } - } + // Go from the observer to the right, calculating Z as we go. + for (int iPixel = iStart; iPixel < iEnd; iPixel++, pThis++, pLast++) + { + int nXOffset = std::abs(iPixel - nX); + double dfZ = + oZcalc(nXOffset, nYOffset, *(pThis - 1), *pLast, *(pLast - 1)); + setOutput(vResult[iPixel], *pThis, dfZ); } - /* process right direction */ - for (int iPixel = nX + 2; iPixel < nXSize; iPixel++) + // For cells outside of the [start, end) range, set the outOfRange value. + std::fill(vResult.begin() + iEnd, vResult.end(), oOpts.outOfRangeVal); +} + +/// Set the output Z value depending o the observable height and computation mode. +/// +/// dfResult Reference to the result cell +/// dfCellVal Reference to the current cell height. Replace with observable height. +/// dfZ Observable height at cell. +void Viewshed::setOutput(double &dfResult, double &dfCellVal, double dfZ) +{ + if (oOpts.outputMode != OutputMode::Normal) { - dfGroundLevel = 0; - if (oOpts.outputMode == OutputMode::DEM) - dfGroundLevel = padfFirstLineVal[iPixel]; - if (AdjustHeightInRange(adfGeoTransform.data(), iPixel - nX, 0, - padfFirstLineVal[iPixel], dfDistance2, - oOpts.curveCoeff, dfSphereDiameter)) - { - double dfZ = CalcHeightLine( - iPixel - nX, padfFirstLineVal[iPixel - 1], dfZObserver); + dfResult += (dfZ - dfCellVal); + dfResult = std::max(0.0, dfResult); + } + else + dfResult = (dfCellVal + oOpts.targetHeight < dfZ) ? oOpts.invisibleVal + : oOpts.visibleVal; + dfCellVal = std::max(dfCellVal, dfZ); +} - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = std::max( - 0.0, (dfZ - padfFirstLineVal[iPixel] + dfGroundLevel)); +/// Process the first line (the one with the X coordinate the same as the observer). +/// +/// @param nX X location of the observer +/// @param nY Y location of the observer +/// @param nLine Line number being processed (should always be the same as nY) +/// @param vLastLineVal Vector in which to store the read line. Becomes the last line +/// in further processing. +/// @return True on success, false otherwise. +bool Viewshed::processFirstLine(int nX, int nY, int nLine, + std::vector &vLastLineVal) +{ + int nYOffset = nLine - nY; // Should be zero. + std::vector vResult(oOutExtent.xSize()); + std::vector vThisLineVal(oOutExtent.xSize()); - setVisibility(iPixel, dfZ, padfFirstLineVal, vResult); - } - else - { - for (; iPixel < nXSize; iPixel++) - { - pabyResult[iPixel] = oOpts.outOfRangeVal; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = oOpts.outOfRangeVal; - } - } - } - /* write result line */ + if (!readLine(nLine, vThisLineVal.data())) + return false; - void *data; - GDALDataType dataType; + // This bit is only relevant for the first line. + dfZObserver = oOpts.observer.z + vThisLineVal[nX]; + dfHeightAdjFactor = CalcHeightAdjFactor(poDstDS.get(), oOpts.curveCoeff); if (oOpts.outputMode == OutputMode::Normal) { - data = static_cast(pabyResult); - dataType = GDT_Byte; + vResult[nX] = oOpts.visibleVal; + if (nX - 1 >= 0) + vResult[nX - 1] = oOpts.visibleVal; + if (nX + 1 < oOutExtent.xSize()) + vResult[nX + 1] = oOpts.visibleVal; } - else + + // In DEM mode the base is the pre-adjustment value. + // In ground mode the base is zero. + if (oOpts.outputMode == OutputMode::DEM) + vResult = vThisLineVal; + + const auto [iLeft, iRight] = + adjustHeight(nYOffset, nX, vThisLineVal.data() + nX); + + auto t1 = + std::async(std::launch::async, + [&, left = iLeft]() + { + processLineLeft(nX, nYOffset, nX - 2, left - 1, vResult, + vThisLineVal, vLastLineVal); + }); + + auto t2 = + std::async(std::launch::async, + [&, right = iRight]() + { + processLineRight(nX, nYOffset, nX + 2, right, vResult, + vThisLineVal, vLastLineVal); + }); + t1.wait(); + t2.wait(); + + // Make the current line the last line. + vLastLineVal = std::move(vThisLineVal); + + // Create the output writer. + if (!writeLine(nY, vResult)) + return false; + + if (!lineProgress()) + return false; + return true; +} + +/// Process a line above or below the observer. +/// +/// @param nX X location of the observer +/// @param nY Y location of the observer +/// @param nLine Line number being processed. +/// @param vLastLineVal Vector in which to store the read line. Becomes the last line +/// in further processing. +/// @return True on success, false otherwise. +bool Viewshed::processLine(int nX, int nY, int nLine, + std::vector &vLastLineVal) +{ + int nYOffset = nLine - nY; + std::vector vResult(oOutExtent.xSize()); + std::vector vThisLineVal(oOutExtent.xSize()); + + if (!readLine(nLine, vThisLineVal.data())) + return false; + + // In DEM mode the base is the input DEM value. + // In ground mode the base is zero. + if (oOpts.outputMode == OutputMode::DEM) + vResult = vThisLineVal; + + // Adjust height of the read line. + const auto [iLeft, iRight] = + adjustHeight(nYOffset, nX, vThisLineVal.data() + nX); + + // Handle the initial position on the line. + if (iLeft < iRight) { - data = static_cast(dfHeightResult); - dataType = GDT_Float64; + double dfZ = CalcHeightLine(nYOffset, vLastLineVal[nX]); + setOutput(vResult[nX], vThisLineVal[nX], dfZ); } - if (GDALRasterIO(hTargetBand, GF_Write, 0, nY - nYStart, nXSize, 1, data, - nXSize, 1, dataType, 0, 0)) - { - CPLError(CE_Failure, CPLE_AppDefined, - "RasterIO error when writing target raster at position " - "(%d,%d), size (%d,%d)", - 0, nY - nYStart, nXSize, 1); + else + vResult[nX] = oOpts.outOfRangeVal; + + // process left half then right half of line + auto t1 = + std::async(std::launch::async, + [&, left = iLeft]() + { + processLineLeft(nX, nYOffset, nX - 1, left - 1, vResult, + vThisLineVal, vLastLineVal); + }); + + auto t2 = + std::async(std::launch::async, + [&, right = iRight]() + { + processLineRight(nX, nYOffset, nX + 1, right, vResult, + vThisLineVal, vLastLineVal); + }); + t1.wait(); + t2.wait(); + + // Make the current line the last line. + vLastLineVal = std::move(vThisLineVal); + + if (!writeLine(nLine, vResult)) return false; - } - /* scan upwards */ - std::copy(vFirstLineVal.begin(), vFirstLineVal.end(), vLastLineVal.begin()); - for (int iLine = nY - 1; iLine >= nYStart; iLine--) - { - if (GDALRasterIO(hBand, GF_Read, nXStart, iLine, nXSize, 1, - padfThisLineVal, nXSize, 1, GDT_Float64, 0, 0)) - { - CPLError(CE_Failure, CPLE_AppDefined, - "RasterIO error when reading DEM at position (%d,%d), " - "size (%d,%d)", - nXStart, iLine, nXSize, 1); - return false; - } + if (!lineProgress()) + return false; + return true; +} - /* set up initial point on the scanline */ - dfGroundLevel = 0; - if (oOpts.outputMode == OutputMode::DEM) - dfGroundLevel = padfThisLineVal[nX]; - if (AdjustHeightInRange(adfGeoTransform.data(), 0, nY - iLine, - padfThisLineVal[nX], dfDistance2, - oOpts.curveCoeff, dfSphereDiameter)) - { - double dfZ = - CalcHeightLine(nY - iLine, padfLastLineVal[nX], dfZObserver); +/// Compute the viewshed of a raster band. +/// +/// @param band Pointer to the raster band to be processed. +/// @param pfnProgress Pointer to the progress function. Can be null. +/// @param pProgressArg Argument passed to the progress function +/// @return True on success, false otherwise. +bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress, + void *pProgressArg) +{ + using namespace std::placeholders; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[nX] = - std::max(0.0, (dfZ - padfThisLineVal[nX] + dfGroundLevel)); + nLineCount = 0; + pSrcBand = static_cast(band); - setVisibility(nX, dfZ, padfThisLineVal, vResult); - } - else - { - pabyResult[nX] = oOpts.outOfRangeVal; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[nX] = oOpts.outOfRangeVal; - } - - /* process left direction */ - for (int iPixel = nX - 1; iPixel >= 0; iPixel--) - { - dfGroundLevel = 0; - if (oOpts.outputMode == OutputMode::DEM) - dfGroundLevel = padfThisLineVal[iPixel]; - if (AdjustHeightInRange(adfGeoTransform.data(), nX - iPixel, - nY - iLine, padfThisLineVal[iPixel], - dfDistance2, oOpts.curveCoeff, - dfSphereDiameter)) - { - double dfDiagZ = 0; - double dfEdgeZ = 0; - if (oOpts.cellMode != CellMode::Edge) - dfDiagZ = CalcHeightDiagonal( - nX - iPixel, nY - iLine, padfThisLineVal[iPixel + 1], - padfLastLineVal[iPixel], dfZObserver); - - if (oOpts.cellMode != CellMode::Diagonal) - dfEdgeZ = nX - iPixel >= nY - iLine - ? CalcHeightEdge(nY - iLine, nX - iPixel, - padfLastLineVal[iPixel + 1], - padfThisLineVal[iPixel + 1], - dfZObserver) - : CalcHeightEdge(nX - iPixel, nY - iLine, - padfLastLineVal[iPixel + 1], - padfLastLineVal[iPixel], - dfZObserver); - - double dfZ = calcHeight(dfDiagZ, dfEdgeZ); - - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = std::max( - 0.0, (dfZ - padfThisLineVal[iPixel] + dfGroundLevel)); - - setVisibility(iPixel, dfZ, padfThisLineVal, vResult); - } - else - { - for (; iPixel >= 0; iPixel--) - { - pabyResult[iPixel] = oOpts.outOfRangeVal; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = oOpts.outOfRangeVal; - } - } - } - /* process right direction */ - for (int iPixel = nX + 1; iPixel < nXSize; iPixel++) - { - dfGroundLevel = 0; - if (oOpts.outputMode == OutputMode::DEM) - dfGroundLevel = padfThisLineVal[iPixel]; - - if (AdjustHeightInRange(adfGeoTransform.data(), iPixel - nX, - nY - iLine, padfThisLineVal[iPixel], - dfDistance2, oOpts.curveCoeff, - dfSphereDiameter)) - { - double dfDiagZ = 0; - double dfEdgeZ = 0; - if (oOpts.cellMode != CellMode::Edge) - dfDiagZ = CalcHeightDiagonal( - iPixel - nX, nY - iLine, padfThisLineVal[iPixel - 1], - padfLastLineVal[iPixel], dfZObserver); - if (oOpts.cellMode != CellMode::Diagonal) - dfEdgeZ = iPixel - nX >= nY - iLine - ? CalcHeightEdge(nY - iLine, iPixel - nX, - padfLastLineVal[iPixel - 1], - padfThisLineVal[iPixel - 1], - dfZObserver) - : CalcHeightEdge(iPixel - nX, nY - iLine, - padfLastLineVal[iPixel - 1], - padfLastLineVal[iPixel], - dfZObserver); - - double dfZ = calcHeight(dfDiagZ, dfEdgeZ); - - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = std::max( - 0.0, (dfZ - padfThisLineVal[iPixel] + dfGroundLevel)); - - setVisibility(iPixel, dfZ, padfThisLineVal, vResult); - } - else - { - for (; iPixel < nXSize; iPixel++) - { - pabyResult[iPixel] = oOpts.outOfRangeVal; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = oOpts.outOfRangeVal; - } - } - } + if (!pfnProgress) + pfnProgress = GDALDummyProgress; + oProgress = std::bind(pfnProgress, _1, _2, pProgressArg); - /* write result line */ - if (GDALRasterIO(hTargetBand, GF_Write, 0, iLine - nYStart, nXSize, 1, - data, nXSize, 1, dataType, 0, 0)) - { - CPLError(CE_Failure, CPLE_AppDefined, - "RasterIO error when writing target raster at position " - "(%d,%d), size (%d,%d)", - 0, iLine - nYStart, nXSize, 1); - return false; - } + if (!emitProgress(0)) + return false; - std::swap(padfLastLineVal, padfThisLineVal); + // set up geotransformation + GDALDatasetH hSrcDS = GDALGetBandDataset(pSrcBand); + if (hSrcDS != nullptr) + GDALGetGeoTransform(hSrcDS, adfTransform.data()); - if (!pfnProgress((nY - iLine) / static_cast(nYSize), "", - pProgressArg)) - { - CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); - return false; - } + if (!GDALInvGeoTransform(adfTransform.data(), adfInvTransform.data())) + { + CPLError(CE_Failure, CPLE_AppDefined, "Cannot invert geotransform"); + return false; } - /* scan downwards */ - memcpy(padfLastLineVal, padfFirstLineVal, nXSize * sizeof(double)); - for (int iLine = nY + 1; iLine < nYStop; iLine++) - { - if (GDALRasterIO(hBand, GF_Read, nXStart, iLine, nXSize, 1, - padfThisLineVal, nXSize, 1, GDT_Float64, 0, 0)) - { - CPLError(CE_Failure, CPLE_AppDefined, - "RasterIO error when reading DEM at position (%d,%d), " - "size (%d,%d)", - nXStart, iLine, nXStop - nXStart, 1); - return false; - } + // calculate observer position + double dfX, dfY; + GDALApplyGeoTransform(adfInvTransform.data(), oOpts.observer.x, + oOpts.observer.y, &dfX, &dfY); + int nX = static_cast(dfX); + int nY = static_cast(dfY); - /* set up initial point on the scanline */ - dfGroundLevel = 0; - if (oOpts.outputMode == OutputMode::DEM) - dfGroundLevel = padfThisLineVal[nX]; + // calculate the area of interest + if (!calcOutputExtent(nX, nY)) + return false; - if (AdjustHeightInRange(adfGeoTransform.data(), 0, iLine - nY, - padfThisLineVal[nX], dfDistance2, - oOpts.curveCoeff, dfSphereDiameter)) - { - double dfZ = - CalcHeightLine(iLine - nY, padfLastLineVal[nX], dfZObserver); + // normalize horizontal index to [ 0, oOutExtent.xSize() ) + nX -= oOutExtent.xStart; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[nX] = - std::max(0.0, (dfZ - padfThisLineVal[nX] + dfGroundLevel)); + // create the output dataset + if (!createOutputDataset()) + return false; - setVisibility(nX, dfZ, padfThisLineVal, vResult); - } - else - { - pabyResult[nX] = oOpts.outOfRangeVal; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[nX] = oOpts.outOfRangeVal; - } + oZcalc = doLine; - /* process left direction */ - for (int iPixel = nX - 1; iPixel >= 0; iPixel--) - { - dfGroundLevel = 0; - if (oOpts.outputMode == OutputMode::DEM) - dfGroundLevel = padfThisLineVal[iPixel]; - - if (AdjustHeightInRange(adfGeoTransform.data(), nX - iPixel, - iLine - nY, padfThisLineVal[iPixel], - dfDistance2, oOpts.curveCoeff, - dfSphereDiameter)) - { - double dfDiagZ = 0; - double dfEdgeZ = 0; - if (oOpts.cellMode != CellMode::Edge) - dfDiagZ = CalcHeightDiagonal( - nX - iPixel, iLine - nY, padfThisLineVal[iPixel + 1], - padfLastLineVal[iPixel], dfZObserver); - - if (oOpts.cellMode != CellMode::Diagonal) - dfEdgeZ = nX - iPixel >= iLine - nY - ? CalcHeightEdge(iLine - nY, nX - iPixel, - padfLastLineVal[iPixel + 1], - padfThisLineVal[iPixel + 1], - dfZObserver) - : CalcHeightEdge(nX - iPixel, iLine - nY, - padfLastLineVal[iPixel + 1], - padfLastLineVal[iPixel], - dfZObserver); - - double dfZ = calcHeight(dfDiagZ, dfEdgeZ); - - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = std::max( - 0.0, (dfZ - padfThisLineVal[iPixel] + dfGroundLevel)); - - setVisibility(iPixel, dfZ, padfThisLineVal, vResult); - } - else - { - for (; iPixel >= 0; iPixel--) - { - pabyResult[iPixel] = oOpts.outOfRangeVal; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = oOpts.outOfRangeVal; - } - } - } - /* process right direction */ - for (int iPixel = nX + 1; iPixel < nXSize; iPixel++) - { - dfGroundLevel = 0; - if (oOpts.outputMode == OutputMode::DEM) - dfGroundLevel = padfThisLineVal[iPixel]; - - if (AdjustHeightInRange(adfGeoTransform.data(), iPixel - nX, - iLine - nY, padfThisLineVal[iPixel], - dfDistance2, oOpts.curveCoeff, - dfSphereDiameter)) - { - double dfDiagZ = 0; - double dfEdgeZ = 0; - if (oOpts.cellMode != CellMode::Edge) - dfDiagZ = CalcHeightDiagonal( - iPixel - nX, iLine - nY, padfThisLineVal[iPixel - 1], - padfLastLineVal[iPixel], dfZObserver); - - if (oOpts.cellMode != CellMode::Diagonal) - dfEdgeZ = iPixel - nX >= iLine - nY - ? CalcHeightEdge(iLine - nY, iPixel - nX, - padfLastLineVal[iPixel - 1], - padfThisLineVal[iPixel - 1], - dfZObserver) - : CalcHeightEdge(iPixel - nX, iLine - nY, - padfLastLineVal[iPixel - 1], - padfLastLineVal[iPixel], - dfZObserver); - - double dfZ = calcHeight(dfDiagZ, dfEdgeZ); - - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = std::max( - 0.0, (dfZ - padfThisLineVal[iPixel] + dfGroundLevel)); - - setVisibility(iPixel, dfZ, padfThisLineVal, vResult); - } - else - { - for (; iPixel < nXSize; iPixel++) - { - pabyResult[iPixel] = oOpts.outOfRangeVal; - if (oOpts.outputMode != OutputMode::Normal) - dfHeightResult[iPixel] = oOpts.outOfRangeVal; - } - } - } + std::vector vFirstLineVal(oOutExtent.xSize()); + + if (!processFirstLine(nX, nY, nY, vFirstLineVal)) + return false; - /* write result line */ - if (GDALRasterIO(hTargetBand, GF_Write, 0, iLine - nYStart, nXSize, 1, - data, nXSize, 1, dataType, 0, 0)) + if (oOpts.cellMode == CellMode::Edge) + oZcalc = doEdge; + else if (oOpts.cellMode == CellMode::Diagonal) + oZcalc = doDiagonal; + else if (oOpts.cellMode == CellMode::Min) + oZcalc = doMin; + else if (oOpts.cellMode == CellMode::Max) + oZcalc = doMax; + + // scan upwards + std::atomic err(false); + auto tUp = std::async(std::launch::async, + [&]() + { + std::vector vLastLineVal = vFirstLineVal; + + for (int nLine = nY - 1; + nLine >= oOutExtent.yStart && !err; nLine--) + if (!processLine(nX, nY, nLine, vLastLineVal)) + err = true; + }); + + // scan downwards + auto tDown = std::async( + std::launch::async, + [&]() { - CPLError(CE_Failure, CPLE_AppDefined, - "RasterIO error when writing target raster at position " - "(%d,%d), size (%d,%d)", - 0, iLine - nYStart, nXSize, 1); - return false; - } + std::vector vLastLineVal = vFirstLineVal; - std::swap(padfLastLineVal, padfThisLineVal); + for (int nLine = nY + 1; nLine < oOutExtent.yStop && !err; nLine++) + if (!processLine(nX, nY, nLine, vLastLineVal)) + err = true; + }); - if (!pfnProgress((iLine - nYStart) / static_cast(nYSize), "", - pProgressArg)) - { - CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); - return false; - } - } + tUp.wait(); + tDown.wait(); - if (!pfnProgress(1.0, "", pProgressArg)) - { - CPLError(CE_Failure, CPLE_UserInterrupt, "User terminated"); + if (!emitProgress(1)) return false; - } return true; } diff --git a/alg/viewshed.h b/alg/viewshed.h index 1dc1120806e5..857ec6225096 100644 --- a/alg/viewshed.h +++ b/alg/viewshed.h @@ -25,8 +25,12 @@ * DEALINGS IN THE SOFTWARE. ****************************************************************************/ +#include #include +#include +#include #include +#include #include #include "cpl_progress.h" @@ -72,16 +76,39 @@ class Viewshed double z; //!< Z value }; + /** + * A window in a raster including pixels in [xStart, xEnd) and [yStart, yEnd). + */ + struct Window + { + int xStart{}; //!< X start position + int xStop{}; //!< X end position + int yStart{}; //!< Y start position + int yStop{}; //!< Y end position + + /// \brief Window size in the X direction. + int xSize() + { + return xStop - xStart; + } + + /// \brief Window size in the Y direction. + int ySize() + { + return yStop - yStart; + } + }; + /** * Options for viewshed generation. */ struct Options { Point observer{0, 0, 0}; //!< x, y, and z of the observer - uint8_t visibleVal{255}; //!< raster output value for visible pixels. - uint8_t invisibleVal{ + double visibleVal{255}; //!< raster output value for visible pixels. + double invisibleVal{ 0}; //!< raster output value for non-visible pixels. - uint8_t outOfRangeVal{ + double outOfRangeVal{ 0}; //!< raster output value for pixels outside of max distance. double nodataVal{-1}; //!< raster output value for pixels with no data double targetHeight{0.0}; //!< target height above the DEM surface @@ -102,17 +129,20 @@ class Viewshed * * @param opts Options to use when calculating viewshed. */ - CPL_DLL explicit Viewshed(const Options &opts) : oOpts{opts}, poDstDS{} + CPL_DLL explicit Viewshed(const Options &opts) + : oOpts{opts}, oOutExtent{}, dfMaxDistance2{opts.maxDistance * + opts.maxDistance}, + dfZObserver{0}, poDstDS{}, pSrcBand{}, pDstBand{}, + dfHeightAdjFactor{0}, nLineCount{0}, adfTransform{0, 1, 0, 0, 0, 1}, + adfInvTransform{}, oProgress{}, oZcalc{}, oMutex{}, iMutex{} { + if (dfMaxDistance2 == 0) + dfMaxDistance2 = std::numeric_limits::max(); } - /** - * Create the viewshed for the provided raster band. - * - * @param hBand Handle to the raster band. - * @param pfnProgress Progress reporting callback function. - * @param pProgressArg Argument to pass to the progress callback. - */ + Viewshed(const Viewshed &) = delete; + Viewshed &operator=(const Viewshed &) = delete; + CPL_DLL bool run(GDALRasterBandH hBand, GDALProgressFunc pfnProgress, void *pProgressArg = nullptr); @@ -128,11 +158,44 @@ class Viewshed private: Options oOpts; + Window oOutExtent; + double dfMaxDistance2; + double dfZObserver; std::unique_ptr poDstDS; + GDALRasterBand *pSrcBand; + GDALRasterBand *pDstBand; + double dfHeightAdjFactor; + int nLineCount; + std::array adfTransform; + std::array adfInvTransform; + using ProgressFunc = std::function; + ProgressFunc oProgress; + using ZCalc = std::function; + ZCalc oZcalc; + std::mutex oMutex; + std::mutex iMutex; - void setVisibility(int iPixel, double dfZ, double *padfZVal, - std::vector &vResult); + void setOutput(double &dfResult, double &dfCellVal, double dfZ); double calcHeight(double dfZ, double dfZ2); + bool readLine(int nLine, double *data); + bool writeLine(int nLine, std::vector &vResult); + bool processLine(int nX, int nY, int nLine, + std::vector &vLastLineVal); + bool processFirstLine(int nX, int nY, int nLine, + std::vector &vLastLineVal); + void processLineLeft(int nX, int nYOffset, int iStart, int iEnd, + std::vector &vResult, + std::vector &vThisLineVal, + std::vector &vLastLineVal); + void processLineRight(int nX, int nYOffset, int iStart, int iEnd, + std::vector &vResult, + std::vector &vThisLineVal, + std::vector &vLastLineVal); + std::pair adjustHeight(int iLine, int nX, double *const pdfNx); + bool calcOutputExtent(int nX, int nY); + bool createOutputDataset(); + bool lineProgress(); + bool emitProgress(double fraction); }; } // namespace gdal diff --git a/autotest/utilities/test_gdal_viewshed.py b/autotest/utilities/test_gdal_viewshed.py index 5773a0b0279e..eda228c25c5a 100755 --- a/autotest/utilities/test_gdal_viewshed.py +++ b/autotest/utilities/test_gdal_viewshed.py @@ -68,7 +68,7 @@ def viewshed_input(tmp_path): gdaltest.runexternal( test_cli_utilities.get_gdalwarp_path() + " -t_srs EPSG:32617 -overwrite ../gdrivers/data/n43.tif " - + fname + + fname, ) return fname @@ -215,6 +215,98 @@ def test_gdal_viewshed_all_options(gdal_viewshed_path, tmp_path, viewshed_input) ############################################################################### +def test_gdal_viewshed_value_options(gdal_viewshed_path, tmp_path, viewshed_input): + + viewshed_out = str(tmp_path / "test_gdal_viewshed_out.tif") + + _, err = gdaltest.runexternal_out_and_err( + gdal_viewshed_path + + " -om NORMAL -f GTiff -oz {} -ox {} -oy {} -b 1 -a_nodata 0 -iv 127 -vv 254 -ov 0 {} {}".format( + oz[1], ox[0], oy[0], viewshed_input, viewshed_out + ) + ) + assert err is None or err == "" + ds = gdal.Open(viewshed_out) + assert ds + cs = ds.GetRasterBand(1).Checksum() + nodata = ds.GetRasterBand(1).GetNoDataValue() + ds = None + assert cs == 35091 + assert nodata == 0 + + +############################################################################### + + +def test_gdal_viewshed_tz_option(gdal_viewshed_path, tmp_path, viewshed_input): + + viewshed_out = str(tmp_path / "test_gdal_viewshed_out.tif") + + _, err = gdaltest.runexternal_out_and_err( + gdal_viewshed_path + + " -om NORMAL -f GTiff -oz {} -ox {} -oy {} -b 1 -a_nodata 0 -tz 5 {} {}".format( + oz[1], ox[0], oy[0], viewshed_input, viewshed_out + ) + ) + assert err is None or err == "" + ds = gdal.Open(viewshed_out) + assert ds + cs = ds.GetRasterBand(1).Checksum() + nodata = ds.GetRasterBand(1).GetNoDataValue() + ds = None + assert cs == 33725 + assert nodata == 0 + + +############################################################################### + + +def test_gdal_viewshed_cc_option(gdal_viewshed_path, tmp_path, viewshed_input): + + viewshed_out = str(tmp_path / "test_gdal_viewshed_out.tif") + + _, err = gdaltest.runexternal_out_and_err( + gdal_viewshed_path + + " -om NORMAL -f GTiff -oz {} -ox {} -oy {} -b 1 -a_nodata 0 -cc 0 {} {}".format( + oz[1], ox[0], oy[0], viewshed_input, viewshed_out + ) + ) + assert err is None or err == "" + ds = gdal.Open(viewshed_out) + assert ds + cs = ds.GetRasterBand(1).Checksum() + nodata = ds.GetRasterBand(1).GetNoDataValue() + ds = None + assert cs == 17241 + assert nodata == 0 + + +############################################################################### + + +def test_gdal_viewshed_md_option(gdal_viewshed_path, tmp_path, viewshed_input): + + viewshed_out = str(tmp_path / "test_gdal_viewshed_out.tif") + + _, err = gdaltest.runexternal_out_and_err( + gdal_viewshed_path + + " -om NORMAL -f GTiff -oz {} -ox {} -oy {} -b 1 -a_nodata 0 -tz 5 -md 20000 {} {}".format( + oz[1], ox[0], oy[0], viewshed_input, viewshed_out + ) + ) + assert err is None or err == "" + ds = gdal.Open(viewshed_out) + assert ds + cs = ds.GetRasterBand(1).Checksum() + nodata = ds.GetRasterBand(1).GetNoDataValue() + ds = None + assert cs == 22617 + assert nodata == 0 + + +############################################################################### + + def test_gdal_viewshed_missing_source(gdal_viewshed_path): _, err = gdaltest.runexternal_out_and_err(gdal_viewshed_path + " -ox 0 -oy 0") From 901ca4dbe15d9440f59c56b58f085c2dac6fa81a Mon Sep 17 00:00:00 2001 From: coim32 Date: Mon, 3 Jun 2024 12:56:13 -0600 Subject: [PATCH 0049/1119] Fix OPTIMALPADDING documentation --- doc/source/drivers/raster/georaster.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/drivers/raster/georaster.rst b/doc/source/drivers/raster/georaster.rst index a1c35d5efd0b..b221c6d2d5ba 100644 --- a/doc/source/drivers/raster/georaster.rst +++ b/doc/source/drivers/raster/georaster.rst @@ -127,7 +127,7 @@ The following creation options are supported: - .. co:: BLOCKING Decline the use of blocking (NO) or request an - automatic blocking size (OPTIMUM). + automatic blocking size (OPTIMALPADDING). - .. co:: SRID From 7758a2a1097304fd331b1e41a6cea82011c24211 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 00:01:05 +0200 Subject: [PATCH 0050/1119] typo fixes [ci skip] --- alg/viewshed.cpp | 2 +- autotest/ogr/ogr_esrijson.py | 2 +- scripts/typos_allowlist.txt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/alg/viewshed.cpp b/alg/viewshed.cpp index 22f322bb1a3b..1c95701278f3 100644 --- a/alg/viewshed.cpp +++ b/alg/viewshed.cpp @@ -247,7 +247,7 @@ double CalcHeightLine(int nDistance, double Za) return Za * nDistance / (nDistance - 1); } -// Calulate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0) +// Calculate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0) // and passing through the line connecting (i - 1, j, Za) and (i, j - 1, Zb). // In other words, the origin and the two points form a plane and we're calculating Zc // of the point (i, j, Zc), also on the plane. diff --git a/autotest/ogr/ogr_esrijson.py b/autotest/ogr/ogr_esrijson.py index 1924acac2438..3b93528c5141 100755 --- a/autotest/ogr/ogr_esrijson.py +++ b/autotest/ogr/ogr_esrijson.py @@ -693,7 +693,7 @@ def test_ogr_esrijson_identify_srs(): # Test for https://github.com/OSGeo/gdal/issues/9996 -def test_ogr_esrijson_read_CadastralSpecialServies(): +def test_ogr_esrijson_read_CadastralSpecialServices(): ds = ogr.Open("data/esrijson/GetLatLon.json") lyr = ds.GetLayer(0) diff --git a/scripts/typos_allowlist.txt b/scripts/typos_allowlist.txt index 212047039ab2..8708492d5792 100644 --- a/scripts/typos_allowlist.txt +++ b/scripts/typos_allowlist.txt @@ -342,3 +342,4 @@ either 2 or 4 comma separated values. The same rules apply for the source and de "Invalid value for MITRE_LIMIT: %s", pszValue); *
  • JOIN_STYLE=ROUND/MITRE/BEVEL
  • *
  • MITRE_LIMIT=double
  • + DESCRIPTION=Staatliche Verwaltungs- und Sozialdienste wie öffentliche Verwaltung, Katastrophenschutz, Schulen und Krankenhäuser, die von öffentlichen oder privaten Einrichtungen erbracht werden, soweit sie in den Anwendungsbereich der Richtlinie 2007/2/EG fallen. Dieser Datensatz enthält Informationen zu Feuerwehrleitstellen. From 33681b215a3ae14e826c40bac34d03c897db81a1 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 01:23:35 +0200 Subject: [PATCH 0051/1119] OpenFileGDB: fix CodeQL cpp/integer-multiplication-cast-to-long warning Fixe https://github.com/OSGeo/gdal/security/code-scanning/569 --- ogr/ogrsf_frmts/openfilegdb/filegdbindex.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbindex.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbindex.cpp index 6f7fec8e6c48..d6fee21adf7c 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbindex.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbindex.cpp @@ -1324,8 +1324,8 @@ bool FileGDBIndexIterator::FindPages(int iLevel, uint64_t nPage) returnErrorIf(nSubPagesCount[iLevel] == 0 || nSubPagesCount[iLevel] > nMaxPerPages); if (nIndexDepth == 2) - returnErrorIf(m_nValueCountInIdx > - nMaxPerPages * (nSubPagesCount[0] + 1)); + returnErrorIf(m_nValueCountInIdx > static_cast(nMaxPerPages) * + (nSubPagesCount[0] + 1)); if (eOp == FGSO_ISNOTNULL) { From 5573b18034fc8c6b84c709bef92a4658c7d738af Mon Sep 17 00:00:00 2001 From: Ben Lewis Date: Tue, 4 Jun 2024 22:58:00 +1000 Subject: [PATCH 0052/1119] User configuration docs: mention default User-Agent (#10124) --- doc/source/user/configoptions.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/source/user/configoptions.rst b/doc/source/user/configoptions.rst index 8c34ba88fcd0..1878022b9d23 100644 --- a/doc/source/user/configoptions.rst +++ b/doc/source/user/configoptions.rst @@ -928,8 +928,9 @@ Networking options - .. config:: GDAL_HTTP_USERAGENT - When set this string will be used to set the ``User-Agent`` header in the http + This string will be used to set the ``User-Agent`` header in the HTTP request sent to the remote server. + Defaults to "GDAL/x.y.z" where x.y.z is the GDAL build version. - .. config:: GDAL_HTTP_UNSAFESSL :choices: YES, NO From 802b23c6cac6a892704b85a09a1c876b4545d707 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 4 Jun 2024 17:03:22 +0200 Subject: [PATCH 0053/1119] [gdalmanage] Use GDALArgumentParser --- apps/gdalmanage.cpp | 306 ++++++++++++++++++++++++++------------------ 1 file changed, 182 insertions(+), 124 deletions(-) diff --git a/apps/gdalmanage.cpp b/apps/gdalmanage.cpp index 1860cf9ab349..46f965cb3809 100644 --- a/apps/gdalmanage.cpp +++ b/apps/gdalmanage.cpp @@ -33,22 +33,22 @@ #include "gdal_version.h" #include "gdal.h" #include "commonutils.h" +#include "gdalargumentparser.h" /************************************************************************/ -/* Usage() */ +/* GDALManageOptions() */ /************************************************************************/ -static void Usage(bool bIsError) - +struct GDALManageOptions { - fprintf(bIsError ? stderr : stdout, - "Usage: gdalmanage [--help] [--help-general]\n" - " or gdalmanage identify [-r|-fr] [-u] *\n" - " or gdalmanage copy [-f ] \n" - " or gdalmanage rename [-f ] \n" - " or gdalmanage delete [-f ] \n"); - exit(bIsError ? 1 : 0); -} + bool bRecursive = false; + bool bForceRecurse = false; + bool bReportFailures = false; + std::string osNewName; + std::string osDatasetName; + std::vector aosDatasetNames; + std::string osDriverName; +}; /************************************************************************/ /* ProcessIdentifyTarget() */ @@ -92,73 +92,111 @@ static void ProcessIdentifyTarget(const char *pszTarget, } /************************************************************************/ -/* Identify() */ +/* GDALManageAppOptionsGetParser() */ /************************************************************************/ -static void Identify(int nArgc, char **papszArgv) - +static std::unique_ptr +GDALManageAppOptionsGetParser(GDALManageOptions *psOptions) { - /* -------------------------------------------------------------------- */ - /* Scan for command line switches */ - /* -------------------------------------------------------------------- */ - bool bRecursive = false; - bool bForceRecurse = false; - bool bReportFailures = false; + auto argParser = std::make_unique( + "gdalmanage", /* bForBinary */ true); - int i = 0; - for (; i < nArgc && papszArgv[i][0] == '-'; ++i) - { - if (EQUAL(papszArgv[i], "-r")) - bRecursive = true; - else if (EQUAL(papszArgv[i], "-fr")) - { - bForceRecurse = true; - bRecursive = true; - } - else if (EQUAL(papszArgv[i], "-u")) - bReportFailures = true; - else - Usage(true); - } + argParser->add_description( + _("Identify, delete, rename and copy raster data files.")); + argParser->add_epilog(_("For more details, consult the full documentation " + "for the gdalmanage utility " + "https://gdal.org/programs/gdalmanage.html")); - /* -------------------------------------------------------------------- */ - /* Process given files. */ - /* -------------------------------------------------------------------- */ - for (; i < nArgc; ++i) + auto addCommonOptions = [psOptions](GDALArgumentParser *subParser) { - ProcessIdentifyTarget(papszArgv[i], nullptr, bRecursive, - bReportFailures, bForceRecurse); - } -} - -/************************************************************************/ -/* Delete() */ -/************************************************************************/ - -static void Delete(GDALDriverH hDriver, int nArgc, char **papszArgv) - -{ - if (nArgc != 1) - Usage(true); - - GDALDeleteDataset(hDriver, papszArgv[0]); -} - -/************************************************************************/ -/* Copy() */ -/************************************************************************/ - -static void Copy(GDALDriverH hDriver, int nArgc, char **papszArgv, - const char *pszOperation) - -{ - if (nArgc != 2) - Usage(true); - - if (EQUAL(pszOperation, "copy")) - GDALCopyDatasetFiles(hDriver, papszArgv[1], papszArgv[0]); - else - GDALRenameDataset(hDriver, papszArgv[1], papszArgv[0]); + subParser->add_argument("-f") + .metavar("") + .store_into(psOptions->osDriverName) + .help(_("Specify format of raster file if unknown by the " + "application.")); + + subParser->add_argument("newdatasetname") + .metavar("") + .store_into(psOptions->osNewName) + .help(_("Name of the new file.")); + }; + + // Identify + + auto identifyParser = + argParser->add_subparser("identify", /* bForBinary */ true); + identifyParser->add_description(_("List data format of file(s).")); + + identifyParser->add_argument("-r") + .flag() + .store_into(psOptions->bRecursive) + .help(_("Recursively scan files/folders for raster files.")); + + identifyParser->add_argument("-fr") + .flag() + .store_into(psOptions->bRecursive) + .store_into(psOptions->bForceRecurse) + .help(_("Recursively scan folders for raster files, forcing " + "recursion in folders recognized as valid formats.")); + + identifyParser->add_argument("-u") + .flag() + .store_into(psOptions->bReportFailures) + .help(_("Report failures if file type is unidentified.")); + + // Note: this accepts multiple files + identifyParser->add_argument("datasetname") + .metavar("") + .store_into(psOptions->aosDatasetNames) + .remaining() + .help(_("Name(s) of the file(s) to identify.")); + + // Copy + + auto copyParser = argParser->add_subparser("copy", /* bForBinary */ true); + copyParser->add_description( + _("Create a copy of the raster file with a new name.")); + + addCommonOptions(copyParser); + + copyParser->add_argument("datasetname") + .metavar("") + .store_into(psOptions->osDatasetName) + .help(_("Name of the file to copy.")); + + // Rename + + auto renameParser = + argParser->add_subparser("rename", /* bForBinary */ true); + renameParser->add_description(_("Change the name of the raster file.")); + + addCommonOptions(renameParser); + + renameParser->add_argument("datasetname") + .metavar("") + .store_into(psOptions->osDatasetName) + .help(_("Name of the file to rename.")); + + // Delete + + auto deleteParser = + argParser->add_subparser("delete", /* bForBinary */ true); + deleteParser->add_description(_("Delete the raster file(s).")); + + // Note: this accepts multiple files + deleteParser->add_argument("datasetname") + .metavar("") + .store_into(psOptions->aosDatasetNames) + .remaining() + .help(_("Name(s) of the file(s) to delete.")); + + deleteParser->add_argument("-f") + .metavar("") + .store_into(psOptions->osDriverName) + .help( + _("Specify format of raster file if unknown by the application.")); + + return argParser; } /************************************************************************/ @@ -168,64 +206,70 @@ static void Copy(GDALDriverH hDriver, int nArgc, char **papszArgv, MAIN_START(argc, argv) { - char *pszDriver = nullptr; - GDALDriverH hDriver = nullptr; - /* Check that we are running against at least GDAL 1.5 */ - /* Note to developers : if we use newer API, please change the requirement - */ - if (atoi(GDALVersionInfo("VERSION_NUM")) < 1500) - { - fprintf(stderr, - "At least, GDAL >= 1.5.0 is required for this version of %s, " - "which was compiled against GDAL %s\n", - argv[0], GDAL_RELEASE_NAME); - exit(1); - } - - GDALAllRegister(); + EarlySetConfigOptions(argc, argv); argc = GDALGeneralCmdLineProcessor(argc, &argv, 0); if (argc < 1) exit(-argc); - for (int i = 0; i < argc; i++) + /* -------------------------------------------------------------------- */ + /* Parse arguments. */ + /* -------------------------------------------------------------------- */ + + if (argc < 2) { - if (EQUAL(argv[i], "--utility_version")) + try { - printf("%s was compiled against GDAL %s and is running against " - "GDAL %s\n", - argv[0], GDAL_RELEASE_NAME, GDALVersionInfo("RELEASE_NAME")); - return 0; + GDALManageOptions sOptions; + auto argParser = GDALManageAppOptionsGetParser(&sOptions); + fprintf(stderr, "%s\n", argParser->usage().c_str()); } - else if (EQUAL(argv[i], "--help")) + catch (const std::exception &err) { - Usage(false); + CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s", + err.what()); } + CSLDestroy(argv); + exit(1); } - if (argc < 3) - Usage(true); + GDALAllRegister(); - /* -------------------------------------------------------------------- */ - /* Do we have a driver specifier? */ - /* -------------------------------------------------------------------- */ - char **papszRemainingArgv = argv + 2; - int nRemainingArgc = argc - 2; + GDALManageOptions psOptions; + auto argParser = GDALManageAppOptionsGetParser(&psOptions); - if (EQUAL(papszRemainingArgv[0], "-f") && nRemainingArgc > 1) + try { - pszDriver = papszRemainingArgv[1]; - papszRemainingArgv += 2; - nRemainingArgc -= 2; + argParser->parse_args_without_binary_name(argv + 1); + CSLDestroy(argv); + } + catch (const std::exception &error) + { + argParser->display_error_and_usage(error); + CSLDestroy(argv); + exit(1); } - if (pszDriver != nullptr) + // For some obscure reason datasetname is parsed as mandatory + // if used with remaining() in a subparser + if (psOptions.aosDatasetNames.empty() && psOptions.osDatasetName.empty()) { - hDriver = GDALGetDriverByName(pszDriver); + std::invalid_argument error( + _("No dataset name provided. At least one dataset " + "name is required.")); + argParser->display_error_and_usage(error); + exit(1); + } + + GDALDriverH hDriver = nullptr; + if (!psOptions.osDriverName.empty()) + { + hDriver = GDALGetDriverByName(psOptions.osDriverName.c_str()); if (hDriver == nullptr) { - fprintf(stderr, "Unable to find driver named '%s'.\n", pszDriver); + CPLError(CE_Failure, CPLE_AppDefined, "Failed to find driver '%s'.", + psOptions.osDriverName.c_str()); exit(1); } } @@ -233,26 +277,40 @@ MAIN_START(argc, argv) /* -------------------------------------------------------------------- */ /* Split out based on operation. */ /* -------------------------------------------------------------------- */ - if (STARTS_WITH_CI(argv[1], "ident" /* identify" */)) - Identify(nRemainingArgc, papszRemainingArgv); - - else if (EQUAL(argv[1], "copy")) - Copy(hDriver, nRemainingArgc, papszRemainingArgv, "copy"); - - else if (EQUAL(argv[1], "rename")) - Copy(hDriver, nRemainingArgc, papszRemainingArgv, "rename"); - - else if (EQUAL(argv[1], "delete")) - Delete(hDriver, nRemainingArgc, papszRemainingArgv); - else - Usage(true); + if (argParser->is_subcommand_used("identify")) + { + // Process all files in aosDatasetName + for (const auto &datasetName : psOptions.aosDatasetNames) + { + ProcessIdentifyTarget( + datasetName.c_str(), nullptr, psOptions.bRecursive, + psOptions.bReportFailures, psOptions.bForceRecurse); + } + } + else if (argParser->is_subcommand_used("copy")) + { + GDALCopyDatasetFiles(hDriver, psOptions.osDatasetName.c_str(), + psOptions.osNewName.c_str()); + } + else if (argParser->is_subcommand_used("rename")) + { + GDALRenameDataset(hDriver, psOptions.osDatasetName.c_str(), + psOptions.osNewName.c_str()); + } + else if (argParser->is_subcommand_used("delete")) + { + // Process all files in aosDatasetName + for (const auto &datasetName : psOptions.aosDatasetNames) + { + GDALDeleteDataset(hDriver, datasetName.c_str()); + } + } /* -------------------------------------------------------------------- */ /* Cleanup */ /* -------------------------------------------------------------------- */ - CSLDestroy(argv); - GDALDestroyDriverManager(); + GDALDestroy(); exit(0); } From 1d390796cf46386f54a968378c33ac7b3f25026c Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 4 Jun 2024 17:03:59 +0200 Subject: [PATCH 0054/1119] [gdalmanage] Update/fix documentation --- doc/source/programs/gdalmanage.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/source/programs/gdalmanage.rst b/doc/source/programs/gdalmanage.rst index 8c8b995f088c..7929c08c2406 100644 --- a/doc/source/programs/gdalmanage.rst +++ b/doc/source/programs/gdalmanage.rst @@ -16,7 +16,7 @@ Synopsis .. code-block:: Usage: gdalmanage [--help] [--help-general] - [-r] [-u] [-f ] + [-r] [-fr] [-u] [-f ] [] Description @@ -31,18 +31,22 @@ data types and deleting, renaming or copying the files. Mode of operation **identify** **: - List data format of file. + List data format of file(s). **copy** ** **: Create a copy of the raster file with a new name. **rename** ** **: Change the name of the raster file. **delete** **: - Delete raster file. + Delete raster file(s). .. option:: -r Recursively scan files/folders for raster files. +.. option:: -fr + + Recursively scan folders for raster files, forcing recursion in folders recognized as valid formats. + .. option:: -u Report failures if file type is unidentified. @@ -54,7 +58,7 @@ data types and deleting, renaming or copying the files. .. option:: - Raster file to operate on. + Raster file to operate on. With **identify** may be repeated for multiple files. .. option:: From 2c5b5090f67a844be4615479a8cac4ff2046cce0 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 4 Jun 2024 17:04:29 +0200 Subject: [PATCH 0055/1119] [gdalmanage] Tests --- autotest/pymod/test_cli_utilities.py | 8 + autotest/utilities/test_gdalmanage.py | 275 ++++++++++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 autotest/utilities/test_gdalmanage.py diff --git a/autotest/pymod/test_cli_utilities.py b/autotest/pymod/test_cli_utilities.py index 5c9257d0153f..9a8251d4ff4e 100755 --- a/autotest/pymod/test_cli_utilities.py +++ b/autotest/pymod/test_cli_utilities.py @@ -104,6 +104,14 @@ def get_gdalmdiminfo_path(): # +def get_gdalmanage_path(): + return get_cli_utility_path("gdalmanage") + + +############################################################################### +# + + def get_gdal_translate_path(): return get_cli_utility_path("gdal_translate") diff --git a/autotest/utilities/test_gdalmanage.py b/autotest/utilities/test_gdalmanage.py new file mode 100644 index 000000000000..22ef87c4c40c --- /dev/null +++ b/autotest/utilities/test_gdalmanage.py @@ -0,0 +1,275 @@ +#!/usr/bin/env pytest +# -*- coding: utf-8 -*- +############################################################################### +# $Id$ +# +# Project: GDAL/OGR Test Suite +# Purpose: gdalmanage testing +# Author: Alessandro Pasotti +# +############################################################################### +# Copyright (c) 2024, Alessandro Pasotti +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +############################################################################### + +import os + +import gdaltest +import pytest +import test_cli_utilities + +pytestmark = pytest.mark.skipif( + test_cli_utilities.get_gdalmanage_path() is None, + reason="gdalmanage not available", +) + + +@pytest.fixture() +def gdalmanage_path(): + return test_cli_utilities.get_gdalmanage_path() + + +############################################################################### +# Simple identify test + + +def test_gdalmanage_identify(gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + " identify data/utmsmall.tif" + ) + assert err == "" + assert "GTiff" in ret + + +############################################################################### +# Test -r option + + +def test_gdalmanage_identify_recursive_option(gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err(gdalmanage_path + " identify -r data") + assert err == "" + assert "ESRI Shapefile" in ret + assert len(ret.split("\n")) == 2 + + +############################################################################### +# Test -fr option + + +def test_gdalmanage_identify_force_recursive_option(gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + " identify -fr data" + ) + assert err == "" + ret = ret.replace("\\", "/") + assert len(ret.split("\n")) > 10 + assert "whiteblackred.tif: GTiff" in ret + assert "utmsmall.tif: GTiff" in ret + assert "ESRI Shapefile" in ret + assert "data/path.cpg: unrecognized" not in ret + + # Test both the -r and -fr options (shouldn't change the output) + (ret2, err2) = gdaltest.runexternal_out_and_err( + gdalmanage_path + " identify -r -fr data" + ) + ret2 = ret2.replace("\\", "/") + assert ret2 == ret and err2 == err + + +############################################################################### +# Test -u option + + +def test_gdalmanage_identify_report_failures_option(gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + " identify -fr -u data" + ) + assert err == "" + ret = ret.replace("\\", "/") + assert "whiteblackred.tif: GTiff" in ret + assert "utmsmall.tif: GTiff" in ret + assert "ESRI Shapefile" in ret + assert "data/path.cpg: unrecognized" in ret + + +############################################################################### +# Test identify multiple files + + +def test_gdalmanage_identify_multiple_files(gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + " identify data/utmsmall.tif data/whiteblackred.tif" + ) + assert err == "" + assert len(ret.split("\n")) == 3 + assert "whiteblackred.tif: GTiff" in ret + assert "utmsmall.tif: GTiff" in ret + + +############################################################################### +# Test copy file + + +def test_gdalmanage_copy_file(tmp_path, gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + f" copy data/utmsmall.tif {tmp_path}/utmsmall.tif" + ) + assert err == "" + # Verify the file was created + assert os.path.exists(f"{tmp_path}/utmsmall.tif") + + +############################################################################### +# Test copy file with -f option + + +def test_gdalmanage_copy_file_format(tmp_path, gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + f" copy -f GTiff data/utmsmall.tif {tmp_path}/utmsmall2.tif" + ) + assert err == "" + # Verify the file was created + assert os.path.exists(f"{tmp_path}/utmsmall2.tif") + + # Wrong format + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + + f" copy -f WRONGFORMAT data/utmsmall.tif {tmp_path}/utmsmall3.tif" + ) + assert "Failed to find driver 'WRONGFORMAT'" in err + + +############################################################################### +# Test rename file + + +def test_gdalmanage_rename_file(tmp_path, gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + f" copy data/utmsmall.tif {tmp_path}/utmsmall_to_rename.tif" + ) + assert err == "" + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + + f" rename {tmp_path}/utmsmall_to_rename.tif {tmp_path}/utmsmall_renamed.tif" + ) + assert err == "" + # Verify the file was renamed + assert os.path.exists(f"{tmp_path}/utmsmall_renamed.tif") + assert not os.path.exists(f"{tmp_path}/utmsmall_to_rename.tif") + + +############################################################################### +# Test delete file + + +def test_gdalmanage_delete_file(tmp_path, gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + f" copy data/utmsmall.tif {tmp_path}/utmsmall_to_delete.tif" + ) + assert err == "" + assert os.path.exists(f"{tmp_path}/utmsmall_to_delete.tif") + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + f" delete {tmp_path}/utmsmall_to_delete.tif" + ) + assert err == "" + # Verify the file was deleted + assert not os.path.exists(f"{tmp_path}/utmsmall_to_delete.tif") + + +############################################################################### +# Test delete multiple files + + +def test_gdalmanage_delete_multiple_files(tmp_path, gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + f" copy data/utmsmall.tif {tmp_path}/utmsmall_to_delete.tif" + ) + assert err == "" + assert os.path.exists(f"{tmp_path}/utmsmall_to_delete.tif") + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + + f" copy data/whiteblackred.tif {tmp_path}/whiteblackred_to_delete.tif" + ) + assert err == "" + assert os.path.exists(f"{tmp_path}/whiteblackred_to_delete.tif") + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + + f" delete {tmp_path}/utmsmall_to_delete.tif {tmp_path}/whiteblackred_to_delete.tif" + ) + assert err == "" + # Verify the files were deleted + assert not os.path.exists(f"{tmp_path}/utmsmall_to_delete.tif") + assert not os.path.exists(f"{tmp_path}/whiteblackred_to_delete.tif") + + +############################################################################### +# Test no arguments + + +def test_gdalmanage_no_arguments(gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err(gdalmanage_path) + assert "Usage: gdalmanage" in err + + +############################################################################### +# Test invalid command + + +def test_gdalmanage_invalid_command(gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err(gdalmanage_path + " invalidcommand") + assert "Usage: gdalmanage" in err + + +############################################################################### +# Test invalid argument + + +def test_gdalmanage_invalid_argument(gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err( + gdalmanage_path + " identify -WTF data/utmsmall.tif" + ) + assert "Usage: gdalmanage" in err + assert "Unknown argument: -WTF" in err + + +############################################################################### +# Test valid command with no required argument + + +def test_gdalmanage_valid_command_no_argument(gdalmanage_path): + + (ret, err) = gdaltest.runexternal_out_and_err(gdalmanage_path + " identify") + assert "Usage: gdalmanage" in err + assert ( + "Error: No dataset name provided. At least one dataset name is required" in err + ) From bb6e3d07ec31669d0683e69fd4f7dad36d576cca Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 17:13:05 +0200 Subject: [PATCH 0056/1119] VSIStatL(): allow trailing slash on mingw64 builds --- autotest/gcore/vsifile.py | 7 +++++++ port/cpl_vsil_win32.cpp | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/autotest/gcore/vsifile.py b/autotest/gcore/vsifile.py index 3dfdfcf377ee..ec13bbc5ad5e 100755 --- a/autotest/gcore/vsifile.py +++ b/autotest/gcore/vsifile.py @@ -1689,3 +1689,10 @@ def test_vsifile_class_append(tmp_vsimem): f.write("def") with gdaltest.vsi_open(fname) as f: assert f.read() == "abcdef" + + +def test_vsifile_stat_directory_trailing_slash(): + + res = gdal.VSIStatL("data/") + assert res + assert res.IsDirectory() diff --git a/port/cpl_vsil_win32.cpp b/port/cpl_vsil_win32.cpp index 81a5bc3678a0..a0f5385f21e6 100644 --- a/port/cpl_vsil_win32.cpp +++ b/port/cpl_vsil_win32.cpp @@ -904,6 +904,15 @@ int VSIWin32FilesystemHandler::Stat(const char *pszFilename, return nResult; } +#if defined(__MINGW32__) + // MinGW runtime for _wstat64() apparently doesn't like trailing slashes + // for directories. + const size_t nLen = wcslen(pwszFilename); + if (nLen > 0 && + (pwszFilename[nLen - 1] == '/' || pwszFilename[nLen - 1] == '\\')) + pwszFilename[nLen - 1] = 0; +#endif + int nResult = _wstat64(pwszFilename, pStatBuf); // If _wstat64() fails and the original name is not an extended one, From 565f852a138637e3a27a1f8405801314350a0d4e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 19:15:47 +0200 Subject: [PATCH 0057/1119] AAIGRID: fix forcing datatype to Float32 when NODATA_value is nan and pixel values look as ints --- autotest/gdrivers/data/aaigrid/nodata_nan.asc | 8 ++++++++ frmts/aaigrid/aaigriddataset.cpp | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 autotest/gdrivers/data/aaigrid/nodata_nan.asc diff --git a/autotest/gdrivers/data/aaigrid/nodata_nan.asc b/autotest/gdrivers/data/aaigrid/nodata_nan.asc new file mode 100644 index 000000000000..b5f75e3a3eba --- /dev/null +++ b/autotest/gdrivers/data/aaigrid/nodata_nan.asc @@ -0,0 +1,8 @@ +ncols 3 +nrows 2 +xllcorner 0 +yllcorner 0 +cellsize 1 +NODATA_value nan +nan -1 0 +1 2 3 diff --git a/frmts/aaigrid/aaigriddataset.cpp b/frmts/aaigrid/aaigriddataset.cpp index 098a8a47f474..4fc099611788 100644 --- a/frmts/aaigrid/aaigriddataset.cpp +++ b/frmts/aaigrid/aaigriddataset.cpp @@ -606,6 +606,7 @@ int AAIGDataset::ParseHeader(const char *pszHeader, const char *pszDataType) if (pszDataType == nullptr && (strchr(pszNoData, '.') != nullptr || strchr(pszNoData, ',') != nullptr || + std::isnan(dfNoDataValue) || std::numeric_limits::min() > dfNoDataValue || dfNoDataValue > std::numeric_limits::max())) { @@ -718,7 +719,7 @@ int GRASSASCIIDataset::ParseHeader(const char *pszHeader, dfNoDataValue = CPLAtofM(pszNoData); if (pszDataType == nullptr && (strchr(pszNoData, '.') != nullptr || - strchr(pszNoData, ',') != nullptr || + strchr(pszNoData, ',') != nullptr || std::isnan(dfNoDataValue) || std::numeric_limits::min() > dfNoDataValue || dfNoDataValue > std::numeric_limits::max())) { From fa9aa8c3a8d817f14efb509881b62b6c442417f4 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 20:04:43 +0200 Subject: [PATCH 0058/1119] GDALContourGenerateEx(): validate LEVEL_INTERVAL option Refs https://github.com/OSGeo/gdal/issues/10103 --- alg/contour.cpp | 8 ++++++++ autotest/alg/contour.py | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/alg/contour.cpp b/alg/contour.cpp index 34f29761b4f3..c95b28e41ea5 100644 --- a/alg/contour.cpp +++ b/alg/contour.cpp @@ -562,6 +562,14 @@ CPLErr GDALContourGenerateEx(GDALRasterBandH hBand, void *hLayer, if (opt) { contourInterval = CPLAtof(opt); + // Written this way to catch NaN as well. + if (!(contourInterval > 0)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Invalid value for LEVEL_INTERVAL. Should be strictly " + "positive."); + return CE_Failure; + } } double contourBase = 0.0; diff --git a/autotest/alg/contour.py b/autotest/alg/contour.py index a57a0d7875c6..c35a63dc0c65 100755 --- a/autotest/alg/contour.py +++ b/autotest/alg/contour.py @@ -401,3 +401,18 @@ def test_contour_raster_acquisition_error(): gdal.ContourGenerateEx( ds.GetRasterBand(1), ogr_lyr, options=["LEVEL_INTERVAL=1", "ID_FIELD=0"] ) + + +############################################################################### + + +def test_contour_invalid_LEVEL_INTERVAL(): + + ogr_ds = ogr.GetDriverByName("Memory").CreateDataSource("") + ogr_lyr = ogr_ds.CreateLayer("contour", geom_type=ogr.wkbLineString) + ds = gdal.Open("../gcore/data/byte.tif") + + with pytest.raises(Exception, match="Invalid value for LEVEL_INTERVAL"): + gdal.ContourGenerateEx( + ds.GetRasterBand(1), ogr_lyr, options=["LEVEL_INTERVAL=-1"] + ) From 4129d9041dc4f0066f460918b57ad33977b08ddf Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 19:17:16 +0200 Subject: [PATCH 0059/1119] VRT: fix processing of LUT where the first source value is NaN --- autotest/gdrivers/aaigrid.py | 12 ++++++ autotest/gdrivers/data/vrt/lut_with_nan.vrt | 9 +++++ autotest/gdrivers/vrtlut.py | 14 +++++++ doc/source/drivers/raster/vrt.rst | 5 ++- frmts/vrt/vrtsources.cpp | 41 ++++++++++++++++----- 5 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 autotest/gdrivers/data/vrt/lut_with_nan.vrt diff --git a/autotest/gdrivers/aaigrid.py b/autotest/gdrivers/aaigrid.py index a719e4618f01..8c1fb3f79854 100755 --- a/autotest/gdrivers/aaigrid.py +++ b/autotest/gdrivers/aaigrid.py @@ -31,6 +31,7 @@ # DEALINGS IN THE SOFTWARE. ############################################################################### +import math import os import struct @@ -523,3 +524,14 @@ def test_aaigrid_starting_with_nan(): ds = gdal.Open("data/aaigrid/starting_with_nan.asc") assert ds.GetRasterBand(1).DataType == gdal.GDT_Float32 assert ds.GetRasterBand(1).Checksum() == 65300 + + +############################################################################### +# Test reading a file starting with nan as nodata value + + +def test_aaigrid_nodata_nan(): + + ds = gdal.Open("data/aaigrid/nodata_nan.asc") + assert ds.GetRasterBand(1).DataType == gdal.GDT_Float32 + assert math.isnan(ds.GetRasterBand(1).GetNoDataValue()) diff --git a/autotest/gdrivers/data/vrt/lut_with_nan.vrt b/autotest/gdrivers/data/vrt/lut_with_nan.vrt new file mode 100644 index 000000000000..096afea3203d --- /dev/null +++ b/autotest/gdrivers/data/vrt/lut_with_nan.vrt @@ -0,0 +1,9 @@ + + + + ../aaigrid/nodata_nan.asc + 1 + nan:0,0:10,2:20 + + + diff --git a/autotest/gdrivers/vrtlut.py b/autotest/gdrivers/vrtlut.py index 060aec9e5664..251e89ae21ff 100755 --- a/autotest/gdrivers/vrtlut.py +++ b/autotest/gdrivers/vrtlut.py @@ -28,8 +28,12 @@ # DEALINGS IN THE SOFTWARE. ############################################################################### +import struct import gdaltest +import pytest + +from osgeo import gdal ############################################################################### # Simple test @@ -39,3 +43,13 @@ def test_vrtlut_1(): tst = gdaltest.GDALTest("VRT", "vrt/byte_lut.vrt", 1, 4655) tst.testOpen() + + +############################################################################### + + +@pytest.mark.require_driver("AAIGRID") +def test_vrtlut_with_nan(): + + ds = gdal.Open("data/vrt/lut_with_nan.vrt") + assert struct.unpack("B" * 2 * 3, ds.ReadRaster()) == (0, 10, 10, 15, 20, 20) diff --git a/doc/source/drivers/raster/vrt.rst b/doc/source/drivers/raster/vrt.rst index 6c4c937e2399..38e68559fe4e 100644 --- a/doc/source/drivers/raster/vrt.rst +++ b/doc/source/drivers/raster/vrt.rst @@ -424,8 +424,9 @@ the following form: The intermediary values are calculated using a linear interpolation between the bounding destination values of the corresponding range. -Source values should be monotonically non-decreasing. Clamping is performed for -input pixel values outside of the range specified by the LUT. That is, if an +Source values should be listed in a monotonically non-decreasing order. +If there is a Not-A-Number (NaN) source value, it should be the first one. +Clamping is performed for input pixel values outside of the range specified by the LUT. That is, if an input pixel value is lower than the minimum source value, then the destination value corresponding to that minimum source value is used as the output pixel value. And similarly for an input pixel value that is greater than the maximum source value. diff --git a/frmts/vrt/vrtsources.cpp b/frmts/vrt/vrtsources.cpp index ecb411fdc085..e6a696022e31 100644 --- a/frmts/vrt/vrtsources.cpp +++ b/frmts/vrt/vrtsources.cpp @@ -2633,9 +2633,21 @@ VRTComplexSource::XMLInit(const CPLXMLNode *psSrc, const char *pszVRTPath, // Enforce the requirement that the LUT input array is // monotonically non-decreasing. - if (nIndex > 0 && - m_adfLUTInputs[nIndex] < m_adfLUTInputs[nIndex - 1]) + if (std::isnan(m_adfLUTInputs[nIndex]) && nIndex != 0) { + CPLError(CE_Failure, CPLE_AppDefined, + "A Not-A-Number (NaN) source value should be the " + "first one of the LUT."); + m_adfLUTInputs.clear(); + m_adfLUTOutputs.clear(); + return CE_Failure; + } + else if (nIndex > 0 && + m_adfLUTInputs[nIndex] < m_adfLUTInputs[nIndex - 1]) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Source values of the LUT are not listed in a " + "monotonically non-decreasing order"); m_adfLUTInputs.clear(); m_adfLUTOutputs.clear(); return CE_Failure; @@ -2662,20 +2674,29 @@ VRTComplexSource::XMLInit(const CPLXMLNode *psSrc, const char *pszVRTPath, double VRTComplexSource::LookupValue(double dfInput) { + auto beginIter = m_adfLUTInputs.begin(); + auto endIter = m_adfLUTInputs.end(); + size_t offset = 0; + if (std::isnan(m_adfLUTInputs[0])) + { + if (std::isnan(dfInput) || m_adfLUTInputs.size() == 1) + return m_adfLUTOutputs[0]; + ++beginIter; + offset = 1; + } + // Find the index of the first element in the LUT input array that // is not smaller than the input value. - int i = static_cast( - std::lower_bound(m_adfLUTInputs.data(), - m_adfLUTInputs.data() + m_adfLUTInputs.size(), - dfInput) - - m_adfLUTInputs.data()); + const size_t i = + offset + + std::distance(beginIter, std::lower_bound(beginIter, endIter, dfInput)); - if (i == 0) - return m_adfLUTOutputs[0]; + if (i == offset) + return m_adfLUTOutputs[offset]; // If the index is beyond the end of the LUT input array, the input // value is larger than all the values in the array. - if (i == static_cast(m_adfLUTInputs.size())) + if (i == m_adfLUTInputs.size()) return m_adfLUTOutputs.back(); if (m_adfLUTInputs[i] == dfInput) From 6405e523449e7adb071e259c96aafc0d39611707 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 17:27:59 +0200 Subject: [PATCH 0060/1119] OGRSpatialReference: workaround bug of PROJ < 9.5 regarding wrong conversion id for UTM south Complement fix of https://github.com/OSGeo/PROJ/pull/4166 for PROJ < 9.5 Fixes https://gis.stackexchange.com/questions/482037/what-is-epsg17056 --- autotest/osr/osr_basic.py | 16 +++++ ogr/ogrspatialreference.cpp | 134 ++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/autotest/osr/osr_basic.py b/autotest/osr/osr_basic.py index 619b7619252a..5b6a2aef0d20 100755 --- a/autotest/osr/osr_basic.py +++ b/autotest/osr/osr_basic.py @@ -31,6 +31,7 @@ # DEALINGS IN THE SOFTWARE. ############################################################################### +import json import os import subprocess import sys @@ -2472,3 +2473,18 @@ def test_osr_basic_has_point_motion_operation(): srs = osr.SpatialReference() srs.ImportFromEPSG(8255) # NAD83(CSRS)v7 assert srs.HasPointMotionOperation() + + +############################################################################### + + +# Test workaround for https://github.com/OSGeo/PROJ/pull/4166 +def test_osr_basic_export_wkt_utm_south(): + + srs = osr.SpatialReference() + srs.SetFromUserInput("+proj=utm +zone=1 +south +datum=WGS84") + + assert 'ID["EPSG",16101]' in srs.ExportToWkt(["FORMAT=WKT2_2019"]) + + j = json.loads(srs.ExportToPROJJSON()) + assert j["conversion"]["id"]["code"] == 16101 diff --git a/ogr/ogrspatialreference.cpp b/ogr/ogrspatialreference.cpp index fad5d53c090c..f269cc81c470 100644 --- a/ogr/ogrspatialreference.cpp +++ b/ogr/ogrspatialreference.cpp @@ -1751,6 +1751,71 @@ OGRErr OGRSpatialReference::exportToWkt(char **ppszResult, } *ppszResult = CPLStrdup(pszWKT); + +#if !(PROJ_AT_LEAST_VERSION(9, 5, 0)) + if (wktFormat == PJ_WKT2_2018) + { + // Works around bug fixed per https://github.com/OSGeo/PROJ/pull/4166 + // related to a wrong EPSG code assigned to UTM South conversions + char *pszPtr = strstr(*ppszResult, "CONVERSION[\"UTM zone "); + if (pszPtr) + { + pszPtr += strlen("CONVERSION[\"UTM zone "); + const int nZone = atoi(pszPtr); + while (*pszPtr >= '0' && *pszPtr <= '9') + ++pszPtr; + if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' && + pszPtr[1] == '"' && pszPtr[2] == ',') + { + pszPtr += 3; + int nLevel = 0; + bool bInString = false; + // Find the ID node corresponding to this CONVERSION node + while (*pszPtr) + { + if (bInString) + { + if (*pszPtr == '"' && pszPtr[1] == '"') + { + ++pszPtr; + } + else if (*pszPtr == '"') + { + bInString = false; + } + } + else if (nLevel == 0 && STARTS_WITH_CI(pszPtr, "ID[")) + { + if (STARTS_WITH_CI(pszPtr, CPLSPrintf("ID[\"EPSG\",%d]", + 17000 + nZone))) + { + CPLAssert(pszPtr[11] == '7'); + CPLAssert(pszPtr[12] == '0'); + pszPtr[11] = '6'; + pszPtr[12] = '1'; + } + break; + } + else if (*pszPtr == '"') + { + bInString = true; + } + else if (*pszPtr == '[') + { + ++nLevel; + } + else if (*pszPtr == ']') + { + --nLevel; + } + + ++pszPtr; + } + } + } + } +#endif + proj_destroy(boundCRS); return OGRERR_NONE; } @@ -1901,6 +1966,75 @@ OGRErr OGRSpatialReference::exportToPROJJSON( } *ppszResult = CPLStrdup(pszPROJJSON); + +#if !(PROJ_AT_LEAST_VERSION(9, 5, 0)) + { + // Works around bug fixed per https://github.com/OSGeo/PROJ/pull/4166 + // related to a wrong EPSG code assigned to UTM South conversions + char *pszPtr = strstr(*ppszResult, "\"name\": \"UTM zone "); + if (pszPtr) + { + pszPtr += strlen("\"name\": \"UTM zone "); + const int nZone = atoi(pszPtr); + while (*pszPtr >= '0' && *pszPtr <= '9') + ++pszPtr; + if (nZone >= 1 && nZone <= 60 && *pszPtr == 'S' && pszPtr[1] == '"') + { + pszPtr += 2; + int nLevel = 0; + bool bInString = false; + // Find the id node corresponding to this conversion node + while (*pszPtr) + { + if (bInString) + { + if (*pszPtr == '\\') + { + ++pszPtr; + } + else if (*pszPtr == '"') + { + bInString = false; + } + } + else if (nLevel == 0 && STARTS_WITH(pszPtr, "\"id\": {")) + { + const char *pszNextEndCurl = strchr(pszPtr, '}'); + const char *pszAuthEPSG = + strstr(pszPtr, "\"authority\": \"EPSG\""); + char *pszCode = strstr( + pszPtr, CPLSPrintf("\"code\": %d", 17000 + nZone)); + if (pszAuthEPSG && pszCode && pszNextEndCurl && + pszNextEndCurl - pszAuthEPSG > 0 && + pszNextEndCurl - pszCode > 0) + { + CPLAssert(pszCode[9] == '7'); + CPLAssert(pszCode[10] == '0'); + pszCode[9] = '6'; + pszCode[10] = '1'; + } + break; + } + else if (*pszPtr == '"') + { + bInString = true; + } + else if (*pszPtr == '{' || *pszPtr == '[') + { + ++nLevel; + } + else if (*pszPtr == '}' || *pszPtr == ']') + { + --nLevel; + } + + ++pszPtr; + } + } + } + } +#endif + return OGRERR_NONE; } From 797575e2fe71853b9ccdd9755cb0dd8e63a84732 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 17:57:38 +0200 Subject: [PATCH 0061/1119] geojsonseq.rst: fix link to ndjson Fixes #10134 --- doc/source/drivers/vector/geojsonseq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/drivers/vector/geojsonseq.rst b/doc/source/drivers/vector/geojsonseq.rst index d6074a9f293c..3fab119bcdae 100644 --- a/doc/source/drivers/vector/geojsonseq.rst +++ b/doc/source/drivers/vector/geojsonseq.rst @@ -12,7 +12,7 @@ GeoJSONSeq: sequence of GeoJSON features This driver implements read/creation support for features encoded individually as `GeoJSON `__ Feature objects, separated by newline (LF) (`Newline Delimited -JSON `__) or record-separator (RS) characters (`RFC +JSON https://github.com/ndjson/ndjson-spec>`__) or record-separator (RS) characters (`RFC 8142 `__ standard: GeoJSON Text Sequences) From 0077e7e43f6b1db2dac507f65510f38b81b060ff Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 18:35:30 +0200 Subject: [PATCH 0062/1119] Doc: fix link --- doc/source/drivers/vector/geojsonseq.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/drivers/vector/geojsonseq.rst b/doc/source/drivers/vector/geojsonseq.rst index 3fab119bcdae..1701d06dc9a0 100644 --- a/doc/source/drivers/vector/geojsonseq.rst +++ b/doc/source/drivers/vector/geojsonseq.rst @@ -12,7 +12,7 @@ GeoJSONSeq: sequence of GeoJSON features This driver implements read/creation support for features encoded individually as `GeoJSON `__ Feature objects, separated by newline (LF) (`Newline Delimited -JSON https://github.com/ndjson/ndjson-spec>`__) or record-separator (RS) characters (`RFC +JSON `__) or record-separator (RS) characters (`RFC 8142 `__ standard: GeoJSON Text Sequences) @@ -103,7 +103,7 @@ The following layer creation options are supported: :since: 3.8 Whether to write - NaN / Infinity values. Such values are not allowed in strict JSON + NaN / Infinity values. Such values are not allowed in strict JSON mode, but some JSON parsers (libjson-c >= 0.12 for example) can understand them as they are allowed by ECMAScript. From 8a5fb15dc17b18b42c5b26fd0a6e93358b2064e8 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 20:01:03 +0200 Subject: [PATCH 0063/1119] Doc: OpenFileGDB: document that sdc format can't be read --- doc/source/drivers/vector/filegdb.rst | 16 +++++++------- doc/source/drivers/vector/openfilegdb.rst | 27 +++++++++++++++-------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/doc/source/drivers/vector/filegdb.rst b/doc/source/drivers/vector/filegdb.rst index b83bddb2ce2d..718000290d68 100644 --- a/doc/source/drivers/vector/filegdb.rst +++ b/doc/source/drivers/vector/filegdb.rst @@ -281,21 +281,25 @@ On layer creation, the XORIGIN, YORIGIN, ZORIGIN, MORIGIN, XYSCALE, ZSCALE, ZORIGIN, XYTOLERANCE, ZTOLERANCE, MTOLERANCE layer creation options will be used in priority over the settings of :cpp:class:`OGRGeomCoordinatePrecision`. -Known Issues ------------- +Limitations +----------- - The SDK is known to be unable to open layers with particular spatial reference systems. This might be the case if messages "FGDB: Error opening XXXXXXX. Skipping it (Invalid function arguments.)" when running ``ogrinfo --debug on the.gdb`` (reported as warning in GDAL 2.0). Using the OpenFileGDB driver will generally solve that issue. + - FGDB coordinate snapping will cause geometries to be altered during writing. Use the origin and scale layer creation options to control the snapping behavior. -- Driver can't read data in SDC format (Smart Data Compression) because - operation is not supported by the ESRI SDK. + +- Reading data compressed in SDC format (Smart Data Compression) is not + support by the driver, because it is not supported by the ESRI SDK. + - Reading data compressed in CDF format (Compressed Data Format) requires ESRI SDK 1.4 or later. + - Some applications create FileGeodatabases with non-spatial tables which are not present in the GDB_Items metadata table. These tables cannot be opened by the ESRI SDK, so GDAL will automatically fallback to the OpenFileGDB @@ -303,10 +307,6 @@ Known Issues limitations of the OpenFileGDB driver (for instance, they will be read only). - -Other limitations ------------------ - - The driver does not support 64-bit integers. Links diff --git a/doc/source/drivers/vector/openfilegdb.rst b/doc/source/drivers/vector/openfilegdb.rst index e451956981a7..6dea6fbda869 100644 --- a/doc/source/drivers/vector/openfilegdb.rst +++ b/doc/source/drivers/vector/openfilegdb.rst @@ -310,26 +310,35 @@ On layer creation, the XORIGIN, YORIGIN, ZORIGIN, MORIGIN, XYSCALE, ZSCALE, ZORIGIN, XYTOLERANCE, ZTOLERANCE, MTOLERANCE layer creation options will be used in priority over the settings of :cpp:class:`OGRGeomCoordinatePrecision`. -Comparison with the FileGDB driver ----------------------------------- - -(Comparison done with a FileGDB driver using FileGDB API SDK 1.4) - -Advantages of the OpenFileGDB driver: +Advantages of the OpenFileGDB driver, compared to the FileGDB driver +-------------------------------------------------------------------- - Can read ArcGIS 9.X Geodatabases, and not only 10 or above. + - Can open layers with any spatial reference system. + - Thread-safe (i.e. datasources can be processed in parallel). + - Uses the VSI Virtual File API, enabling the user to read a Geodatabase in a ZIP file or stored on a HTTP server. + - Faster on databases with a big number of fields. + - Does not depend on a third-party library. + - Robust against corrupted Geodatabase files. -Drawbacks of the OpenFileGDB driver: +Limitations +----------- + +- Reading data compressed in SDC format (Smart Data Compression) or in + CDF format (Compressed Data Format) is not supported. For CDF, + the :ref:`FileGDB driver ` can be used. -- Cannot read data from compressed data in CDF format (Compressed Data - Format). +- Support for tables with 64-bit OBJECTIDs (which require GDAL >= 3.10), where + OBJECTIDs are sparse, is read-only and incomplete. + The driver will emit a warning if such situation occurs, and will attribute + non-faithful OBJECTIDs. Examples -------- From 063c54cb45a0c0c54289c95eaed657820c5a46b7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 22:48:15 +0200 Subject: [PATCH 0064/1119] GeoTIFF multithreaded reading: avoid emitting tons of warnings when ExtraSamples tag is missing Fixes https://github.com/OSGeo/gdal/issues/8984#issuecomment-2150655514 --- .../gcore/data/gtiff/missing_extrasamples.tif | Bin 0 -> 2734 bytes autotest/gcore/tiff_read.py | 14 ++++++++ frmts/gtiff/gtiffdataset_read.cpp | 30 +++++++++++++++--- 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 autotest/gcore/data/gtiff/missing_extrasamples.tif diff --git a/autotest/gcore/data/gtiff/missing_extrasamples.tif b/autotest/gcore/data/gtiff/missing_extrasamples.tif new file mode 100644 index 0000000000000000000000000000000000000000..c993258b667e519744673102a728fdd457800352 GIT binary patch literal 2734 zcmeIzp-*aI6aerGI-ifXxjnYW+t{0dKp+?l1cHIU_6!8xMj#Lf1_FUVAP@)ygTY`h z5C{Z=!9XAo2n2({U@#B}1OkCzAh7qt8Jz!u-m~A``OeO_-S6z)-M@cR!F~_~KY}2% z6NF^1-tz4tEZRzv5F*;MTiw$ABRt#!R^9fk&QrX3C5?3zn>SVaCxwgTkaSzWW<;W_dM{(6VFVUF=xS& z6)&vW5RN!M5e|qFBhDcSlB77|m^2wq$dcofJOzrBC{y8#DmCgfXwsrhhjT8tnYCrbCrSubSampling1); } } - if (psContext->pExtraSamples) + if (poDS->m_nPlanarConfig == PLANARCONFIG_CONTIG) { - TIFFSetField(hTIFFTmp, TIFFTAG_EXTRASAMPLES, - psContext->nExtraSampleCount, - psContext->pExtraSamples); + if (psContext->pExtraSamples) + { + TIFFSetField(hTIFFTmp, TIFFTAG_EXTRASAMPLES, + psContext->nExtraSampleCount, + psContext->pExtraSamples); + } + else + { + const int nSamplesAccountedFor = + poDS->m_nPhotometric == PHOTOMETRIC_RGB ? 3 + : poDS->m_nPhotometric == PHOTOMETRIC_MINISBLACK ? 1 + : 0; + if (nSamplesAccountedFor > 0 && + poDS->m_nSamplesPerPixel > nSamplesAccountedFor) + { + // If the input image is not compliant regarndig ExtraSamples, + // generate a synthetic one to avoid gazillons of warnings + const auto nExtraSampleCount = static_cast( + poDS->m_nSamplesPerPixel - nSamplesAccountedFor); + std::vector anExtraSamples( + nExtraSampleCount, EXTRASAMPLE_UNSPECIFIED); + TIFFSetField(hTIFFTmp, TIFFTAG_EXTRASAMPLES, + nExtraSampleCount, anExtraSamples.data()); + } + } } TIFFWriteCheck(hTIFFTmp, FALSE, "ThreadDecompressionFunc"); TIFFWriteDirectory(hTIFFTmp); From 4ad7504c2615a7862af9d9633dad7fa670a7ae64 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 22:06:44 +0200 Subject: [PATCH 0065/1119] OGRSpatialReference::FindMatches(): improve when input SRS doesn't have expected axis order Relates to https://github.com/OSGeo/gdal/issues/10129 --- autotest/osr/osr_epsg.py | 39 +++++++++++++++++++++ ogr/ogrspatialreference.cpp | 67 +++++++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/autotest/osr/osr_epsg.py b/autotest/osr/osr_epsg.py index 901b034389a1..7356a19fd995 100755 --- a/autotest/osr/osr_epsg.py +++ b/autotest/osr/osr_epsg.py @@ -392,6 +392,45 @@ def test_osr_epsg_13(): assert matches[0][0].IsSame(sr) != 1 +############################################################################### +# Test FindMatches() when input SRS doesn't have expected axis order + +# Not sure about the minimum PROJ version, but 6.3 doesn't work +@pytest.mark.require_proj(8, 0) +def test_osr_epsg_find_matches_wrong_axis_order(): + + sr = osr.SpatialReference() + # NZTM2000 with implicit axis (thus east, north) + sr.SetFromUserInput( + """PROJCS["NZGD2000 / New Zealand Transverse Mercator 2000", + GEOGCS["NZGD2000", + DATUM["New_Zealand_Geodetic_Datum_2000", + SPHEROID["GRS 1980",6378137,298.257222101, + AUTHORITY["EPSG","7019"]], + AUTHORITY["EPSG","6167"]], + PRIMEM["Greenwich",0, + AUTHORITY["EPSG","8901"]], + UNIT["degree",0.0174532925199433, + AUTHORITY["EPSG","9122"]], + AUTHORITY["EPSG","4167"]], + PROJECTION["Transverse_Mercator"], + PARAMETER["latitude_of_origin",0], + PARAMETER["central_meridian",173], + PARAMETER["scale_factor",0.9996], + PARAMETER["false_easting",1600000], + PARAMETER["false_northing",10000000], + UNIT["metre",1, + AUTHORITY["EPSG","9001"]], + AXIS["X",EAST], + AXIS["Y",NORTH]] +""" + ) + matches = sr.FindMatches() + assert len(matches) == 1 and matches[0][1] == 90 + assert matches[0][0].GetAuthorityCode(None) == "2193" + assert matches[0][0].GetDataAxisToSRSAxisMapping() == [2, 1] + + ############################################################################### diff --git a/ogr/ogrspatialreference.cpp b/ogr/ogrspatialreference.cpp index fad5d53c090c..09e52f9bf27a 100644 --- a/ogr/ogrspatialreference.cpp +++ b/ogr/ogrspatialreference.cpp @@ -11367,8 +11367,9 @@ OGRSpatialReference::FindMatches(char **papszOptions, int *pnEntries, return nullptr; int *panConfidence = nullptr; - auto list = proj_identify(d->getPROJContext(), d->m_pj_crs, nullptr, - nullptr, &panConfidence); + auto ctxt = d->getPROJContext(); + auto list = + proj_identify(ctxt, d->m_pj_crs, nullptr, nullptr, &panConfidence); if (!list) return nullptr; @@ -11383,16 +11384,76 @@ OGRSpatialReference::FindMatches(char **papszOptions, int *pnEntries, *ppanMatchConfidence = static_cast(CPLMalloc(sizeof(int) * (nMatches + 1))); } + + bool bSortAgain = false; + for (int i = 0; i < nMatches; i++) { - PJ *obj = proj_list_get(d->getPROJContext(), list, i); + PJ *obj = proj_list_get(ctxt, list, i); CPLAssert(obj); OGRSpatialReference *poSRS = new OGRSpatialReference(); poSRS->d->setPjCRS(obj); pahRet[i] = ToHandle(poSRS); + + // Identify matches that only differ by axis order + if (panConfidence[i] == 50 && GetAxesCount() == 2 && + poSRS->GetAxesCount() == 2 && + GetDataAxisToSRSAxisMapping() == std::vector{1, 2}) + { + OGRAxisOrientation eThisAxis0 = OAO_Other; + OGRAxisOrientation eThisAxis1 = OAO_Other; + OGRAxisOrientation eSRSAxis0 = OAO_Other; + OGRAxisOrientation eSRSAxis1 = OAO_Other; + GetAxis(nullptr, 0, &eThisAxis0); + GetAxis(nullptr, 1, &eThisAxis1); + poSRS->GetAxis(nullptr, 0, &eSRSAxis0); + poSRS->GetAxis(nullptr, 1, &eSRSAxis1); + if (eThisAxis0 == OAO_East && eThisAxis1 == OAO_North && + eSRSAxis0 == OAO_North && eSRSAxis1 == OAO_East) + { + auto pj_crs_normalized = + proj_normalize_for_visualization(ctxt, poSRS->d->m_pj_crs); + if (pj_crs_normalized) + { + if (proj_is_equivalent_to(d->m_pj_crs, pj_crs_normalized, + PJ_COMP_EQUIVALENT)) + { + bSortAgain = true; + panConfidence[i] = 90; + poSRS->SetDataAxisToSRSAxisMapping({2, 1}); + } + proj_destroy(pj_crs_normalized); + } + } + } + if (ppanMatchConfidence) (*ppanMatchConfidence)[i] = panConfidence[i]; } + + if (bSortAgain) + { + std::vector anIndices; + for (int i = 0; i < nMatches; ++i) + anIndices.push_back(i); + + std::stable_sort(anIndices.begin(), anIndices.end(), + [&panConfidence](int i, int j) + { return panConfidence[i] > panConfidence[j]; }); + + OGRSpatialReferenceH *pahRetSorted = + static_cast( + CPLCalloc(sizeof(OGRSpatialReferenceH), nMatches + 1)); + for (int i = 0; i < nMatches; ++i) + { + pahRetSorted[i] = pahRet[anIndices[i]]; + if (ppanMatchConfidence) + (*ppanMatchConfidence)[i] = panConfidence[anIndices[i]]; + } + CPLFree(pahRet); + pahRet = pahRetSorted; + } + pahRet[nMatches] = nullptr; proj_list_destroy(list); proj_int_list_destroy(panConfidence); From 04f11a9a70ea253aaf27f50f6782279d5df53e3f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 22:13:39 +0200 Subject: [PATCH 0066/1119] HFA SRS reading: strip TOWGS84 when datum name is known, and use FindBestMatch() to try to find known SRS Fixes https://github.com/OSGeo/gdal/issues/10129 --- autotest/gcore/hfa_srs.py | 25 ++++++++++++++++++++++++- frmts/hfa/hfadataset.cpp | 12 +++++++----- frmts/hfa/hfaopen.cpp | 30 +++++++++++++++++++++++------- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/autotest/gcore/hfa_srs.py b/autotest/gcore/hfa_srs.py index a921095c8e2d..f560996ed906 100755 --- a/autotest/gcore/hfa_srs.py +++ b/autotest/gcore/hfa_srs.py @@ -250,7 +250,8 @@ def test_hfa_srs_NAD83_CORS96_UTM(): ds = gdal.Open("/vsimem/TestHFASRS.img") srs_got = ds.GetSpatialRef() - assert srs_got.GetAuthorityName(None) is None + assert srs_got.GetAuthorityName(None) == "ESRI" + assert srs_got.GetAuthorityCode(None) == "102411" assert srs_got.IsSame(sr), srs_got.ExportToWkt() ds = None @@ -286,3 +287,25 @@ def test_hfa_srs_DISABLEPESTRING(): ds = None gdal.Unlink(filename) + + +# Not sure about the minimum PROJ version, but 6.3 doesn't work +@pytest.mark.require_proj(8, 0) +def test_hfa_srs_EPSG_2193(tmp_vsimem): + + filename = str(tmp_vsimem / "test.img") + sr = osr.SpatialReference() + sr.ImportFromEPSG(2193) + sr.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER) + + ds = gdal.GetDriverByName("HFA").Create(filename, 1, 1) + ds.SetSpatialRef(sr) + ds = None + + ds = gdal.Open(filename) + srs_got = ds.GetSpatialRef() + assert srs_got.GetAuthorityName(None) == "EPSG" + assert srs_got.GetAuthorityCode(None) == "2193" + assert srs_got.GetDataAxisToSRSAxisMapping() == [2, 1] + assert srs_got.IsSame(sr) + ds = None diff --git a/frmts/hfa/hfadataset.cpp b/frmts/hfa/hfadataset.cpp index d4ae113c0a65..4615d4d86366 100644 --- a/frmts/hfa/hfadataset.cpp +++ b/frmts/hfa/hfadataset.cpp @@ -3184,16 +3184,18 @@ CPLErr HFADataset::WriteProjection() if (nGCS == 4326) sDatum.datumname = const_cast("WGS 84"); - if (nGCS == 4322) + else if (nGCS == 4322) sDatum.datumname = const_cast("WGS 1972"); - if (nGCS == 4267) + else if (nGCS == 4267) sDatum.datumname = const_cast("NAD27"); - if (nGCS == 4269) + else if (nGCS == 4269) sDatum.datumname = const_cast("NAD83"); - if (nGCS == 4283) + else if (nGCS == 4283) sDatum.datumname = const_cast("GDA94"); - if (nGCS == 6284) + else if (nGCS == 4284) sDatum.datumname = const_cast("Pulkovo 1942"); + else if (nGCS == 4272) + sDatum.datumname = const_cast("Geodetic Datum 1949"); if (poGeogSRS->GetTOWGS84(sDatum.params) == OGRERR_NONE) { diff --git a/frmts/hfa/hfaopen.cpp b/frmts/hfa/hfaopen.cpp index f8ee8fecdd2c..0cf3458b3cdc 100644 --- a/frmts/hfa/hfaopen.cpp +++ b/frmts/hfa/hfaopen.cpp @@ -3926,13 +3926,22 @@ static int ESRIToUSGSZone(int nESRIZone) static const char *const apszDatumMap[] = { // Imagine name, WKT name. - "NAD27", "North_American_Datum_1927", - "NAD83", "North_American_Datum_1983", - "WGS 84", "WGS_1984", - "WGS 1972", "WGS_1972", - "GDA94", "Geocentric_Datum_of_Australia_1994", - "Pulkovo 1942", "Pulkovo_1942", - nullptr, nullptr}; + "NAD27", + "North_American_Datum_1927", + "NAD83", + "North_American_Datum_1983", + "WGS 84", + "WGS_1984", + "WGS 1972", + "WGS_1972", + "GDA94", + "Geocentric_Datum_of_Australia_1994", + "Pulkovo 1942", + "Pulkovo_1942", + "Geodetic Datum 1949", + "New_Zealand_Geodetic_Datum_1949", + nullptr, + nullptr}; const char *const *HFAGetDatumMap() { @@ -4730,6 +4739,7 @@ HFAPCSStructToOSR(const Eprj_Datum *psDatum, const Eprj_ProParameters *psPro, -psDatum->params[4] * RAD2ARCSEC, -psDatum->params[5] * RAD2ARCSEC, psDatum->params[6] * 1e+6); + poSRS->StripTOWGS84IfKnownDatumAndAllowed(); } } } @@ -4737,5 +4747,11 @@ HFAPCSStructToOSR(const Eprj_Datum *psDatum, const Eprj_ProParameters *psPro, // Try to insert authority information if possible. poSRS->AutoIdentifyEPSG(); + auto poSRSBestMatch = poSRS->FindBestMatch(90, nullptr, nullptr); + if (poSRSBestMatch) + { + poSRS.reset(poSRSBestMatch); + } + return poSRS; } From f07ecef01259b1e577c06bff29aa5f19a67cde14 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 23:31:38 +0200 Subject: [PATCH 0067/1119] LIBKML: fix performance issue when writing large files Fixes #10138 --- .../libkml/ogrlibkmldatasource.cpp | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp b/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp index e20653c857a9..ac600fe32475 100644 --- a/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp +++ b/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp @@ -206,37 +206,57 @@ static void OGRLIBKMLPreProcessInput(std::string &oKml) /* OGRLIBKMLRemoveSpaces() */ /************************************************************************/ -static void OGRLIBKMLRemoveSpaces(std::string &oKml, +static void OGRLIBKMLRemoveSpaces(std::string &osKml, const std::string &osNeedle) { size_t nPos = 0; + const std::string osLtNeedle(std::string("<").append(osNeedle)); + std::string osTmp; + std::string osRet; while (true) { - nPos = oKml.find("<" + osNeedle, nPos); - if (nPos == std::string::npos) + auto nPosNew = osKml.find(osLtNeedle, nPos); + if (nPosNew == std::string::npos) { + osRet.append(osKml, nPos); break; } - const size_t nPosOri = nPos; - nPos = oKml.find(">", nPos); - if (nPos == std::string::npos || oKml[nPos + 1] != '\n') + const size_t nPosOri = nPosNew; + nPosNew = osKml.find(">\n", nPosNew); + if (nPosNew == std::string::npos || nPosNew + 2 == osKml.size()) { + osRet.append(osKml, nPos); break; } - oKml = oKml.substr(0, nPos) + ">" + oKml.substr(nPos + strlen(">\n")); - CPLString osSpaces; - for (size_t nPosTmp = nPosOri - 1; oKml[nPosTmp] == ' '; nPosTmp--) + // Skip \n character + osRet.append(osKml, nPos, nPosNew - nPos + 1); + nPos = nPosNew + 2; + + // Remove leading spaces of " " + osTmp.clear(); + for (size_t nPosTmp = nPosOri - 1; osKml[nPosTmp] == ' '; nPosTmp--) + { + osTmp += ' '; + } + osTmp += "'; + nPosNew = osKml.find(osTmp, nPos); + if (nPosNew != std::string::npos) { - osSpaces += ' '; + osRet.append(osKml, nPos, nPosNew - nPos); + osRet += "'; + nPos = nPosNew + osTmp.size(); } - nPos = oKml.find(osSpaces + "", nPos); - if (nPos != std::string::npos) - oKml = oKml.substr(0, nPos) + "" + - oKml.substr(nPos + osSpaces.size() + strlen("") + - osNeedle.size()); else + { + osRet.append(osKml, nPos); break; + } } + osKml = std::move(osRet); } /************************************************************************/ From 7aae632f21ac69dd05ea46c7f724f9800cd77b52 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 23:37:50 +0200 Subject: [PATCH 0068/1119] Doc: gdalwarp/ogr2ogr -ct: be more explicit about axis order issues Refs https://github.com/OSGeo/gdal/issues/10141 --- doc/source/programs/gdalwarp.rst | 9 +++++++-- doc/source/programs/ogr2ogr.rst | 10 +++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/doc/source/programs/gdalwarp.rst b/doc/source/programs/gdalwarp.rst index d21f00e50ece..f4886cbc9122 100644 --- a/doc/source/programs/gdalwarp.rst +++ b/doc/source/programs/gdalwarp.rst @@ -159,8 +159,13 @@ with control information. A PROJ string (single step operation or multiple step string starting with +proj=pipeline), a WKT2 string describing a CoordinateOperation, or a urn:ogc:def:coordinateOperation:EPSG::XXXX URN overriding the default - transformation from the source to the target CRS. It must take into account the - axis order of the source and target CRS. + transformation from the source to the target CRS. + + It must take into account the axis order of the source and target CRS, that + is typically include a ``step proj=axisswap order=2,1`` at the beginning of + the pipeline if the source CRS has northing/easting axis order, and/or at + the end of the pipeline if the target CRS has northing/easting axis order. + When creating a new output file, using :option:`-t_srs` is still necessary to have the target CRS written in the metadata of the output file, but the parameters of the CoordinateOperation will override those of the diff --git a/doc/source/programs/ogr2ogr.rst b/doc/source/programs/ogr2ogr.rst index a45c7395a601..7faa5a6d55bd 100644 --- a/doc/source/programs/ogr2ogr.rst +++ b/doc/source/programs/ogr2ogr.rst @@ -340,8 +340,12 @@ output coordinate system or even reprojecting the features during translation. A PROJ string (single step operation or multiple step string starting with +proj=pipeline), a WKT2 string describing a CoordinateOperation, or a urn:ogc:def:coordinateOperation:EPSG::XXXX URN overriding the default - transformation from the source to the target CRS. It must take into account - the axis order of the source and target CRS. + transformation from the source to the target CRS. + + It must take into account the axis order of the source and target CRS, that + is typically include a ``step proj=axisswap order=2,1`` at the beginning of + the pipeline if the source CRS has northing/easting axis order, and/or at + the end of the pipeline if the target CRS has northing/easting axis order. .. versionadded:: 3.0 @@ -453,7 +457,7 @@ output coordinate system or even reprojecting the features during translation. The specified value of this option is the tolerance used to merge consecutive points of the output geometry using the :cpp:func:`OGRGeometry::Simplify` method - The unit of the distance is in + The unit of the distance is in georeferenced units of the source vector dataset. This option is applied before the reprojection implied by :option:`-t_srs` From 11b43a35fc1d1b8145b17796d8198b65a15fd0a3 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 00:46:49 +0200 Subject: [PATCH 0069/1119] gdalinfo/ogrinfo text output: avoid weird truncation of coordinate epoch when its value is {year}.0 --- apps/gdalinfo_lib.cpp | 8 +++++--- apps/ogrinfo_lib.cpp | 8 +++++--- autotest/utilities/test_gdalinfo_lib.py | 9 +++++---- autotest/utilities/test_ogrinfo_lib.py | 23 ++++++++++++++++++++++- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/apps/gdalinfo_lib.cpp b/apps/gdalinfo_lib.cpp index 8aa00d02b6a7..e357e76e211e 100644 --- a/apps/gdalinfo_lib.cpp +++ b/apps/gdalinfo_lib.cpp @@ -639,10 +639,12 @@ char *GDALInfo(GDALDatasetH hDataset, const GDALInfoOptions *psOptions) { std::string osCoordinateEpoch = CPLSPrintf("%f", dfCoordinateEpoch); - if (osCoordinateEpoch.find('.') != std::string::npos) + const size_t nDotPos = osCoordinateEpoch.find('.'); + if (nDotPos != std::string::npos) { - while (osCoordinateEpoch.back() == '0') - osCoordinateEpoch.resize(osCoordinateEpoch.size() - 1); + while (osCoordinateEpoch.size() > nDotPos + 2 && + osCoordinateEpoch.back() == '0') + osCoordinateEpoch.pop_back(); } Concat(osStr, psOptions->bStdoutOutput, "Coordinate epoch: %s\n", osCoordinateEpoch.c_str()); diff --git a/apps/ogrinfo_lib.cpp b/apps/ogrinfo_lib.cpp index 9612d6570d7b..42538bc65f6b 100644 --- a/apps/ogrinfo_lib.cpp +++ b/apps/ogrinfo_lib.cpp @@ -1191,10 +1191,12 @@ static void ReportOnLayer(CPLString &osRet, CPLJSONObject &oLayer, { std::string osCoordinateEpoch = CPLSPrintf("%f", dfCoordinateEpoch); - if (osCoordinateEpoch.find('.') != std::string::npos) + const size_t nDotPos = osCoordinateEpoch.find('.'); + if (nDotPos != std::string::npos) { - while (osCoordinateEpoch.back() == '0') - osCoordinateEpoch.resize(osCoordinateEpoch.size() - 1); + while (osCoordinateEpoch.size() > nDotPos + 2 && + osCoordinateEpoch.back() == '0') + osCoordinateEpoch.pop_back(); } Concat(osRet, psOptions->bStdoutOutput, "Coordinate epoch: %s\n", osCoordinateEpoch.c_str()); diff --git a/autotest/utilities/test_gdalinfo_lib.py b/autotest/utilities/test_gdalinfo_lib.py index 73a8c9648fe7..def364dcab77 100755 --- a/autotest/utilities/test_gdalinfo_lib.py +++ b/autotest/utilities/test_gdalinfo_lib.py @@ -201,17 +201,18 @@ def test_gdalinfo_lib_nodatavalues(): ############################################################################### -def test_gdalinfo_lib_coordinate_epoch(): +@pytest.mark.parametrize("epoch", ["2021.0", "2021.3"]) +def test_gdalinfo_lib_coordinate_epoch(epoch): ds = gdal.Translate( - "", "../gcore/data/byte.tif", options='-of MEM -a_coord_epoch 2021.3"' + "", "../gcore/data/byte.tif", options=f'-of MEM -a_coord_epoch {epoch}"' ) ret = gdal.Info(ds) - assert "Coordinate epoch: 2021.3" in ret + assert f"Coordinate epoch: {epoch}" in ret ret = gdal.Info(ds, format="json") assert "coordinateEpoch" in ret - assert ret["coordinateEpoch"] == 2021.3 + assert ret["coordinateEpoch"] == float(epoch) ############################################################################### diff --git a/autotest/utilities/test_ogrinfo_lib.py b/autotest/utilities/test_ogrinfo_lib.py index 055e69ed2c40..876f75e935ff 100755 --- a/autotest/utilities/test_ogrinfo_lib.py +++ b/autotest/utilities/test_ogrinfo_lib.py @@ -33,7 +33,7 @@ import gdaltest import pytest -from osgeo import gdal, ogr +from osgeo import gdal, ogr, osr ############################################################################### # Simple test @@ -637,3 +637,24 @@ def test_ogrinfo_lib_layers(): with pytest.raises(Exception, match="Couldn't fetch requested layer"): gdal.VectorInfo(ds, format="json", layers=["invalid"]) + + +############################################################################### + + +@pytest.mark.parametrize("epoch", ["2021.0", "2021.3"]) +def test_ogrinfo_lib_coordinate_epoch(epoch): + + ds = gdal.GetDriverByName("Memory").Create("dummy", 0, 0, 0, gdal.GDT_Unknown) + srs = osr.SpatialReference() + srs.ImportFromEPSG(4326) + srs.SetCoordinateEpoch(float(epoch)) + ds.CreateLayer("foo", srs=srs) + + ret = gdal.VectorInfo(ds) + assert f"Coordinate epoch: {epoch}" in ret + + j = gdal.VectorInfo(ds, format="json") + crs = j["layers"][0]["geometryFields"][0]["coordinateSystem"] + assert "coordinateEpoch" in crs + assert crs["coordinateEpoch"] == float(epoch) From 0b1f77f15f833365009f5069c46571db8e59e0c6 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 01:03:01 +0200 Subject: [PATCH 0070/1119] Update rfc36_open_by_drivername.rst as withdrawn --- doc/source/development/rfc/rfc36_open_by_drivername.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/source/development/rfc/rfc36_open_by_drivername.rst b/doc/source/development/rfc/rfc36_open_by_drivername.rst index 29e2601df494..3ba29ba62fce 100644 --- a/doc/source/development/rfc/rfc36_open_by_drivername.rst +++ b/doc/source/development/rfc/rfc36_open_by_drivername.rst @@ -1,14 +1,16 @@ .. _rfc-36: ================================================================================ -RFC 36: Allow specification of intended driver on GDALOpen +RFC 36: Allow specification of intended driver on GDALOpen (withdrawn) ================================================================================ Authors: Ivan Lucena Contact: ivan.lucena@pmldnet.com -Status: Proposed +Status: Withdrawn. + +Covered by `RFC 46: GDAL/OGR unification <./rfc46_gdal_ogr_unification>`__ Summary ------- From 82795f7f16822e6d429f25c4db9c14429390a002 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 31 May 2024 16:09:14 +0200 Subject: [PATCH 0071/1119] gdal_create: allow specifying -burn before output dataset (master only) Specifying nargs(nargs_pattern::at_least_one) on a non-positional argument seems to confuse argparser, which no longer accepted things like "gdal_create -outsize 1 1 -burn 10 out.tif" Hence to accept all forms of -burn, pre-process it manually. --- apps/gdal_create.cpp | 86 ++++++++++++++++++-------- autotest/utilities/test_gdal_create.py | 4 +- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/apps/gdal_create.cpp b/apps/gdal_create.cpp index 4984a0956579..9c35a7b6ed7a 100644 --- a/apps/gdal_create.cpp +++ b/apps/gdal_create.cpp @@ -123,30 +123,9 @@ GDALCreateAppOptionsGetParser(GDALCreateOptions *psOptions) .store_into(psOptions->nBandCount) .help(_("Set the number of bands in the output file.")); - argParser->add_argument("-burn") - .metavar("") - .nargs(nargs_pattern::at_least_one) - .append() - .action( - [psOptions](const std::string &s) - { - if (s.find(' ') != std::string::npos) - { - const CPLStringList aosTokens(CSLTokenizeString(s.c_str())); - for (int i = 0; i < aosTokens.size(); i++) - { - psOptions->adfBurnValues.push_back( - CPLAtof(aosTokens[i])); - } - } - else - { - psOptions->adfBurnValues.push_back(CPLAtof(s.c_str())); - } - }) - .help( - _("A fixed value to burn into a band for all objects. A list of " - "-burn options can be supplied, one per band being written to.")); + argParser->add_argument("-burn").metavar("").append().help( + _("A fixed value to burn into a band. A list of " + "-burn options can be supplied, one per band being written to.")); argParser->add_argument("-a_srs") .metavar("") @@ -253,16 +232,69 @@ MAIN_START(argc, argv) GDALCreateOptions sOptions; + CPLStringList aosArgv; + for (int iArg = 1; iArg < argc; iArg++) + { + if (iArg + 1 < argc && EQUAL(argv[iArg], "-burn")) + { + ++iArg; + while (true) + { + if (strchr(argv[iArg], ' ')) + { + const CPLStringList aosTokens( + CSLTokenizeString(argv[iArg])); + for (int i = 0; i < aosTokens.size(); i++) + { + char *endptr = nullptr; + sOptions.adfBurnValues.push_back( + CPLStrtodM(aosTokens[i], &endptr)); + if (endptr != aosTokens[i] + strlen(aosTokens[i])) + { + fprintf(stderr, "Invalid value for -burn\n"); + CSLDestroy(argv); + GDALExit(1); + } + } + } + else + { + char *endptr = nullptr; + sOptions.adfBurnValues.push_back( + CPLStrtodM(argv[iArg], &endptr)); + if (endptr != argv[iArg] + strlen(argv[iArg])) + { + fprintf(stderr, "Invalid value for -burn\n"); + CSLDestroy(argv); + GDALExit(1); + } + } + if (iArg + 1 < argc && + CPLGetValueType(argv[iArg + 1]) != CPL_VALUE_STRING) + { + ++iArg; + } + else + { + break; + } + } + } + else + { + aosArgv.AddString(argv[iArg]); + } + } + CSLDestroy(argv); + try { auto argParser = GDALCreateAppOptionsGetParser(&sOptions); - argParser->parse_args_without_binary_name(argv + 1); - CSLDestroy(argv); + argParser->parse_args_without_binary_name(aosArgv.List()); } catch (const std::exception &error) { - CSLDestroy(argv); CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what()); GDALExit(1); } diff --git a/autotest/utilities/test_gdal_create.py b/autotest/utilities/test_gdal_create.py index dc85453edc8e..13c26b7fb58d 100755 --- a/autotest/utilities/test_gdal_create.py +++ b/autotest/utilities/test_gdal_create.py @@ -55,14 +55,14 @@ def gdal_create_path(): ############################################################################### -@pytest.mark.parametrize("burn", ("-burn 1 2", '-burn "1 2"', "-burn 1 -burn 2")) +@pytest.mark.parametrize("burn", ("-burn 1.1 2", '-burn "1 2"', "-burn 1 -burn 2")) def test_gdal_create_pdf_tif(gdal_create_path, tmp_path, burn): output_tif = str(tmp_path / "tmp.tif") (_, err) = gdaltest.runexternal_out_and_err( gdal_create_path - + f" {output_tif} -bands 3 -outsize 1 2 -a_srs EPSG:4326 -a_ullr 2 50 3 49 -a_nodata 5 {burn} -ot UInt16 -co COMPRESS=DEFLATE -mo FOO=BAR" + + f" -bands 3 -outsize 1 2 -a_srs EPSG:4326 -a_ullr 2 50 3 49 -a_nodata 5 {burn} -ot UInt16 -co COMPRESS=DEFLATE -mo FOO=BAR {output_tif}" ) assert err is None or err == "", "got error/warning" From 399a0f55d20755120fa49dec36054d2fa8531fbc Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 19:46:49 +0200 Subject: [PATCH 0072/1119] argparse/argparse.hpp: restore lost changes --- apps/argparse/README.TXT | 2 ++ apps/argparse/argparse.hpp | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/argparse/README.TXT b/apps/argparse/README.TXT index d2f296a7f1c8..5febab7c4667 100644 --- a/apps/argparse/README.TXT +++ b/apps/argparse/README.TXT @@ -1,4 +1,6 @@ Provenance: https://github.com/p-ranav/argparse Retrieved: https://github.com/p-ranav/argparse/blob/8dead89026466b3818e9c6b6b1d938600db39d8f/include/argparse/argparse.hpp ++ patch of https://github.com/p-ranav/argparse/pull/356 (FEATURE: multiple actions) ++ patch of https://github.com/p-ranav/argparse/pull/363 (Accept integer literals in store_into) diff --git a/apps/argparse/argparse.hpp b/apps/argparse/argparse.hpp index 0ff318b74615..a29c120e00ab 100644 --- a/apps/argparse/argparse.hpp +++ b/apps/argparse/argparse.hpp @@ -702,7 +702,14 @@ class Argument { template ::value>::type * = nullptr> auto &store_into(T &var) { if (m_default_value.has_value()) { - var = std::any_cast(m_default_value); + try + { + var = std::any_cast(m_default_value); + } + catch (...) + { + var = static_cast(std::any_cast(m_default_value)); + } } action([&var](const auto &s) { var = details::parse_number()(s); @@ -712,7 +719,14 @@ class Argument { auto &store_into(double &var) { if (m_default_value.has_value()) { - var = std::any_cast(m_default_value); + try + { + var = std::any_cast(m_default_value); + } + catch (...) + { + var = std::any_cast(m_default_value); + } } action([&var](const auto &s) { var = details::parse_number()(s); From 68451b3a4d5b14ce4ab649d4982a44cd05fc3969 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 19:52:02 +0200 Subject: [PATCH 0073/1119] Doc: move build requirements to building_from_source, and put it in first place --- .../development/building_from_source.rst | 23 +++++++++++++++++++ doc/source/development/dev_environment.rst | 22 ++---------------- doc/source/development/index.rst | 2 +- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/doc/source/development/building_from_source.rst b/doc/source/development/building_from_source.rst index 26ce35a9c598..b842a04bd721 100644 --- a/doc/source/development/building_from_source.rst +++ b/doc/source/development/building_from_source.rst @@ -4,6 +4,29 @@ Building GDAL from source ================================================================================ +.. _build_requirements: + +Build requirements +-------------------------------------------------------------------------------- + +The minimum requirements to build GDAL are: + +- CMake >= 3.16, and an associated build system (make, ninja, Visual Studio, etc.) +- C99 compiler +- C++17 compiler since GDAL 3.9 (C++11 in previous versions) +- PROJ >= 6.3.1 + +Additional requirements to run the GDAL test suite are: + +- SWIG >= 4, for building bindings to other programming languages +- Python >= 3.8 +- Python packages listed in `autotest/requirements.txt` + +A number of optional libraries are also strongly recommended for most builds: +SQLite3, expat, libcurl, zlib, libtiff, libgeotiff, libpng, libjpeg, etc. +Consult :ref:`raster_drivers` and :ref:`vector_drivers` pages for information +on dependencies of optional drivers. + CMake (GDAL versions >= 3.5.0) -------------------------------------------------------------------------------- diff --git a/doc/source/development/dev_environment.rst b/doc/source/development/dev_environment.rst index b47b934f08b8..e074684d53e3 100644 --- a/doc/source/development/dev_environment.rst +++ b/doc/source/development/dev_environment.rst @@ -4,28 +4,10 @@ Setting up a development environment ================================================================================ -.. _build_requirements: - Build requirements --------------------------------------------------------------------------------- - -The minimum requirements to build GDAL are: - -- CMake >= 3.16, and an associated build system (make, ninja, Visual Studio, etc.) -- C99 compiler -- C++17 compiler since GDAL 3.9 (C++11 in previous versions) -- PROJ >= 6.3.1 - -Additional requirements to run the GDAL test suite are: - -- SWIG >= 4, for building bindings to other programming languages -- Python >= 3.8 -- Python packages listed in `autotest/requirements.txt` +------------------ -A number of optional libraries are also strongly recommended for most builds: -SQLite3, expat, libcurl, zlib, libtiff, libgeotiff, libpng, libjpeg, etc. -Consult :ref:`raster_drivers` and :ref:`vector_drivers` pages for information -on dependencies of optional drivers. +See :ref:`build_requirements` Vagrant ------- diff --git a/doc/source/development/index.rst b/doc/source/development/index.rst index c369d385d947..59902fdbcfe5 100644 --- a/doc/source/development/index.rst +++ b/doc/source/development/index.rst @@ -8,8 +8,8 @@ Development .. toctree:: :maxdepth: 1 - dev_environment building_from_source + dev_environment dev_practices testing dev_documentation From 5a9ef441292b5a92ab58fe54a9fcdab5b1e58bdd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 23:01:46 +0200 Subject: [PATCH 0074/1119] CI: workaround issue with too old vcruntime140.dll on image windows-latest 20240603.1.0 Cf https://github.com/actions/runner-images/issues/10004 Other approach attempted in https://github.com/rouault/gdal/commit/9ff56b3749ef28b8a2c06fefc62b4ecfda5d5090 didn't result in finding a recent enough vcruntime140.dll --- .github/workflows/cmake_builds.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index c83846e711fe..be406c3b5fe5 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -454,9 +454,10 @@ jobs: # otherwise interpret /bla has a file relative to the Bash root directory and would replace it by a path like c:\Program Files\git\WX # BUILD_JAVA_BINDINGS=OFF because we get "Error occurred during initialization of VM. Corrupted ZIP library: C:\Miniconda\envs\gdalenv\Library\bin\zip.dll" when running java. Not reproducible on a standard VM # Build PDF driver as plugin due to the PDFium build including libopenjp2 symbols which would conflict with external libopenjp2 + # /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR to workaround issue with too old vcruntime140.dll on image 20240603.1.0 (https://github.com/actions/runner-images/issues/10004) run: | mkdir -p $GITHUB_WORKSPACE/build - cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Release -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON + cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Release -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config Release -j 2 @@ -526,9 +527,10 @@ jobs: cmake --version - name: Configure shell: bash -l {0} + # /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR to workaround issue with too old vcruntime140.dll on image 20240603.1.0 (https://github.com/actions/runner-images/issues/10004) run: | mkdir -p $GITHUB_WORKSPACE/build - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 @@ -536,12 +538,12 @@ jobs: shell: bash -l {0} run: | rm -f build/CMakeCache.txt - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Configure with even less dependencies, and disabling all optional drivers shell: bash -l {0} run: | rm -f build/CMakeCache.txt - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF -DOGR_BUILD_OPTIONAL_DRIVERS=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF -DOGR_BUILD_OPTIONAL_DRIVERS=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 From bf22b8c1dcb80e67576c9c52fc863bb58d05cac3 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 23:44:58 +0200 Subject: [PATCH 0075/1119] building_from_source.rst: fix link --- doc/source/development/building_from_source.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/development/building_from_source.rst b/doc/source/development/building_from_source.rst index b842a04bd721..26bd2a714a12 100644 --- a/doc/source/development/building_from_source.rst +++ b/doc/source/development/building_from_source.rst @@ -2508,7 +2508,7 @@ How do I get PROJ ? PROJ is the only required build-time dependency of GDAL that is not vendorized in the GDAL source code tree. Consequently, the PROJ header and library must be available -when configuring GDAL's CMake. Consult `PROJ installation `. +when configuring GDAL's CMake. Consult `PROJ installation `__. Conflicting PROJ libraries ************************** From e677cbb3418999a878703e14c3f68d858835b2a5 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Fri, 7 Jun 2024 14:39:01 +0200 Subject: [PATCH 0076/1119] [ogrlineref] Use GDALArgumentParser (#10147) --- apps/ogrlineref.cpp | 1394 ++++++++++++++++++++++--------------------- 1 file changed, 705 insertions(+), 689 deletions(-) diff --git a/apps/ogrlineref.cpp b/apps/ogrlineref.cpp index a150cc82d16e..630eca9f4f66 100644 --- a/apps/ogrlineref.cpp +++ b/apps/ogrlineref.cpp @@ -37,6 +37,7 @@ #include "ogr_api.h" #include "ogr_p.h" #include "ogrsf_frmts.h" +#include "gdalargumentparser.h" #include #include @@ -72,86 +73,6 @@ typedef struct _curve_data } } CURVE_DATA; -/************************************************************************/ -/* Usage() */ -/************************************************************************/ -static void Usage(bool bIsError, const char *pszAdditionalMsg = nullptr, - bool bShort = true) CPL_NO_RETURN; - -static void Usage(bool bIsError, const char *pszAdditionalMsg, bool bShort) -{ - OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar(); - - fprintf( - bIsError ? stderr : stdout, - "Usage: ogrlineref [--help] [--help-general] [-progress] [-quiet]\n" - " [-f ] [-dsco =]... [-lco " - "=]...\n" - " [-create]\n" - " [-l ] [-ln ] " - "[-lf " - "]\n" - " [-p ] [-pn ] " - "[-pm ] [-pf ]\n" - " [-r ] [-rn ]\n" - " [-o ] [-on ] [-of " - "] [-s ]\n" - " [-get_pos] [-x ] [-y ]\n" - " [-get_coord] [-m ] \n" - " [-get_subline] [-mb ] [-me ]\n"); - - if (bShort) - { - fprintf(bIsError ? stderr : stdout, - "\nNote: ogrlineref --long-usage for full help.\n"); - if (pszAdditionalMsg) - fprintf(stderr, "\nFAILURE: %s\n", pszAdditionalMsg); - exit(1); - } - - fprintf( - bIsError ? stderr : stdout, - "\n -f format_name: output file format name, possible values are:\n"); - - for (int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++) - { - GDALDriver *poDriver = poR->GetDriver(iDriver); - - if (CPLTestBool(CSLFetchNameValueDef(poDriver->GetMetadata(), - GDAL_DCAP_CREATE, "FALSE"))) - fprintf(bIsError ? stderr : stdout, " -f \"%s\"\n", - poDriver->GetDescription()); - } - - fprintf(bIsError ? stderr : stdout, - " -progress: Display progress on terminal. Only works if input " - "layers have the \n" - " \"fast feature count\" " - "capability\n" - " -dsco NAME=VALUE: Dataset creation option (format specific)\n" - " -lco NAME=VALUE: Layer creation option (format specific)\n" - " -l src_line_datasource_name: Datasource of line path name\n" - " -ln layer_name: Layer name in datasource (optional)\n" - " -lf field_name: Field name for unique paths in layer (optional)\n" - " -p src_repers_datasource_name: Datasource of repers name\n" - " -pn layer_name: Layer name in datasource (optional)\n" - " -pm pos_field_name: Line position field name\n" - " -pf field_name: Field name for correspondence repers of separate " - "paths in layer (optional)\n" - " -r src_parts_datasource_name: Parts datasource name\n" - " -rn layer_name: Layer name in datasource (optional)\n" - " -o dst_datasource_name: Parts datasource name\n" - " -on layer_name: Layer name in datasource (optional)\n" - " -of field_name: Field name for correspondence parts of separate " - "paths in layer (optional)\n" - " -s step: part size in m\n"); - - if (pszAdditionalMsg) - fprintf(stderr, "\nFAILURE: %s\n", pszAdditionalMsg); - - exit(bIsError ? 1 : 0); -} - /************************************************************************/ /* SetupTargetLayer() */ /************************************************************************/ @@ -212,8 +133,8 @@ static OGRLayer *SetupTargetLayer(OGRLayer *poSrcLayer, GDALDataset *poDstDS, if (!poDstDS->TestCapability(ODsCCreateLayer)) { fprintf(stderr, - "Layer %s not found, and " - "CreateLayer not supported by driver.\n", + _("Layer %s not found, and " + "CreateLayer not supported by driver.\n"), szLayerName.c_str()); return nullptr; } @@ -249,7 +170,7 @@ static OGRLayer *SetupTargetLayer(OGRLayer *poSrcLayer, GDALDataset *poDstDS, /* -------------------------------------------------------------------- */ else { - fprintf(stderr, "FAILED: Layer %s already exists.\n", + fprintf(stderr, _("FAILED: Layer %s already exists.\n"), szLayerName.c_str()); return nullptr; } @@ -355,10 +276,10 @@ static void CheckDestDataSourceNameConsistency(const char *pszDestFilename, !EQUAL(pszDriverName, apszExtensions[i][1])) { fprintf(stderr, - "Warning: The target file has a '%s' extension, " - "which is normally used by the %s driver,\n" - "but the requested output driver is %s. " - "Is it really what you want ?\n", + _("Warning: The target file has a '%s' extension, " + "which is normally used by the %s driver,\n" + "but the requested output driver is %s. " + "Is it really what you want ?\n"), pszDestExtension, apszExtensions[i][1], pszDriverName); break; } @@ -371,10 +292,10 @@ static void CheckDestDataSourceNameConsistency(const char *pszDestFilename, !EQUAL(pszDriverName, apszBeginName[i][1])) { fprintf(stderr, - "Warning: The target file has a name which is normally " - "recognized by the %s driver,\n" - "but the requested output driver is %s. " - "Is it really what you want ?\n", + _("Warning: The target file has a name which is normally " + "recognized by the %s driver,\n" + "but the requested output driver is %s. " + "Is it really what you want ?\n"), apszBeginName[i][1], pszDriverName); break; } @@ -439,7 +360,7 @@ static OGRErr CreateSubline(OGRLayer *const poPkLayer, double dfPosBeg, } else { - fprintf(stderr, "Get step for positions %f - %f failed\n", dfPosBeg, + fprintf(stderr, _("Get step for positions %f - %f failed\n"), dfPosBeg, dfPosEnd); return OGRERR_FAILURE; } @@ -455,7 +376,7 @@ static OGRErr CreateSubline(OGRLayer *const poPkLayer, double dfPosBeg, } else { - fprintf(stderr, "Get step for positions %f - %f failed\n", dfPosBeg, + fprintf(stderr, _("Get step for positions %f - %f failed\n"), dfPosBeg, dfPosEnd); return OGRERR_FAILURE; } @@ -482,7 +403,7 @@ static OGRErr CreateSubline(OGRLayer *const poPkLayer, double dfPosBeg, if (moParts.empty()) { - fprintf(stderr, "Get parts for positions %f - %f failed\n", dfPosBeg, + fprintf(stderr, _("Get parts for positions %f - %f failed\n"), dfPosBeg, dfPosEnd); return OGRERR_FAILURE; } @@ -593,7 +514,7 @@ static OGRErr CreatePartsFromLineString( OGRwkbGeometryType eGeomType = poPkLayer->GetGeomType(); if (wkbFlatten(eGeomType) != wkbPoint) { - fprintf(stderr, "Unsupported geometry type %s for path\n", + fprintf(stderr, _("Unsupported geometry type %s for path\n"), OGRGeometryTypeToName(eGeomType)); return OGRERR_FAILURE; } @@ -655,14 +576,14 @@ static OGRErr CreatePartsFromLineString( if (moRepers.size() < 2) { - fprintf(stderr, "Not enough repers to proceed\n"); + fprintf(stderr, _("Not enough repers to proceed.\n")); return OGRERR_FAILURE; } // Check direction. if (!bQuiet) { - fprintf(stdout, "Check path direction\n"); + fprintf(stdout, "Check path direction.\n"); } // Get distance along path from pt1 and pt2. @@ -683,8 +604,8 @@ static OGRErr CreatePartsFromLineString( if (!bQuiet) { fprintf(stderr, - "Warning: The path is opposite the repers direction. " - "Let's reverse path\n"); + _("Warning: The path is opposite the repers direction. " + "Let's reverse path.\n")); } pPathGeom->reversePoints(); @@ -891,7 +812,7 @@ static OGRErr CreatePartsFromLineString( // Create pickets if (!bQuiet) { - fprintf(stdout, "\nCreate pickets\n"); + fprintf(stdout, "\nCreate pickets.\n"); } const double dfRoundBeg = pPtBeg != nullptr @@ -947,7 +868,7 @@ static OGRErr CreatePartsFromLineString( if (!bQuiet) { - fprintf(stdout, "\nCreate sublines\n"); + fprintf(stdout, "\nCreate sublines.\n"); } IT = moRepers.begin(); @@ -1021,7 +942,7 @@ static OGRErr CreateParts(OGRLayer *const poLnLayer, OGRLayer *const poPkLayer, if (wkbFlatten(eGeomType) != wkbLineString && wkbFlatten(eGeomType) != wkbMultiLineString) { - fprintf(stderr, "Unsupported geometry type %s for path\n", + fprintf(stderr, _("Unsupported geometry type %s for path.\n"), OGRGeometryTypeToName(eGeomType)); return eRetCode; } @@ -1040,8 +961,8 @@ static OGRErr CreateParts(OGRLayer *const poLnLayer, OGRLayer *const poPkLayer, if (!bQuiet) { fprintf(stdout, - "\nThe geometry " CPL_FRMT_GIB - " is wkbMultiLineString type\n", + _("\nThe geometry " CPL_FRMT_GIB + " is wkbMultiLineString type.\n"), pPathFeature->GetFID()); } @@ -1098,7 +1019,8 @@ static OGRErr CreatePartsMultiple( const int nLineSepFieldInd = pDefn->GetFieldIndex(pszLineSepFieldName); if (nLineSepFieldInd == -1) { - fprintf(stderr, "The field %s not found\n", pszLineSepFieldName); + fprintf(stderr, _("The field was %s not found.\n"), + pszLineSepFieldName); return OGRERR_FAILURE; } @@ -1180,7 +1102,7 @@ static OGRErr GetPosition(OGRLayer *const poPkLayer, double dfX, double dfY, if (nullptr == pCloserPart) { - fprintf(stderr, "Filed to find closest part\n"); + fprintf(stderr, _("Failed to find closest part.\n")); return OGRERR_FAILURE; } // Now we have closest part @@ -1195,10 +1117,10 @@ static OGRErr GetPosition(OGRLayer *const poPkLayer, double dfX, double dfY, } else { - fprintf( - stdout, "%s", - CPLSPrintf("The position for coordinates lat:%f, long:%f is %f\n", - dfY, dfX, dfRefDist)); + fprintf(stdout, "%s", + CPLSPrintf( + _("The position for coordinates lat:%f, long:%f is %f\n"), + dfY, dfX, dfRefDist)); } return OGRERR_NONE; @@ -1240,8 +1162,8 @@ static OGRErr GetCoordinates(OGRLayer *const poPkLayer, double dfPos, else { fprintf(stdout, "%s", - CPLSPrintf("The position for distance %f is lat:%f, " - "long:%f, height:%f\n", + CPLSPrintf(_("The position for distance %f is lat:%f, " + "long:%f, height:%f\n"), dfPos, pt.getY(), pt.getX(), pt.getZ())); } } @@ -1252,716 +1174,810 @@ static OGRErr GetCoordinates(OGRLayer *const poPkLayer, double dfPos, } else { - fprintf(stderr, "Get coordinates for position %f failed\n", dfPos); + fprintf(stderr, _("Get coordinates for position %f failed.\n"), dfPos); return OGRERR_FAILURE; } } /************************************************************************/ -/* main() */ +/* OGRLineRefOptions */ /************************************************************************/ -#define CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(nExtraArg) \ - do \ - { \ - if (iArg + nExtraArg >= nArgc) \ - Usage(true, CPLSPrintf("%s option requires %d argument(s)", \ - papszArgv[iArg], nExtraArg)); \ - } while (false) - -MAIN_START(nArgc, papszArgv) - +struct OGRLineRefOptions { - OGRErr eErr = OGRERR_NONE; bool bQuiet = false; - const char *pszFormat = "ESRI Shapefile"; - - const char *pszOutputDataSource = nullptr; - const char *pszLineDataSource = nullptr; - const char *pszPicketsDataSource = nullptr; - const char *pszPartsDataSource = nullptr; - char *pszOutputLayerName = nullptr; - const char *pszLineLayerName = nullptr; + bool bDisplayProgress = false; + std::string osFormat = "ESRI Shapefile"; + + std::string osSrcLineDataSourceName; + std::string osSrcLineLayerName; #ifdef HAVE_GEOS - const char *pszPicketsLayerName = nullptr; - const char *pszPicketsMField = nullptr; + std::string osSrcLineSepFieldName; #endif - const char *pszPartsLayerName = nullptr; + std::string osSrcPicketsDataSourceName; #ifdef HAVE_GEOS - const char *pszLineSepFieldName = nullptr; - const char *pszPicketsSepFieldName = nullptr; - const char *pszOutputSepFieldName = "uniq_uid"; + std::string osSrcPicketsLayerName; + std::string osSrcPicketsSepFieldName; + std::string osSrcPicketsMFieldName; #endif - char **papszDSCO = nullptr; - char **papszLCO = nullptr; + std::string osSrcPartsDataSourceName; + std::string osSrcPartsLayerName; - operation stOper = op_unknown; #ifdef HAVE_GEOS - double dfX = -100000000.0; - double dfY = -100000000.0; + std::string osOutputSepFieldName = "uniq_uid"; #endif - double dfPos = -100000000.0; + std::string osOutputDataSourceName; + std::string osOutputLayerName; + + CPLStringList aosDSCO; + CPLStringList aosLCO; - int bDisplayProgress = FALSE; + // Operations + bool bCreate = false; + bool bGetPos = false; + bool bGetSubLine = false; + bool bGetCoord = false; - double dfPosBeg = -100000000.0; - double dfPosEnd = -100000000.0; #ifdef HAVE_GEOS - double dfStep = -100000000.0; + double dfXPos = std::numeric_limits::quiet_NaN(); + double dfYPos = std::numeric_limits::quiet_NaN(); + double dfStep = std::numeric_limits::quiet_NaN(); #endif + double dfPosBeg = std::numeric_limits::quiet_NaN(); + double dfPosEnd = std::numeric_limits::quiet_NaN(); + double dfPos = std::numeric_limits::quiet_NaN(); +}; - // Check strict compilation and runtime library version as we use C++ API. - if (!GDAL_CHECK_VERSION(papszArgv[0])) - exit(1); +/************************************************************************/ +/* OGRLineRefGetParser */ +/************************************************************************/ - EarlySetConfigOptions(nArgc, papszArgv); +static std::unique_ptr +OGRLineRefAppOptionsGetParser(OGRLineRefOptions *psOptions) +{ + auto argParser = std::make_unique( + "ogrlineref", /* bForBinary */ true); - OGRRegisterAll(); + argParser->add_description( + _("Create linear reference and provide some calculations using it.")); - /* -------------------------------------------------------------------- */ - /* Processing command line arguments. */ - /* -------------------------------------------------------------------- */ - nArgc = OGRGeneralCmdLineProcessor(nArgc, &papszArgv, 0); + argParser->add_epilog(_("For more details, consult the full documentation " + "for the ogrlineref utility " + "https://gdal.org/programs/ogrlineref.html")); - if (nArgc < 1) - exit(-nArgc); + auto &quietArg{argParser->add_quiet_argument(&psOptions->bQuiet)}; + argParser->add_hidden_alias_for(quietArg, "-quiet"); - for (int iArg = 1; iArg < nArgc; iArg++) - { - if (EQUAL(papszArgv[iArg], "--utility_version")) - { - printf("%s was compiled against GDAL %s and " - "is running against GDAL %s\n", - papszArgv[0], GDAL_RELEASE_NAME, - GDALVersionInfo("RELEASE_NAME")); - CSLDestroy(papszArgv); - return 0; - } - else if (EQUAL(papszArgv[iArg], "--help")) - { - Usage(false); - } - else if (EQUAL(papszArgv[iArg], "--long-usage")) - { - Usage(false, nullptr, false); - } + argParser->add_argument("-progress") + .flag() + .store_into(psOptions->bDisplayProgress) + .help(_("Display progress.")); + + argParser->add_output_format_argument(psOptions->osFormat); + + argParser->add_dataset_creation_options_argument(psOptions->aosDSCO); + + argParser->add_layer_creation_options_argument(psOptions->aosLCO); - else if (EQUAL(papszArgv[iArg], "-q") || - EQUAL(papszArgv[iArg], "-quiet")) - { - bQuiet = true; - } - else if ((EQUAL(papszArgv[iArg], "-f") || - EQUAL(papszArgv[iArg], "-of"))) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // bFormatExplicitlySet = TRUE; - // coverity[tainted_data] - pszFormat = papszArgv[++iArg]; - } - else if (EQUAL(papszArgv[iArg], "-dsco")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - papszDSCO = CSLAddString(papszDSCO, papszArgv[++iArg]); - } - else if (EQUAL(papszArgv[iArg], "-lco")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - papszLCO = CSLAddString(papszLCO, papszArgv[++iArg]); - } - else if (EQUAL(papszArgv[iArg], "-create")) - { - stOper = op_create; - } - else if (EQUAL(papszArgv[iArg], "-get_pos")) - { - stOper = op_get_pos; - } - else if (EQUAL(papszArgv[iArg], "-get_coord")) - { - stOper = op_get_coord; - } - else if (EQUAL(papszArgv[iArg], "-get_subline")) - { - stOper = op_get_subline; - } - else if (EQUAL(papszArgv[iArg], "-l")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - pszLineDataSource = papszArgv[++iArg]; - } - else if (EQUAL(papszArgv[iArg], "-ln")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - pszLineLayerName = papszArgv[++iArg]; - } - else if (EQUAL(papszArgv[iArg], "-lf")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); #ifdef HAVE_GEOS - // coverity[tainted_data] - pszLineSepFieldName = papszArgv[++iArg]; -#else - fprintf(stderr, - "GEOS support not enabled or incompatible version.\n"); - exit(1); + argParser->add_argument("-create") + .flag() + .store_into(psOptions->bCreate) + .help(_("Create the linear reference file (linestring of parts).")); #endif - } - else if (EQUAL(papszArgv[iArg], "-p")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - pszPicketsDataSource = papszArgv[++iArg]; - } - else if (EQUAL(papszArgv[iArg], "-pn")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); + + argParser->add_argument("-l") + .metavar("") + .store_into(psOptions->osSrcLineDataSourceName) + .help(_("Name of the line path datasource.")); + + argParser->add_argument("-ln") + .metavar("") + .store_into(psOptions->osSrcLineLayerName) + .help(_("Layer name in the line path datasource.")); + #ifdef HAVE_GEOS - // coverity[tainted_data] - pszPicketsLayerName = papszArgv[++iArg]; -#else - fprintf(stderr, - "GEOS support not enabled or incompatible version.\n"); - exit(1); + + argParser->add_argument("-lf") + .metavar("") + .store_into(psOptions->osSrcLineSepFieldName) + .help(_("Field name for unique paths in layer.")); #endif - } - else if (EQUAL(papszArgv[iArg], "-pm")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); + + argParser->add_argument("-p") + .metavar("") + .store_into(psOptions->osSrcPicketsDataSourceName) + .help(_("Datasource of repers name.")); + #ifdef HAVE_GEOS - // coverity[tainted_data] - pszPicketsMField = papszArgv[++iArg]; -#else - fprintf(stderr, - "GEOS support not enabled or incompatible version.\n"); - exit(1); + argParser->add_argument("-pn") + .metavar("") + .store_into(psOptions->osSrcPicketsLayerName) + .help(_("Layer name in repers datasource.")); + + argParser->add_argument("-pm") + .metavar("") + .store_into(psOptions->osSrcPicketsMFieldName) + .help(_("Line position field name.")); + + argParser->add_argument("-pf") + .metavar("") + .store_into(psOptions->osSrcPicketsSepFieldName) + .help(_("Field name of unique values to map input reference points " + "to lines.")); #endif - } - else if (EQUAL(papszArgv[iArg], "-pf")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); + + argParser->add_argument("-r") + .metavar("") + .store_into(psOptions->osSrcPartsDataSourceName) + .help(_("Path to linear reference file.")); + + argParser->add_argument("-rn") + .metavar("") + .store_into(psOptions->osSrcPartsLayerName) + .help(_("Name of the layer in the input linear reference datasource.")); + + argParser->add_argument("-o") + .metavar("") + .store_into(psOptions->osOutputDataSourceName) + .help(_("Path to output linear reference file (linestring " + "datasource).")); + + argParser->add_argument("-on") + .metavar("") + .store_into(psOptions->osOutputLayerName) + .help(_("Name of the layer in the output linear reference " + "datasource.")); + #ifdef HAVE_GEOS - // coverity[tainted_data] - pszPicketsSepFieldName = papszArgv[++iArg]; -#else - fprintf(stderr, - "GEOS support not enabled or incompatible version.\n"); - exit(1); + argParser->add_argument("-of") + .metavar("") + .store_into(psOptions->osOutputSepFieldName) + .help(_( + "Name of the field for storing the unique values of input lines.")); + + argParser->add_argument("-s") + .metavar("") + .scan<'g', double>() + .store_into(psOptions->dfStep) + .help(_("Part size in linear units.")); + + argParser->add_argument("-get_pos") + .flag() + .store_into(psOptions->bGetPos) + .help(_("Get the position for the given coordinates.")); + + argParser->add_argument("-x") + .metavar("") + .scan<'g', double>() + .store_into(psOptions->dfXPos) + .help(_("X coordinate.")); + + argParser->add_argument("-y") + .metavar("") + .scan<'g', double>() + .store_into(psOptions->dfYPos) + .help(_("Y coordinate.")); #endif - } - else if (EQUAL(papszArgv[iArg], "-r")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - pszPartsDataSource = papszArgv[++iArg]; - } - else if (EQUAL(papszArgv[iArg], "-rn")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - pszPartsLayerName = papszArgv[++iArg]; - } - else if (EQUAL(papszArgv[iArg], "-o")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - pszOutputDataSource = papszArgv[++iArg]; - } - else if (EQUAL(papszArgv[iArg], "-on")) + + argParser->add_argument("-get_coord") + .flag() + .store_into(psOptions->bGetCoord) + .help(_("Return point on path for input linear distance.")); + + argParser->add_argument("-m") + .metavar("") + .scan<'g', double>() + .store_into(psOptions->dfPos) + .help(_("Input linear distance.")); + + argParser->add_argument("-get_subline") + .flag() + .store_into(psOptions->bGetSubLine) + .help(_("Return the portion of the input path from and to input linear " + "positions.")); + + argParser->add_argument("-mb") + .metavar("") + .scan<'g', double>() + .store_into(psOptions->dfPosBeg) + .help(_("Input linear distance begin.")); + + argParser->add_argument("-me") + .metavar("") + .scan<'g', double>() + .store_into(psOptions->dfPosEnd) + .help(_("Input linear distance end.")); + + return argParser; +} + +/************************************************************************/ +/* main() */ +/************************************************************************/ + +MAIN_START(argc, argv) + +{ + OGRErr eErr = OGRERR_NONE; + + operation stOper = op_unknown; + + EarlySetConfigOptions(argc, argv); + + argc = GDALGeneralCmdLineProcessor(argc, &argv, 0); + + if (argc < 2) + { + try { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - pszOutputLayerName = CPLStrdup(papszArgv[++iArg]); + OGRLineRefOptions sOptions; + auto argParser = OGRLineRefAppOptionsGetParser(&sOptions); + fprintf(stderr, "%s\n", argParser->usage().c_str()); } - else if (EQUAL(papszArgv[iArg], "-of")) + catch (const std::exception &err) { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); -#ifdef HAVE_GEOS - // coverity[tainted_data] - pszOutputSepFieldName = papszArgv[++iArg]; -#else - fprintf(stderr, - "GEOS support not enabled or incompatible version.\n"); - exit(1); -#endif + CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s", + err.what()); } - else if (EQUAL(papszArgv[iArg], "-x")) + CSLDestroy(argv); + exit(1); + } + + OGRRegisterAll(); + + OGRLineRefOptions psOptions; + auto argParser = OGRLineRefAppOptionsGetParser(&psOptions); + + try + { + argParser->parse_args_without_binary_name(argv + 1); + CSLDestroy(argv); + } + catch (const std::exception &error) + { + argParser->display_error_and_usage(error); + CSLDestroy(argv); + exit(1); + } + + // Select operation mode + if (psOptions.bCreate) + stOper = op_create; + + if (psOptions.bGetPos) + { + if (stOper != op_unknown) { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); -#ifdef HAVE_GEOS - // coverity[tainted_data] - dfX = CPLAtofM(papszArgv[++iArg]); -#else - fprintf(stderr, - "GEOS support not enabled or incompatible version.\n"); + fprintf(stderr, _("Only one operation can be specified\n")); + argParser->usage(); exit(1); -#endif } - else if (EQUAL(papszArgv[iArg], "-y")) + stOper = op_get_pos; + } + + if (psOptions.bGetCoord) + { + if (stOper != op_unknown) { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); -#ifdef HAVE_GEOS - // coverity[tainted_data] - dfY = CPLAtofM(papszArgv[++iArg]); -#else - fprintf(stderr, - "GEOS support not enabled or incompatible version.\n"); + fprintf(stderr, _("Only one operation can be specified\n")); + argParser->usage(); exit(1); -#endif } - else if (EQUAL(papszArgv[iArg], "-m")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - dfPos = CPLAtofM(papszArgv[++iArg]); - } - else if (EQUAL(papszArgv[iArg], "-mb")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - dfPosBeg = CPLAtofM(papszArgv[++iArg]); - } - else if (EQUAL(papszArgv[iArg], "-me")) - { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); - // coverity[tainted_data] - dfPosEnd = CPLAtofM(papszArgv[++iArg]); - } - else if (EQUAL(papszArgv[iArg], "-s")) + stOper = op_get_coord; + } + + if (psOptions.bGetSubLine) + { + if (stOper != op_unknown) { - CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1); -#ifdef HAVE_GEOS - // coverity[tainted_data] - dfStep = CPLAtofM(papszArgv[++iArg]); -#else - fprintf(stderr, - "GEOS support not enabled or incompatible version.\n"); + fprintf(stderr, _("Only one operation can be specified\n")); + argParser->usage(); exit(1); -#endif - } - else if (EQUAL(papszArgv[iArg], "-progress")) - { - bDisplayProgress = TRUE; - } - else if (papszArgv[iArg][0] == '-') - { - Usage(true, - CPLSPrintf("Unknown option name '%s'", papszArgv[iArg])); } + stOper = op_get_subline; } - if (stOper == op_create) + if (stOper == op_unknown) { -#ifdef HAVE_GEOS - if (pszOutputDataSource == nullptr) - Usage(true, "no output datasource provided"); - else if (pszLineDataSource == nullptr) - Usage(true, "no path datasource provided"); - else if (pszPicketsDataSource == nullptr) - Usage(true, "no repers datasource provided"); - else if (pszPicketsMField == nullptr) - Usage(true, "no position field provided"); - else if (dfStep == -100000000.0) - Usage(true, "no step provided"); - - /* -------------------------------------------------------------------- - */ - /* Open data source. */ - /* -------------------------------------------------------------------- - */ - - GDALDataset *poLnDS = - GDALDataset::FromHandle(OGROpen(pszLineDataSource, FALSE, nullptr)); - - /* -------------------------------------------------------------------- - */ - /* Report failure */ - /* -------------------------------------------------------------------- - */ - if (poLnDS == nullptr) + fprintf(stderr, _("No operation specified\n")); + argParser->usage(); + exit(1); + } + + /* -------------------------------------------------------------------- */ + /* Create linear reference */ + /* -------------------------------------------------------------------- */ + + switch (stOper) + { + case op_create: { - OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar(); +#ifdef HAVE_GEOS + if (psOptions.osOutputDataSourceName.empty()) + { + fprintf(stderr, _("No output datasource provided.\n")); + argParser->usage(); + exit(1); + } + if (psOptions.osSrcLineDataSourceName.empty()) + { + fprintf(stderr, _("No path datasource provided.\n")); + argParser->usage(); + exit(1); + } + if (psOptions.osSrcPicketsMFieldName.empty()) + { + fprintf(stderr, _("No repers position field provided.\n")); + argParser->usage(); + exit(1); + } + if (psOptions.osSrcPicketsDataSourceName.empty()) + { + fprintf(stderr, _("No repers datasource provided.\n")); + argParser->usage(); + exit(1); + } + if (psOptions.dfStep == std::numeric_limits::quiet_NaN()) + { + fprintf(stderr, _("No step provided.\n")); + argParser->usage(); + exit(1); + } - fprintf(stderr, - "FAILURE:\n" - "Unable to open path datasource `%s' with " - "the following drivers.\n", - pszLineDataSource); + /* ------------------------------------------------------------- */ + /* Open data source. */ + /* ------------------------------------------------------------- */ + + GDALDataset *poLnDS = GDALDataset::FromHandle(OGROpen( + psOptions.osSrcLineDataSourceName.c_str(), FALSE, nullptr)); + + /* ------------------------------------------------------------- */ + /* Report failure */ + /* ------------------------------------------------------------- */ + if (poLnDS == nullptr) + { + OGRSFDriverRegistrar *poR = + OGRSFDriverRegistrar::GetRegistrar(); + + fprintf(stderr, + _("FAILURE:\n" + "Unable to open path datasource `%s' with " + "the following drivers.\n"), + psOptions.osSrcLineDataSourceName.c_str()); + + for (int iDriver = 0; iDriver < poR->GetDriverCount(); + iDriver++) + { + fprintf(stderr, " -> %s\n", + poR->GetDriver(iDriver)->GetDescription()); + } + + exit(1); + } + + GDALDataset *poPkDS = GDALDataset::FromHandle(OGROpen( + psOptions.osSrcPicketsDataSourceName.c_str(), FALSE, nullptr)); + + /* --------------------------------------------------------------- */ + /* Report failure */ + /* --------------------------------------------------------------- */ - for (int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++) + if (poPkDS == nullptr) { - fprintf(stderr, " -> %s\n", - poR->GetDriver(iDriver)->GetDescription()); + OGRSFDriverRegistrar *poR = + OGRSFDriverRegistrar::GetRegistrar(); + + fprintf(stderr, + _("FAILURE:\n" + "Unable to open repers datasource `%s' " + "with the following drivers.\n"), + psOptions.osSrcPicketsDataSourceName.c_str()); + + for (int iDriver = 0; iDriver < poR->GetDriverCount(); + iDriver++) + { + fprintf(stderr, " -> %s\n", + poR->GetDriver(iDriver)->GetDescription()); + } + + exit(1); } - exit(1); - } + /* ----------------------------------------------------------------- */ + /* Find the output driver. */ + /* ----------------------------------------------------------------- */ - GDALDataset *poPkDS = GDALDataset::FromHandle( - OGROpen(pszPicketsDataSource, FALSE, nullptr)); - /* -------------------------------------------------------------------- - */ - /* Report failure */ - /* -------------------------------------------------------------------- - */ - if (poPkDS == nullptr) - { - OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar(); + if (!psOptions.bQuiet) + CheckDestDataSourceNameConsistency( + psOptions.osOutputDataSourceName.c_str(), + psOptions.osFormat.c_str()); - fprintf(stderr, - "FAILURE:\n" - "Unable to open repers datasource `%s' " - "with the following drivers.\n", - pszPicketsDataSource); + OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar(); - for (int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++) + GDALDriver *poDriver = + poR->GetDriverByName(psOptions.osFormat.c_str()); + if (poDriver == nullptr) { - fprintf(stderr, " -> %s\n", - poR->GetDriver(iDriver)->GetDescription()); + fprintf(stderr, _("Unable to find driver `%s'.\n"), + psOptions.osFormat.c_str()); + fprintf(stderr, _("The following drivers are available:\n")); + + for (int iDriver = 0; iDriver < poR->GetDriverCount(); + iDriver++) + { + fprintf(stderr, " -> `%s'\n", + poR->GetDriver(iDriver)->GetDescription()); + } + exit(1); } - exit(1); - } + if (!CPLTestBool(CSLFetchNameValueDef(poDriver->GetMetadata(), + GDAL_DCAP_CREATE, "FALSE"))) + { + fprintf(stderr, + _("%s driver does not support data source creation.\n"), + psOptions.osSrcPicketsDataSourceName.c_str()); + exit(1); + } - /* -------------------------------------------------------------------- - */ - /* Find the output driver. */ - /* -------------------------------------------------------------------- - */ + /* ---------------------------------------------------------------- */ + /* Create the output data source. */ + /* ---------------------------------------------------------------- */ + GDALDataset *poODS = + poDriver->Create(psOptions.osOutputDataSourceName.c_str(), 0, 0, + 0, GDT_Unknown, psOptions.aosDSCO); + if (poODS == nullptr) + { + fprintf(stderr, _("%s driver failed to create %s.\n"), + psOptions.osFormat.c_str(), + psOptions.osOutputDataSourceName.c_str()); + exit(1); + } - if (!bQuiet) - CheckDestDataSourceNameConsistency(pszOutputDataSource, pszFormat); + OGRLayer *poLnLayer = + psOptions.osSrcLineLayerName.empty() + ? poLnDS->GetLayer(0) + : poLnDS->GetLayerByName( + psOptions.osSrcLineLayerName.c_str()); - OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar(); + if (poLnLayer == nullptr) + { + fprintf(stderr, _("Get path layer failed.\n")); + exit(1); + } - GDALDriver *poDriver = poR->GetDriverByName(pszFormat); - if (poDriver == nullptr) - { - fprintf(stderr, "Unable to find driver `%s'.\n", pszFormat); - fprintf(stderr, "The following drivers are available:\n"); + OGRLayer *poPkLayer = + psOptions.osSrcPicketsLayerName.empty() + ? poPkDS->GetLayer(0) + : poPkDS->GetLayerByName( + psOptions.osSrcPicketsLayerName.c_str()); - for (int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++) + if (poPkLayer == nullptr) { - fprintf(stderr, " -> `%s'\n", - poR->GetDriver(iDriver)->GetDescription()); + fprintf(stderr, _("Get repers layer failed.\n")); + exit(1); } - exit(1); - } - if (!CPLTestBool(CSLFetchNameValueDef(poDriver->GetMetadata(), - GDAL_DCAP_CREATE, "FALSE"))) - { - fprintf(stderr, - "%s driver does not support data source creation.\n", - pszFormat); - exit(1); - } + OGRFeatureDefn *poPkFDefn = poPkLayer->GetLayerDefn(); + int nMValField = poPkFDefn->GetFieldIndex( + psOptions.osSrcPicketsMFieldName.c_str()); - /* -------------------------------------------------------------------- - */ - /* Create the output data source. */ - /* -------------------------------------------------------------------- - */ - GDALDataset *poODS = poDriver->Create(pszOutputDataSource, 0, 0, 0, - GDT_Unknown, papszDSCO); - if (poODS == nullptr) - { - fprintf(stderr, "%s driver failed to create %s\n", pszFormat, - pszOutputDataSource); - exit(1); - } + OGRLayer *poOutLayer = nullptr; + if (!psOptions.osSrcLineSepFieldName.empty() && + !psOptions.osSrcPicketsSepFieldName.empty()) + { + poOutLayer = + SetupTargetLayer(poLnLayer, poODS, psOptions.aosLCO, + psOptions.osOutputLayerName.c_str(), + psOptions.osOutputSepFieldName.c_str()); + if (poOutLayer == nullptr) + { + fprintf(stderr, _("Create output layer failed.\n")); + exit(1); + } - OGRLayer *poLnLayer = pszLineLayerName == nullptr - ? poLnDS->GetLayer(0) - : poLnDS->GetLayerByName(pszLineLayerName); + // Do the work + eErr = CreatePartsMultiple( + poLnLayer, psOptions.osSrcLineSepFieldName.c_str(), + poPkLayer, psOptions.osSrcPicketsSepFieldName.c_str(), + nMValField, psOptions.dfStep, poOutLayer, + psOptions.osOutputSepFieldName.c_str(), + psOptions.bDisplayProgress, psOptions.bQuiet); + } + else + { + poOutLayer = + SetupTargetLayer(poLnLayer, poODS, psOptions.aosLCO, + psOptions.osOutputLayerName.c_str()); + if (poOutLayer == nullptr) + { + fprintf(stderr, _("Create output layer failed.\n")); + exit(1); + } - if (poLnLayer == nullptr) - { - fprintf(stderr, "Get path layer failed.\n"); - exit(1); - } + // Do the work + eErr = CreateParts( + poLnLayer, poPkLayer, nMValField, psOptions.dfStep, + poOutLayer, psOptions.bDisplayProgress, psOptions.bQuiet); + } - OGRLayer *poPkLayer = pszPicketsLayerName == nullptr - ? poPkDS->GetLayer(0) - : poPkDS->GetLayerByName(pszPicketsLayerName); + GDALClose(poLnDS); + GDALClose(poPkDS); + if (GDALClose(poODS) != CE_None) + eErr = CE_Failure; - if (poPkLayer == nullptr) - { - fprintf(stderr, "Get repers layer failed.\n"); +#else // HAVE_GEOS + fprintf(stderr, + _("GEOS support not enabled or incompatible version.\n")); exit(1); +#endif // HAVE_GEOS + break; } + case op_get_pos: + { +#ifdef HAVE_GEOS - OGRFeatureDefn *poPkFDefn = poPkLayer->GetLayerDefn(); - int nMValField = poPkFDefn->GetFieldIndex(pszPicketsMField); + if (psOptions.dfXPos == std::numeric_limits::quiet_NaN() || + psOptions.dfYPos == std::numeric_limits::quiet_NaN()) + { + fprintf(stderr, _("no coordinates provided\n")); + argParser->usage(); + exit(1); + } + if (psOptions.osSrcPartsDataSourceName.empty()) + { + fprintf(stderr, _("no parts datasource provided\n")); + argParser->usage(); + exit(1); + } - OGRLayer *poOutLayer = nullptr; - if (pszLineSepFieldName != nullptr && pszPicketsSepFieldName != nullptr) - { - poOutLayer = - SetupTargetLayer(poLnLayer, poODS, papszLCO, pszOutputLayerName, - pszOutputSepFieldName); - if (poOutLayer == nullptr) + GDALDataset *poPartsDS = GDALDataset::FromHandle(OGROpen( + psOptions.osSrcPartsDataSourceName.c_str(), FALSE, nullptr)); + /* ------------------------------------------------------------------ */ + /* Report failure */ + /* ------------------------------------------------------------------ */ + if (poPartsDS == nullptr) { - fprintf(stderr, "Create output layer failed.\n"); + OGRSFDriverRegistrar *poR = + OGRSFDriverRegistrar::GetRegistrar(); + + fprintf(stderr, + _("FAILURE:\n" + "Unable to open parts datasource `%s' with " + "the following drivers.\n"), + psOptions.osSrcPicketsDataSourceName.c_str()); + + for (int iDriver = 0; iDriver < poR->GetDriverCount(); + iDriver++) + { + fprintf(stderr, " -> %s\n", + poR->GetDriver(iDriver)->GetDescription()); + } + exit(1); } - // Do the work - eErr = CreatePartsMultiple( - poLnLayer, pszLineSepFieldName, poPkLayer, - pszPicketsSepFieldName, nMValField, dfStep, poOutLayer, - pszOutputSepFieldName, bDisplayProgress, bQuiet); - } - else - { - poOutLayer = SetupTargetLayer(poLnLayer, poODS, papszLCO, - pszOutputLayerName); - if (poOutLayer == nullptr) + OGRLayer *poPartsLayer = + psOptions.osSrcPartsLayerName.empty() + ? poPartsDS->GetLayer(0) + : poPartsDS->GetLayerByName( + psOptions.osSrcPartsLayerName.c_str()); + + if (poPartsLayer == nullptr) { - fprintf(stderr, "Create output layer failed.\n"); + fprintf(stderr, _("Get parts layer failed.\n")); exit(1); } // Do the work - eErr = CreateParts(poLnLayer, poPkLayer, nMValField, dfStep, - poOutLayer, bDisplayProgress, bQuiet); - } + eErr = GetPosition(poPartsLayer, psOptions.dfXPos, psOptions.dfYPos, + psOptions.bDisplayProgress, psOptions.bQuiet); - GDALClose(poLnDS); - GDALClose(poPkDS); - if (GDALClose(poODS) != CE_None) - eErr = CE_Failure; + GDALClose(poPartsDS); - if (nullptr != pszOutputLayerName) - CPLFree(pszOutputLayerName); #else // HAVE_GEOS - fprintf(stderr, "GEOS support not enabled or incompatible version.\n"); - exit(1); + fprintf(stderr, + "GEOS support not enabled or incompatible version.\n"); + exit(1); #endif // HAVE_GEOS - } - else if (stOper == op_get_pos) - { -#ifdef HAVE_GEOS - if (pszPartsDataSource == nullptr) - Usage(true, "no parts datasource provided"); - else if (dfX == -100000000.0 || dfY == -100000000.0) - Usage(true, "no coordinates provided"); - - GDALDataset *poPartsDS = GDALDataset::FromHandle( - OGROpen(pszPartsDataSource, FALSE, nullptr)); - /* -------------------------------------------------------------------- - */ - /* Report failure */ - /* -------------------------------------------------------------------- - */ - if (poPartsDS == nullptr) + break; + } + case op_get_coord: { - OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar(); - - fprintf(stderr, - "FAILURE:\n" - "Unable to open parts datasource `%s' with " - "the following drivers.\n", - pszPicketsDataSource); - - for (int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++) + if (psOptions.osSrcPartsDataSourceName.empty()) + { + fprintf(stderr, _("No parts datasource provided.\n")); + argParser->usage(); + exit(1); + } + if (psOptions.dfPos == std::numeric_limits::quiet_NaN()) { - fprintf(stderr, " -> %s\n", - poR->GetDriver(iDriver)->GetDescription()); + fprintf(stderr, _("No position provided.\n")); + argParser->usage(); + exit(1); } - exit(1); - } + GDALDataset *poPartsDS = GDALDataset::FromHandle(OGROpen( + psOptions.osSrcPartsDataSourceName.c_str(), FALSE, nullptr)); + /* ----------------------------------------------------------------- */ + /* Report failure */ + /* ----------------------------------------------------------------- */ + if (poPartsDS == nullptr) + { + OGRSFDriverRegistrar *poR = + OGRSFDriverRegistrar::GetRegistrar(); - OGRLayer *poPartsLayer = - pszPartsLayerName == nullptr - ? poPartsDS->GetLayer(0) - : poPartsDS->GetLayerByName(pszPartsLayerName); + fprintf(stderr, + _("FAILURE:\n" + "Unable to open parts datasource `%s' with " + "the following drivers.\n"), + psOptions.osSrcPicketsDataSourceName.c_str()); - if (poPartsLayer == nullptr) - { - fprintf(stderr, "Get parts layer failed.\n"); - exit(1); - } + for (int iDriver = 0; iDriver < poR->GetDriverCount(); + iDriver++) + { + fprintf(stderr, " -> %s\n", + poR->GetDriver(iDriver)->GetDescription()); + } - // Do the work - eErr = GetPosition(poPartsLayer, dfX, dfY, bDisplayProgress, bQuiet); + exit(1); + } - GDALClose(poPartsDS); + OGRLayer *poPartsLayer = + psOptions.osSrcPartsLayerName.empty() + ? poPartsDS->GetLayer(0) + : poPartsDS->GetLayerByName( + psOptions.osSrcPartsLayerName.c_str()); -#else // HAVE_GEOS - fprintf(stderr, "GEOS support not enabled or incompatible version.\n"); - exit(1); -#endif // HAVE_GEOS - } - else if (stOper == op_get_coord) - { - if (pszPartsDataSource == nullptr) - Usage(true, "no parts datasource provided"); - else if (dfPos == -100000000.0) - Usage(true, "no position provided"); - - GDALDataset *poPartsDS = GDALDataset::FromHandle( - OGROpen(pszPartsDataSource, FALSE, nullptr)); - /* -------------------------------------------------------------------- - */ - /* Report failure */ - /* -------------------------------------------------------------------- - */ - if (poPartsDS == nullptr) - { - OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar(); + if (poPartsLayer == nullptr) + { + fprintf(stderr, _("Get parts layer failed.\n")); + exit(1); + } + // Do the work + eErr = GetCoordinates(poPartsLayer, psOptions.dfPos, + psOptions.bDisplayProgress, psOptions.bQuiet); - fprintf(stderr, - "FAILURE:\n" - "Unable to open parts datasource `%s' with " - "the following drivers.\n", - pszPicketsDataSource); + GDALClose(poPartsDS); - for (int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++) + break; + } + case op_get_subline: + { + if (psOptions.dfPosBeg == std::numeric_limits::quiet_NaN()) + { + fprintf(stderr, _("No begin position provided.\n")); + argParser->usage(); + exit(1); + } + if (psOptions.dfPosEnd == std::numeric_limits::quiet_NaN()) + { + fprintf(stderr, _("No end position provided.\n")); + argParser->usage(); + exit(1); + } + if (psOptions.osSrcPartsDataSourceName.empty()) { - fprintf(stderr, " -> %s\n", - poR->GetDriver(iDriver)->GetDescription()); + fprintf(stderr, _("No parts datasource provided.\n")); + argParser->usage(); + exit(1); } - exit(1); - } + GDALDataset *poPartsDS = GDALDataset::FromHandle(OGROpen( + psOptions.osSrcPartsDataSourceName.c_str(), FALSE, nullptr)); - OGRLayer *poPartsLayer = - pszPartsLayerName == nullptr - ? poPartsDS->GetLayer(0) - : poPartsDS->GetLayerByName(pszPartsLayerName); + // Report failure. + if (poPartsDS == nullptr) + { + OGRSFDriverRegistrar *poR = + OGRSFDriverRegistrar::GetRegistrar(); - if (poPartsLayer == nullptr) - { - fprintf(stderr, "Get parts layer failed.\n"); - exit(1); - } - // Do the work - eErr = GetCoordinates(poPartsLayer, dfPos, bDisplayProgress, bQuiet); + fprintf(stderr, + _("FAILURE:\n" + "Unable to open parts datasource `%s' with " + "the following drivers.\n"), + psOptions.osSrcPicketsDataSourceName.c_str()); + + for (int iDriver = 0; iDriver < poR->GetDriverCount(); + iDriver++) + { + fprintf(stderr, " -> %s\n", + poR->GetDriver(iDriver)->GetDescription()); + } + + exit(1); + } + + // Find the output driver. + if (!psOptions.bQuiet) + CheckDestDataSourceNameConsistency( + psOptions.osOutputDataSourceName.c_str(), + psOptions.osFormat.c_str()); - GDALClose(poPartsDS); - } - else if (stOper == op_get_subline) - { - if (pszOutputDataSource == nullptr) - Usage(true, "no output datasource provided"); - else if (pszPartsDataSource == nullptr) - Usage(true, "no parts datasource provided"); - else if (dfPosBeg == -100000000.0) - Usage(true, "no begin position provided"); - else if (dfPosEnd == -100000000.0) - Usage(true, "no end position provided"); - - // Open data source. - GDALDataset *poPartsDS = GDALDataset::FromHandle( - OGROpen(pszPartsDataSource, FALSE, nullptr)); - - // Report failure. - if (poPartsDS == nullptr) - { OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar(); - fprintf(stderr, - "FAILURE:\n" - "Unable to open parts datasource `%s' with " - "the following drivers.\n", - pszLineDataSource); + GDALDriver *poDriver = + poR->GetDriverByName(psOptions.osFormat.c_str()); + if (poDriver == nullptr) + { + fprintf(stderr, _("Unable to find driver `%s'.\n"), + psOptions.osFormat.c_str()); + fprintf(stderr, _("The following drivers are available:\n")); + + for (int iDriver = 0; iDriver < poR->GetDriverCount(); + iDriver++) + { + fprintf(stderr, " -> `%s'\n", + poR->GetDriver(iDriver)->GetDescription()); + } + exit(1); + } - for (int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++) + if (!CPLTestBool(CSLFetchNameValueDef(poDriver->GetMetadata(), + GDAL_DCAP_CREATE, "FALSE"))) { - fprintf(stderr, " -> %s\n", - poR->GetDriver(iDriver)->GetDescription()); + fprintf(stderr, + _("%s driver does not support data source creation.\n"), + psOptions.osFormat.c_str()); + exit(1); } - exit(1); - } + // Create the output data source. - // Find the output driver. + GDALDataset *poODS = + poDriver->Create(psOptions.osOutputDataSourceName.c_str(), 0, 0, + 0, GDT_Unknown, psOptions.aosDSCO); + if (poODS == nullptr) + { + fprintf(stderr, _("%s driver failed to create %s\n"), + psOptions.osFormat.c_str(), + psOptions.osOutputDataSourceName.c_str()); + exit(1); + } - if (!bQuiet) - CheckDestDataSourceNameConsistency(pszOutputDataSource, pszFormat); + OGRLayer *poPartsLayer = + psOptions.osSrcLineLayerName.empty() + ? poPartsDS->GetLayer(0) + : poPartsDS->GetLayerByName( + psOptions.osSrcLineLayerName.c_str()); - OGRSFDriverRegistrar *poR = OGRSFDriverRegistrar::GetRegistrar(); + if (poPartsLayer == nullptr) + { + fprintf(stderr, _("Get parts layer failed.\n")); + exit(1); + } - GDALDriver *poDriver = poR->GetDriverByName(pszFormat); - if (poDriver == nullptr) - { - fprintf(stderr, "Unable to find driver `%s'.\n", pszFormat); - fprintf(stderr, "The following drivers are available:\n"); + OGRLayer *poOutLayer = + SetupTargetLayer(poPartsLayer, poODS, psOptions.aosLCO, + psOptions.osOutputLayerName.c_str()); - for (int iDriver = 0; iDriver < poR->GetDriverCount(); iDriver++) + if (poOutLayer == nullptr) { - fprintf(stderr, " -> `%s'\n", - poR->GetDriver(iDriver)->GetDescription()); + fprintf(stderr, _("Create output layer failed.\n")); + exit(1); } - exit(1); - } - if (!CPLTestBool(CSLFetchNameValueDef(poDriver->GetMetadata(), - GDAL_DCAP_CREATE, "FALSE"))) - { - fprintf(stderr, - "%s driver does not support data source creation.\n", - pszFormat); - exit(1); - } + // Do the work - // Create the output data source. - GDALDataset *poODS = poDriver->Create(pszOutputDataSource, 0, 0, 0, - GDT_Unknown, papszDSCO); - if (poODS == nullptr) - { - fprintf(stderr, "%s driver failed to create %s\n", pszFormat, - pszOutputDataSource); - exit(1); - } + eErr = CreateSubline(poPartsLayer, psOptions.dfPosBeg, + psOptions.dfPosEnd, poOutLayer, + psOptions.bDisplayProgress, psOptions.bQuiet); - OGRLayer *poPartsLayer = - pszLineLayerName == nullptr - ? poPartsDS->GetLayer(0) - : poPartsDS->GetLayerByName(pszLineLayerName); + GDALClose(poPartsDS); + if (GDALClose(poODS) != CE_None) + eErr = CE_Failure; - if (poPartsLayer == nullptr) - { - fprintf(stderr, "Get parts layer failed.\n"); - exit(1); + break; } - - OGRLayer *poOutLayer = - SetupTargetLayer(poPartsLayer, poODS, papszLCO, pszOutputLayerName); - if (poOutLayer == nullptr) - { - fprintf(stderr, "Create output layer failed.\n"); + default: + fprintf(stderr, _("Unknown operation.\n")); + argParser->usage(); exit(1); - } - - // Do the work. - eErr = CreateSubline(poPartsLayer, dfPosBeg, dfPosEnd, poOutLayer, - bDisplayProgress, bQuiet); - - GDALClose(poPartsDS); - if (GDALClose(poODS) != CE_None) - eErr = CE_Failure; - - if (nullptr != pszOutputLayerName) - CPLFree(pszOutputLayerName); - } - else - { - Usage(true, "no operation provided"); } - CSLDestroy(papszArgv); - CSLDestroy(papszDSCO); - CSLDestroy(papszLCO); - OGRCleanupAll(); #ifdef DBMALLOC From 4282f75bb709f4fe2c16bd27e98c5d92d1fe2804 Mon Sep 17 00:00:00 2001 From: Vincent Picavet Date: Fri, 7 Jun 2024 16:28:47 +0200 Subject: [PATCH 0077/1119] Fix small typo There is no too small contribution :-) --- doc/source/drivers/vector/dwg.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/drivers/vector/dwg.rst b/doc/source/drivers/vector/dwg.rst index 49fa440c97e1..5669134281a4 100644 --- a/doc/source/drivers/vector/dwg.rst +++ b/doc/source/drivers/vector/dwg.rst @@ -8,7 +8,7 @@ AutoCAD DWG .. build_dependencies:: Open Design Alliance Teigha library OGR supports reading most versions of AutoCAD DWG when built with the -Open Design Alliance Teigha library. DWG is an binary working format used +Open Design Alliance Teigha library. DWG is a binary working format used for AutoCAD drawings. A reasonable effort has been made to make the OGR DWG driver work similarly to the OGR DXF driver which shares a common data model. The entire contents of the .dwg file is represented as a From f1a85269249b8e7aa3f986af9eb9ef0291770bbf Mon Sep 17 00:00:00 2001 From: Andrew Johnston Date: Fri, 7 Jun 2024 09:03:41 -0800 Subject: [PATCH 0078/1119] Fix rendering of code block in python readme --- swig/python/README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/swig/python/README.rst b/swig/python/README.rst index e3e7863bbea8..838bd1f70718 100644 --- a/swig/python/README.rst +++ b/swig/python/README.rst @@ -118,6 +118,7 @@ This is most often due to pip reusing a cached GDAL installation. Verify that the necessary dependencies have been installed and then run the following to force a clean build: :: + pip install --no-cache --force-reinstall gdal[numpy]=="$(gdal-config --version).*" From 4cd56d9dd70ae3e01351d7d526c02ba211a15f91 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 7 Jun 2024 19:48:34 +0200 Subject: [PATCH 0079/1119] Fix flake8 E721 warnings --- autotest/utilities/test_gdal_footprint_lib.py | 2 +- autotest/utilities/test_gdalinfo_lib.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/autotest/utilities/test_gdal_footprint_lib.py b/autotest/utilities/test_gdal_footprint_lib.py index b82f9de9ead9..d8a969b887e5 100755 --- a/autotest/utilities/test_gdal_footprint_lib.py +++ b/autotest/utilities/test_gdal_footprint_lib.py @@ -126,7 +126,7 @@ def test_gdal_footprint_lib_destSRS(): def test_gdal_footprint_lib_inline_geojson(): ret = gdal.Footprint("", "../gcore/data/byte.tif", format="GeoJSON") - assert type(ret) == dict + assert isinstance(ret, dict) assert ret["crs"]["properties"]["name"] == "urn:ogc:def:crs:OGC:1.3:CRS84" diff --git a/autotest/utilities/test_gdalinfo_lib.py b/autotest/utilities/test_gdalinfo_lib.py index 73a8c9648fe7..8e49b231fd43 100755 --- a/autotest/utilities/test_gdalinfo_lib.py +++ b/autotest/utilities/test_gdalinfo_lib.py @@ -156,7 +156,7 @@ def test_gdalinfo_lib_6(): ret = gdal.Info("../gcore/data/byte.tif", options="-json") assert ret["driverShortName"] == "GTiff", "wrong value for driverShortName." - assert type(ret) == dict + assert isinstance(ret, dict) ############################################################################### @@ -170,7 +170,7 @@ def test_gdalinfo_lib_7(): options="-json".encode("ascii").decode("ascii"), ) assert ret["driverShortName"] == "GTiff", "wrong value for driverShortName." - assert type(ret) == dict + assert isinstance(ret, dict) ############################################################################### @@ -181,7 +181,7 @@ def test_gdalinfo_lib_8(): ret = gdal.Info("../gcore/data/byte.tif", options=["-json"]) assert ret["driverShortName"] == "GTiff", "wrong value for driverShortName." - assert type(ret) == dict + assert isinstance(ret, dict) ############################################################################### From 4514311e044dab5288fa7717d2cd5b03cd5f601d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 7 Jun 2024 19:46:14 +0200 Subject: [PATCH 0080/1119] Update flake8 in .pre-commit-config.yaml --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df38d94bc0bd..d07cd0fca1e9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: autotest/ogr/data/ ) - repo: https://github.com/pycqa/flake8 - rev: 3.9.2 + rev: 7.0.0 hooks: - id: flake8 exclude: > From 11bfdfabfeb433a14283885d5ac5caf6cda62cbb Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 8 Jun 2024 03:04:30 +0200 Subject: [PATCH 0081/1119] Suppress MSVC warnings related to argparse.hpp --- apps/gdalargumentparser.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/gdalargumentparser.h b/apps/gdalargumentparser.h index bb4ebb522534..d72b69083f98 100644 --- a/apps/gdalargumentparser.h +++ b/apps/gdalargumentparser.h @@ -39,8 +39,18 @@ // Use our locale-unaware strtod() #define ARGPARSE_CUSTOM_STRTOD CPLStrtodM +#ifdef _MSC_VER +#pragma warning(push) +// unreachable code +#pragma warning(disable : 4702) +#endif + #include "argparse/argparse.hpp" +#ifdef _MSC_VER +#pragma warning(pop) +#endif + using namespace argparse; // Place-holder macro using gettext() convention to indicate (future) translatable strings From 550f4010357681cfcc56a17ca92aa260cffddf38 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 8 Jun 2024 03:10:16 +0200 Subject: [PATCH 0082/1119] Suppress MSVC warnings related to PublicDecompWT --- frmts/msg/PublicDecompWT_headers.h | 2 ++ frmts/msg/generate_PublicDecompWT_all.h.cmake | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/frmts/msg/PublicDecompWT_headers.h b/frmts/msg/PublicDecompWT_headers.h index c53d10d2e59e..bc7e706e7171 100644 --- a/frmts/msg/PublicDecompWT_headers.h +++ b/frmts/msg/PublicDecompWT_headers.h @@ -5,6 +5,8 @@ #ifdef _MSC_VER #pragma warning(push) +// '<=': signed/unsigned mismatch +#pragma warning(disable : 4018) //conversion from 'int' to 'WORD', possible loss of data #pragma warning(disable : 4244) // '=': conversion from 'size_t' to 'int', possible loss of data diff --git a/frmts/msg/generate_PublicDecompWT_all.h.cmake b/frmts/msg/generate_PublicDecompWT_all.h.cmake index adc244472cf5..686ef7a18b95 100644 --- a/frmts/msg/generate_PublicDecompWT_all.h.cmake +++ b/frmts/msg/generate_PublicDecompWT_all.h.cmake @@ -6,6 +6,12 @@ set(PUBLIC_DECOMPTWT_ALL_H "// This is a generated file. Do not edit ! #if ((__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)) && !defined(_MSC_VER)) #pragma GCC system_header #endif + +#ifdef _MSC_VER +#pragma warning(push) +// '<=': signed/unsigned mismatch +#pragma warning(disable : 4018) +#endif ") foreach(_f IN LISTS PUBLIC_DECOMP_WT_SRC) if (NOT("${IS_WIN32}" AND (${_f} MATCHES "TimeSpan.cpp" OR ${_f} MATCHES "UTCTime.cpp"))) @@ -13,4 +19,11 @@ foreach(_f IN LISTS PUBLIC_DECOMP_WT_SRC) ") endif() endforeach() + +string(APPEND PUBLIC_DECOMPTWT_ALL_H " +#ifdef _MSC_VER +#pragma warning(pop) +#endif +") + file(WRITE "${BINARY_DIR}/PublicDecompWT_all.h" "${PUBLIC_DECOMPTWT_ALL_H}") From 97f7f5cc9f3e8c9ee8402e45971eba3341949a86 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 8 Jun 2024 03:37:42 +0200 Subject: [PATCH 0083/1119] gdal_unit_test: make CheckEqualGeometries() not crash without GEOS --- autotest/cpp/gdal_unit_test.cpp | 38 +++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/autotest/cpp/gdal_unit_test.cpp b/autotest/cpp/gdal_unit_test.cpp index d5d4ebe04d5f..608c6fdc6ec0 100644 --- a/autotest/cpp/gdal_unit_test.cpp +++ b/autotest/cpp/gdal_unit_test.cpp @@ -106,25 +106,35 @@ CheckEqualGeometries(OGRGeometryH lhs, OGRGeometryH rhs, double tolerance) { std::unique_ptr lhs_normalized_cpp; std::unique_ptr rhs_normalized_cpp; - if (EQUAL(OGR_G_GetGeometryName(lhs), "LINEARRING")) + OGRGeometryH lhs_normalized; + OGRGeometryH rhs_normalized; + if (OGRGeometryFactory::haveGEOS()) { - // Normalize() doesn't work with LinearRing - OGRLineString lhs_as_ls( - *OGRGeometry::FromHandle(lhs)->toLineString()); - lhs_normalized_cpp.reset(lhs_as_ls.Normalize()); - OGRLineString rhs_as_ls( - *OGRGeometry::FromHandle(rhs)->toLineString()); - rhs_normalized_cpp.reset(rhs_as_ls.Normalize()); + if (EQUAL(OGR_G_GetGeometryName(lhs), "LINEARRING")) + { + // Normalize() doesn't work with LinearRing + OGRLineString lhs_as_ls( + *OGRGeometry::FromHandle(lhs)->toLineString()); + lhs_normalized_cpp.reset(lhs_as_ls.Normalize()); + OGRLineString rhs_as_ls( + *OGRGeometry::FromHandle(rhs)->toLineString()); + rhs_normalized_cpp.reset(rhs_as_ls.Normalize()); + } + else + { + lhs_normalized_cpp.reset( + OGRGeometry::FromHandle(OGR_G_Normalize(lhs))); + rhs_normalized_cpp.reset( + OGRGeometry::FromHandle(OGR_G_Normalize(rhs))); + } + lhs_normalized = OGRGeometry::ToHandle(lhs_normalized_cpp.get()); + rhs_normalized = OGRGeometry::ToHandle(rhs_normalized_cpp.get()); } else { - lhs_normalized_cpp.reset( - OGRGeometry::FromHandle(OGR_G_Normalize(lhs))); - rhs_normalized_cpp.reset( - OGRGeometry::FromHandle(OGR_G_Normalize(rhs))); + lhs_normalized = lhs; + rhs_normalized = rhs; } - auto lhs_normalized = OGRGeometry::ToHandle(lhs_normalized_cpp.get()); - auto rhs_normalized = OGRGeometry::ToHandle(rhs_normalized_cpp.get()); // Test geometry points const std::size_t csize = 3; From b1890d703b650ebcedb7c56bc283a49c9833ebc5 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 7 Jun 2024 19:35:41 +0200 Subject: [PATCH 0084/1119] CI: another attempt at fixing issues with too old vcruntime140.dll on image windows-latest 20240603.1.0 --- .github/workflows/cmake_builds.yml | 49 +++++++++++++++++++++++---- cmake/helpers/GdalSetRuntimeEnv.cmake | 3 +- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index be406c3b5fe5..95ea167e078a 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -399,6 +399,17 @@ jobs: generator: Ninja cache-name: cmake-msvc steps: + + - name: Copy in the correct VC runtime DLLs (workaround for actions/runner-images#10004) + shell: powershell + run: | + mkdir c:\vcruntime + Copy-Item (Join-Path ` + ((Get-ChildItem -Directory ` + -Path "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Redist\MSVC\14.*" | + Sort -Descending | Select-Object -First 1).FullName + ) 'x64\Microsoft.VC143.CRT\*.dll') "c:\vcruntime" + # To avoid git clone to mess with the line endings of GDAL autotest data # files that look like text, but should be handled as binary content - name: Set git core.autocrlf to false @@ -447,6 +458,14 @@ jobs: rm -rf C:/Strawberry || /bin/true rm -rf "C:/Program Files/OpenSSL/lib" || /bin/true + - name: Set path for the correct VC runtime DLLs (workaround for actions/runner-images#10004) + shell: powershell + run: | + $env:PATH="c:\vcruntime;$env:PATH" + echo "PATH=$env:PATH" >> $env:GITHUB_ENV + echo ((Get-Command vcruntime140.dll).Path) + echo ((Get-Command vcruntime140.dll).Version) + - name: Configure shell: bash -l {0} # Disable MySQL because of "error LNK2038: mismatch detected for '_MSC_VER': value '1800' doesn't match value '1900' in ogrmysqldatasource.obj" and other errors @@ -454,10 +473,9 @@ jobs: # otherwise interpret /bla has a file relative to the Bash root directory and would replace it by a path like c:\Program Files\git\WX # BUILD_JAVA_BINDINGS=OFF because we get "Error occurred during initialization of VM. Corrupted ZIP library: C:\Miniconda\envs\gdalenv\Library\bin\zip.dll" when running java. Not reproducible on a standard VM # Build PDF driver as plugin due to the PDFium build including libopenjp2 symbols which would conflict with external libopenjp2 - # /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR to workaround issue with too old vcruntime140.dll on image 20240603.1.0 (https://github.com/actions/runner-images/issues/10004) run: | mkdir -p $GITHUB_WORKSPACE/build - cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Release -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON + cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Release -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config Release -j 2 @@ -502,6 +520,17 @@ jobs: generator: Visual Studio 17 2022 GDAL_PYTHON_BINDINGS_WITHOUT_NUMPY: YES steps: + + - name: Copy in the correct VC runtime DLLs (workaround for actions/runner-images#10004) + shell: powershell + run: | + mkdir c:\vcruntime + Copy-Item (Join-Path ` + ((Get-ChildItem -Directory ` + -Path "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Redist\MSVC\14.*" | + Sort -Descending | Select-Object -First 1).FullName + ) 'x64\Microsoft.VC143.CRT\*.dll') "c:\vcruntime" + # To avoid git clone to mess with the line endings of GDAL autotest data # files that look like text, but should be handled as binary content - name: Set git core.autocrlf to false @@ -525,12 +554,20 @@ jobs: shell: bash -l {0} run: | cmake --version + + - name: Set path for the correct VC runtime DLLs (workaround for actions/runner-images#10004) + shell: powershell + run: | + $env:PATH="c:\vcruntime;$env:PATH" + echo "PATH=$env:PATH" >> $env:GITHUB_ENV + echo ((Get-Command vcruntime140.dll).Path) + echo ((Get-Command vcruntime140.dll).Version) + - name: Configure shell: bash -l {0} - # /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR to workaround issue with too old vcruntime140.dll on image 20240603.1.0 (https://github.com/actions/runner-images/issues/10004) run: | mkdir -p $GITHUB_WORKSPACE/build - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 @@ -538,12 +575,12 @@ jobs: shell: bash -l {0} run: | rm -f build/CMakeCache.txt - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Configure with even less dependencies, and disabling all optional drivers shell: bash -l {0} run: | rm -f build/CMakeCache.txt - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF -DOGR_BUILD_OPTIONAL_DRIVERS=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF -DOGR_BUILD_OPTIONAL_DRIVERS=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 diff --git a/cmake/helpers/GdalSetRuntimeEnv.cmake b/cmake/helpers/GdalSetRuntimeEnv.cmake index e8152d949d08..766f2442dea9 100644 --- a/cmake/helpers/GdalSetRuntimeEnv.cmake +++ b/cmake/helpers/GdalSetRuntimeEnv.cmake @@ -33,7 +33,8 @@ function(gdal_set_runtime_env res) if (GTEST_DIR) set(SEP_GTEST_DIR "\\;${GTEST_DIR}") endif() - list(APPEND RUNTIME_ENV "PATH=${GDAL_OUTPUT_DIR}\\;${GDAL_APPS_OUTPUT_DIR_WITH_SEP}${NATIVE_GDAL_PYTHON_SCRIPTS_DIR_WITH_SEP}${PATH_ESCAPED}${SEP_GTEST_DIR}") + # c:\vcruntime is a hack for actions/runner-images#10004 (cf .github/workflows/cmake_builds.yml) + list(APPEND RUNTIME_ENV "PATH=c:\\vcruntime\\;${GDAL_OUTPUT_DIR}\\;${GDAL_APPS_OUTPUT_DIR_WITH_SEP}${NATIVE_GDAL_PYTHON_SCRIPTS_DIR_WITH_SEP}${PATH_ESCAPED}${SEP_GTEST_DIR}") else () if (Python_Interpreter_FOUND) set(GDAL_PYTHON_SCRIPTS_DIR_WITH_SEP "${GDAL_PYTHON_SCRIPTS_DIR}:") From d1952402504a41554b924f779a0e7a0f3763bd8e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 8 Jun 2024 02:32:59 +0200 Subject: [PATCH 0085/1119] CI Windows: switch to Debug build to workaround issue with too old vcruntime140.dll on image windows-latest 20240603.1.0 --- .github/workflows/cmake_builds.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index 95ea167e078a..6e4fec332eeb 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -475,7 +475,7 @@ jobs: # Build PDF driver as plugin due to the PDFium build including libopenjp2 symbols which would conflict with external libopenjp2 run: | mkdir -p $GITHUB_WORKSPACE/build - cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Release -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON + cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Debug -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config Release -j 2 @@ -484,11 +484,11 @@ jobs: - name: test shell: bash -l {0} run: | - cmake --build $GITHUB_WORKSPACE/build --config Release --target quicktest + cmake --build $GITHUB_WORKSPACE/build --config Debug --target quicktest - name: test (with ctest) shell: bash -l {0} run: | - ctest --test-dir $GITHUB_WORKSPACE/build -C Release -V -j 3 + ctest --test-dir $GITHUB_WORKSPACE/build -C Debug -V -j 3 env: SKIP_OGR_GMLAS_HUGE_PROCESSING_TIME: YES SKIP_OGR_GMLAS_HTTP_RELATED: YES @@ -497,7 +497,7 @@ jobs: - name: Install shell: bash -l {0} run: | - cmake --build $GITHUB_WORKSPACE/build --config Release --target install + cmake --build $GITHUB_WORKSPACE/build --config Debug --target install export PATH=$GITHUB_WORKSPACE/install-gdal/bin:$PATH gdalinfo --version python -VV @@ -570,7 +570,7 @@ jobs: cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} - run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 + run: cmake --build $GITHUB_WORKSPACE/build --config Debug -j 2 - name: Configure with even less dependencies shell: bash -l {0} run: | @@ -583,11 +583,11 @@ jobs: cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF -DOGR_BUILD_OPTIONAL_DRIVERS=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} - run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 + run: cmake --build $GITHUB_WORKSPACE/build --config Debug -j 2 - name: test (with ctest) shell: bash -l {0} run: | - ctest --test-dir $GITHUB_WORKSPACE/build -C RelWithDebInfo -V -j 3 + ctest --test-dir $GITHUB_WORKSPACE/build -C Debug -V -j 3 env: SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES BUILD_NAME: "build-windows-minimum" From 12b0a44444cc9545ae56aa5901ba514e83e547f4 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 8 Jun 2024 03:38:31 +0200 Subject: [PATCH 0086/1119] Revert "CI Windows: switch to Debug build to workaround issue with too old vcruntime140.dll on image windows-latest 20240603.1.0" This reverts commit f2f45cc80bc97d1a60f68e072be8d10b7f75c990. --- .github/workflows/cmake_builds.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index 6e4fec332eeb..95ea167e078a 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -475,7 +475,7 @@ jobs: # Build PDF driver as plugin due to the PDFium build including libopenjp2 symbols which would conflict with external libopenjp2 run: | mkdir -p $GITHUB_WORKSPACE/build - cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Debug -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON + cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Release -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config Release -j 2 @@ -484,11 +484,11 @@ jobs: - name: test shell: bash -l {0} run: | - cmake --build $GITHUB_WORKSPACE/build --config Debug --target quicktest + cmake --build $GITHUB_WORKSPACE/build --config Release --target quicktest - name: test (with ctest) shell: bash -l {0} run: | - ctest --test-dir $GITHUB_WORKSPACE/build -C Debug -V -j 3 + ctest --test-dir $GITHUB_WORKSPACE/build -C Release -V -j 3 env: SKIP_OGR_GMLAS_HUGE_PROCESSING_TIME: YES SKIP_OGR_GMLAS_HTTP_RELATED: YES @@ -497,7 +497,7 @@ jobs: - name: Install shell: bash -l {0} run: | - cmake --build $GITHUB_WORKSPACE/build --config Debug --target install + cmake --build $GITHUB_WORKSPACE/build --config Release --target install export PATH=$GITHUB_WORKSPACE/install-gdal/bin:$PATH gdalinfo --version python -VV @@ -570,7 +570,7 @@ jobs: cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} - run: cmake --build $GITHUB_WORKSPACE/build --config Debug -j 2 + run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 - name: Configure with even less dependencies shell: bash -l {0} run: | @@ -583,11 +583,11 @@ jobs: cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF -DOGR_BUILD_OPTIONAL_DRIVERS=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} - run: cmake --build $GITHUB_WORKSPACE/build --config Debug -j 2 + run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 - name: test (with ctest) shell: bash -l {0} run: | - ctest --test-dir $GITHUB_WORKSPACE/build -C Debug -V -j 3 + ctest --test-dir $GITHUB_WORKSPACE/build -C RelWithDebInfo -V -j 3 env: SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES BUILD_NAME: "build-windows-minimum" From 839374d3e73af02af8a796509d44254db0c6b8af Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 8 Jun 2024 03:42:11 +0200 Subject: [PATCH 0087/1119] CI: disable running Windows tests to workaround issue with too old vcruntime140.dll on image windows-latest 20240603.1.0 --- .github/workflows/cmake_builds.yml | 58 ++++++++++++++++-------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index 95ea167e078a..9c5bbd2481b5 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -481,29 +481,34 @@ jobs: run: cmake --build $GITHUB_WORKSPACE/build --config Release -j 2 env: GIT_LFS_SKIP_SMUDGE: 1 # for PublicDecompWT github repository clone - - name: test - shell: bash -l {0} - run: | - cmake --build $GITHUB_WORKSPACE/build --config Release --target quicktest - - name: test (with ctest) - shell: bash -l {0} - run: | - ctest --test-dir $GITHUB_WORKSPACE/build -C Release -V -j 3 - env: - SKIP_OGR_GMLAS_HUGE_PROCESSING_TIME: YES - SKIP_OGR_GMLAS_HTTP_RELATED: YES - SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES - BUILD_NAME: "build-windows-conda" + # FIXME !! Disabled because of actions/runner-images#10004 + #- name: test + # shell: bash -l {0} + # run: | + # cmake --build $GITHUB_WORKSPACE/build --config Release --target quicktest + #- name: test (with ctest) + # shell: bash -l {0} + # run: | + # ctest --test-dir $GITHUB_WORKSPACE/build -C Release -V -j 3 + # env: + # SKIP_OGR_GMLAS_HUGE_PROCESSING_TIME: YES + # SKIP_OGR_GMLAS_HTTP_RELATED: YES + # SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES + # BUILD_NAME: "build-windows-conda" - name: Install shell: bash -l {0} run: | cmake --build $GITHUB_WORKSPACE/build --config Release --target install - export PATH=$GITHUB_WORKSPACE/install-gdal/bin:$PATH - gdalinfo --version - python -VV - PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages python -c "from osgeo import gdal;print(gdal.VersionInfo(None))" - export PATH=$GITHUB_WORKSPACE/install-gdal/Scripts:$PATH - PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages gdal_edit --version + # FIXME !! Disabled because of actions/runner-images#10004 + #- name: Test install + # shell: bash -l {0} + # run: | + # export PATH=$GITHUB_WORKSPACE/install-gdal/bin:$PATH + # gdalinfo --version + # python -VV + # PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages python -c "from osgeo import gdal;print(gdal.VersionInfo(None))" + # export PATH=$GITHUB_WORKSPACE/install-gdal/Scripts:$PATH + # PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages gdal_edit --version - name: Show gdal.pc shell: bash -l {0} run: cat $GITHUB_WORKSPACE/build/gdal.pc @@ -584,13 +589,14 @@ jobs: - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 - - name: test (with ctest) - shell: bash -l {0} - run: | - ctest --test-dir $GITHUB_WORKSPACE/build -C RelWithDebInfo -V -j 3 - env: - SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES - BUILD_NAME: "build-windows-minimum" + # FIXME !! Disable tests because of actions/runner-images#10004 + #- name: test (with ctest) + # shell: bash -l {0} + # run: | + # ctest --test-dir $GITHUB_WORKSPACE/build -C RelWithDebInfo -V -j 3 + # env: + # SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES + # BUILD_NAME: "build-windows-minimum" - name: Show gdal.pc shell: bash -l {0} run: cat $GITHUB_WORKSPACE/build/gdal.pc From 2bda0b9659e6c3c2d266a9eed0dc637c38cdc58e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 7 Jun 2024 15:41:11 +0200 Subject: [PATCH 0088/1119] GTiff: fix reading angular projection parameters in non-degree unit (typically grads), when reading projection from ProjXXXXGeoKeys Fixes https://github.com/OSGeo/gdal/issues/10154 libgeotiff is unfortunately inconsistent. When it synthetizes the projection parameters from the EPSG ProjectedCRS code, it returns them normalized in degrees. But when it gets them from ProjCoordTransGeoKey and other Proj....GeoKey's it return them in a raw way, that is in the units of GeogAngularUnitSizeGeoKey From GDAL 3.0 to 3.9.0, we didn't take the later case into account, and lacked a conversion to degree. And all versions of GDAL <= 3.9.0 when writing those geokeys, wrote them as degrees, hence this GTIFF_READ_ANGULAR_PARAMS_IN_DEGREE config option that can be set to YES to avoid that conversion and assume that the angular parameters have been written as degree. --- .../data/gtiff/epsg_27563_allgeokeys.tif | Bin 0 -> 757 bytes autotest/gcore/tiff_srs.py | 36 ++++++++++++++++++ frmts/gtiff/gt_wkt_srs.cpp | 22 +++++++++++ 3 files changed, 58 insertions(+) create mode 100644 autotest/gcore/data/gtiff/epsg_27563_allgeokeys.tif diff --git a/autotest/gcore/data/gtiff/epsg_27563_allgeokeys.tif b/autotest/gcore/data/gtiff/epsg_27563_allgeokeys.tif new file mode 100644 index 0000000000000000000000000000000000000000..da79734dd3783b94b72d96481892886a684858cf GIT binary patch literal 757 zcmebD)MC(KU|?vle-L1yBF4+!;*=P$BgIUXzr!Ud&|*oLE?`8`^mo1c7Whpv4>8 znHc}4QF009RWG6LPg$Z+<|kw@mN5A0{nJPM{EDsYpB{ncUQJI}% zLp#VM4lqAiniI@tWZ(wVObonWnwfzQNV9`1;{lnwI-8LPWRETb6PUl&jTuaHGO&PY zO=VUdkh$Cp9AKJkLPhxITYEg+oaA}HyTTxUOMAngleInDegrees; + adfParam[1] *= psDefn->UOMAngleInDegrees; + adfParam[2] *= psDefn->UOMAngleInDegrees; + adfParam[3] *= psDefn->UOMAngleInDegrees; + } + /* -------------------------------------------------------------------- */ /* Translation the fundamental projection. */ From 26c163d1938ad4a7ea5de43eea0fa9e4d6ffc7bd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 7 Jun 2024 17:28:59 +0200 Subject: [PATCH 0089/1119] GTiff: fix writing angular projection parameters in non-degree unit (typically grads), when writing ProjXXXXGeoKeys Convert angular projection parameters from their normalized value in degree to the units of GeogAngularUnitsGeoKey. Note: for GDAL <= 3.9.0, we always have written them in degrees ! We can set GTIFF_WRITE_ANGULAR_PARAMS_IN_DEGREE=YES to get that non-conformant behavior... Relates to https://github.com/OSGeo/gdal/issues/10154 --- autotest/gcore/tiff_srs.py | 67 +++++++++ frmts/gtiff/gt_wkt_srs.cpp | 280 ++++++++++++++++++++----------------- 2 files changed, 219 insertions(+), 128 deletions(-) diff --git a/autotest/gcore/tiff_srs.py b/autotest/gcore/tiff_srs.py index 65dd01fd30fc..71a922ad89f6 100755 --- a/autotest/gcore/tiff_srs.py +++ b/autotest/gcore/tiff_srs.py @@ -1460,3 +1460,70 @@ def test_tiff_srs_write_read_epsg_27563_only_code(tmp_vsimem): srs.ExportToProj4() == "+proj=lcc +lat_1=44.1 +lat_0=44.1 +lon_0=0 +k_0=0.999877499 +x_0=600000 +y_0=200000 +ellps=clrk80ign +pm=paris +towgs84=-168,-60,320,0,0,0,0 +units=m +no_defs" ) + + +@pytest.mark.parametrize( + "config_options", + [ + {}, + { + "GTIFF_WRITE_ANGULAR_PARAMS_IN_DEGREE": "YES", + "GTIFF_READ_ANGULAR_PARAMS_IN_DEGREE": "YES", + }, + ], +) +def test_tiff_srs_write_read_epsg_27563_full_def(tmp_vsimem, config_options): + + with gdal.config_options(config_options): + filename = str(tmp_vsimem / "test.tif") + srs = osr.SpatialReference() + srs.SetFromUserInput( + """PROJCRS["NTF (Paris) / Lambert Sud France", + BASEGEOGCRS["NTF (Paris)", + DATUM["Nouvelle Triangulation Francaise (Paris)", + ELLIPSOID["Clarke 1880 (IGN)",6378249.2,293.466021293627, + LENGTHUNIT["metre",1]]], + PRIMEM["Paris",2.5969213, + ANGLEUNIT["grad",0.0157079632679489]], + ID["EPSG",4807]], + CONVERSION["Lambert Sud France", + METHOD["Lambert Conic Conformal (1SP)", + ID["EPSG",9801]], + PARAMETER["Latitude of natural origin",49, + ANGLEUNIT["grad",0.0157079632679489], + ID["EPSG",8801]], + PARAMETER["Longitude of natural origin",0, + ANGLEUNIT["grad",0.0157079632679489], + ID["EPSG",8802]], + PARAMETER["Scale factor at natural origin",0.999877499, + SCALEUNIT["unity",1], + ID["EPSG",8805]], + PARAMETER["False easting",600000, + LENGTHUNIT["metre",1], + ID["EPSG",8806]], + PARAMETER["False northing",200000, + LENGTHUNIT["metre",1], + ID["EPSG",8807]]], + CS[Cartesian,2], + AXIS["easting (X)",east, + ORDER[1], + LENGTHUNIT["metre",1]], + AXIS["northing (Y)",north, + ORDER[2], + LENGTHUNIT["metre",1]]]""" + ) + ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1) + ds.SetSpatialRef(srs) + ds = None + + ds = gdal.Open(filename) + srs = ds.GetSpatialRef() + wkt = srs.ExportToWkt(["FORMAT=WKT2_2019"]) + # deal with differences of precision according to PROJ version + wkt = wkt.replace("49.0000000000001", "49") + wkt = wkt.replace("49.0000000000002", "49") + assert 'PARAMETER["Latitude of natural origin",49,ANGLEUNIT["grad"' in wkt + assert ( + srs.ExportToProj4() + == "+proj=lcc +lat_1=44.1 +lat_0=44.1 +lon_0=0 +k_0=0.999877499 +x_0=600000 +y_0=200000 +ellps=clrk80ign +pm=paris +units=m +no_defs" + ) diff --git a/frmts/gtiff/gt_wkt_srs.cpp b/frmts/gtiff/gt_wkt_srs.cpp index acb181055bfc..a1ea06dfac60 100644 --- a/frmts/gtiff/gt_wkt_srs.cpp +++ b/frmts/gtiff/gt_wkt_srs.cpp @@ -2172,6 +2172,31 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, } #endif + double dfAngUnitValue = 0; + std::string osAngUnitName; + if (bHasEllipsoid) + { + const char *angUnitNameTmp = ""; + dfAngUnitValue = poSRS->GetAngularUnits(&angUnitNameTmp); + osAngUnitName = angUnitNameTmp; + } + + // Convert angular projection parameters from its normalized value in degree + // to the units of GeogAngularUnitsGeoKey. + // Note: for GDAL <= 3.9.0, we always have written them in degrees ! + // We can set GTIFF_WRITE_ANGULAR_PARAMS_IN_DEGREE=YES to get that + // non-conformant behavior... + const auto ConvertAngularParam = [dfAngUnitValue](double dfValInDeg) + { + constexpr double DEG_TO_RAD = M_PI / 180.0; + return dfAngUnitValue != 0 && + std::fabs(dfAngUnitValue - DEG_TO_RAD) > 1e-10 && + !CPLTestBool(CPLGetConfigOption( + "GTIFF_WRITE_ANGULAR_PARAMS_IN_DEGREE", "NO")) + ? dfValInDeg * DEG_TO_RAD / dfAngUnitValue + : dfValInDeg; + }; + const char *pszProjection = poSRSCompatibleOfWKT1->GetAttrValue("PROJECTION"); if (nPCS != KvUserDefined) @@ -2213,20 +2238,20 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_AlbersEqualArea); GTIFKeySet(psGTIF, ProjStdParallelGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_STANDARD_PARALLEL_1, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_STANDARD_PARALLEL_1, 0.0))); GTIFKeySet(psGTIF, ProjStdParallel2GeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_STANDARD_PARALLEL_2, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_STANDARD_PARALLEL_2, 0.0))); GTIFKeySet(psGTIF, ProjNatOriginLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_CENTER, 0.0))); GTIFKeySet(psGTIF, ProjNatOriginLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LONGITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LONGITUDE_OF_CENTER, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2289,12 +2314,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_TransverseMercator); GTIFKeySet(psGTIF, ProjNatOriginLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjNatOriginLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet( psGTIF, ProjScaleAtNatOriginGeoKey, TYPE_DOUBLE, 1, @@ -2320,12 +2345,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_TransvMercator_SouthOriented); GTIFKeySet(psGTIF, ProjNatOriginLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjNatOriginLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet( psGTIF, ProjScaleAtNatOriginGeoKey, TYPE_DOUBLE, 1, @@ -2352,17 +2377,18 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, GTIFKeySet(psGTIF, ProjCoordTransGeoKey, TYPE_SHORT, 1, CT_Mercator); GTIFKeySet(psGTIF, ProjNatOriginLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjNatOriginLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); if (EQUAL(pszProjection, SRS_PT_MERCATOR_2SP)) - GTIFKeySet(psGTIF, ProjStdParallel1GeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_STANDARD_PARALLEL_1, 0.0)); + GTIFKeySet( + psGTIF, ProjStdParallel1GeoKey, TYPE_DOUBLE, 1, + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_STANDARD_PARALLEL_1, 0.0))); else GTIFKeySet(psGTIF, ProjScaleAtNatOriginGeoKey, TYPE_DOUBLE, 1, poSRSCompatibleOfWKT1->GetNormProjParm( @@ -2388,12 +2414,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_ObliqueStereographic); GTIFKeySet(psGTIF, ProjNatOriginLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjNatOriginLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet( psGTIF, ProjScaleAtNatOriginGeoKey, TYPE_DOUBLE, 1, @@ -2419,12 +2445,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_Stereographic); GTIFKeySet(psGTIF, ProjCenterLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet( psGTIF, ProjScaleAtNatOriginGeoKey, TYPE_DOUBLE, 1, @@ -2450,12 +2476,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_PolarStereographic); GTIFKeySet(psGTIF, ProjNatOriginLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjStraightVertPoleLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet( psGTIF, ProjScaleAtNatOriginGeoKey, TYPE_DOUBLE, 1, @@ -2481,19 +2507,19 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_ObliqueMercator); GTIFKeySet(psGTIF, ProjCenterLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_CENTER, 0.0))); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LONGITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LONGITUDE_OF_CENTER, 0.0))); GTIFKeySet(psGTIF, ProjAzimuthAngleGeoKey, TYPE_DOUBLE, 1, poSRSCompatibleOfWKT1->GetNormProjParm(SRS_PP_AZIMUTH, 0.0)); GTIFKeySet(psGTIF, ProjRectifiedGridAngleGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_RECTIFIED_GRID_ANGLE, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_RECTIFIED_GRID_ANGLE, 0.0))); GTIFKeySet( psGTIF, ProjScaleAtCenterGeoKey, TYPE_DOUBLE, 1, @@ -2520,19 +2546,19 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_HotineObliqueMercatorAzimuthCenter); GTIFKeySet(psGTIF, ProjCenterLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_CENTER, 0.0))); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LONGITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LONGITUDE_OF_CENTER, 0.0))); GTIFKeySet(psGTIF, ProjAzimuthAngleGeoKey, TYPE_DOUBLE, 1, poSRSCompatibleOfWKT1->GetNormProjParm(SRS_PP_AZIMUTH, 0.0)); GTIFKeySet(psGTIF, ProjRectifiedGridAngleGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_RECTIFIED_GRID_ANGLE, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_RECTIFIED_GRID_ANGLE, 0.0))); GTIFKeySet( psGTIF, ProjScaleAtCenterGeoKey, TYPE_DOUBLE, 1, @@ -2558,12 +2584,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_ObliqueMercator_Laborde); GTIFKeySet(psGTIF, ProjCenterLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_CENTER, 0.0))); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LONGITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LONGITUDE_OF_CENTER, 0.0))); GTIFKeySet(psGTIF, ProjAzimuthAngleGeoKey, TYPE_DOUBLE, 1, poSRSCompatibleOfWKT1->GetNormProjParm(SRS_PP_AZIMUTH, 0.0)); @@ -2592,12 +2618,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_CassiniSoldner); GTIFKeySet(psGTIF, ProjNatOriginLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjNatOriginLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2619,20 +2645,20 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_EquidistantConic); GTIFKeySet(psGTIF, ProjStdParallel1GeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_STANDARD_PARALLEL_1, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_STANDARD_PARALLEL_1, 0.0))); GTIFKeySet(psGTIF, ProjStdParallel2GeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_STANDARD_PARALLEL_2, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_STANDARD_PARALLEL_2, 0.0))); GTIFKeySet(psGTIF, ProjNatOriginLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_CENTER, 0.0))); GTIFKeySet(psGTIF, ProjNatOriginLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LONGITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LONGITUDE_OF_CENTER, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2684,12 +2710,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_AzimuthalEquidistant); GTIFKeySet(psGTIF, ProjCenterLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_CENTER, 0.0))); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LONGITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LONGITUDE_OF_CENTER, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2711,12 +2737,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_MillerCylindrical); GTIFKeySet(psGTIF, ProjCenterLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_CENTER, 0.0))); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LONGITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LONGITUDE_OF_CENTER, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2738,16 +2764,16 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_Equirectangular); GTIFKeySet(psGTIF, ProjCenterLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet(psGTIF, ProjStdParallel1GeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_STANDARD_PARALLEL_1, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_STANDARD_PARALLEL_1, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2768,12 +2794,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, GTIFKeySet(psGTIF, ProjCoordTransGeoKey, TYPE_SHORT, 1, CT_Gnomonic); GTIFKeySet(psGTIF, ProjCenterLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2795,12 +2821,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_LambertAzimEqualArea); GTIFKeySet(psGTIF, ProjCenterLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_CENTER, 0.0))); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LONGITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LONGITUDE_OF_CENTER, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2822,12 +2848,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_Orthographic); GTIFKeySet(psGTIF, ProjCenterLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2849,12 +2875,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_NewZealandMapGrid); GTIFKeySet(psGTIF, ProjNatOriginLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjNatOriginLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2875,8 +2901,8 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, GTIFKeySet(psGTIF, ProjCoordTransGeoKey, TYPE_SHORT, 1, CT_Robinson); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LONGITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LONGITUDE_OF_CENTER, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2897,8 +2923,8 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, GTIFKeySet(psGTIF, ProjCoordTransGeoKey, TYPE_SHORT, 1, CT_Sinusoidal); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LONGITUDE_OF_CENTER, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LONGITUDE_OF_CENTER, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2920,8 +2946,8 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_VanDerGrinten); GTIFKeySet(psGTIF, ProjCenterLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -2943,20 +2969,20 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_LambertConfConic_2SP); GTIFKeySet(psGTIF, ProjFalseOriginLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjFalseOriginLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet(psGTIF, ProjStdParallel1GeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_STANDARD_PARALLEL_1, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_STANDARD_PARALLEL_1, 0.0))); GTIFKeySet(psGTIF, ProjStdParallel2GeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_STANDARD_PARALLEL_2, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_STANDARD_PARALLEL_2, 0.0))); GTIFKeySet( psGTIF, ProjFalseOriginEastingGeoKey, TYPE_DOUBLE, 1, @@ -2978,12 +3004,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_LambertConfConic_1SP); GTIFKeySet(psGTIF, ProjNatOriginLatGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_LATITUDE_OF_ORIGIN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_LATITUDE_OF_ORIGIN, 0.0))); GTIFKeySet(psGTIF, ProjNatOriginLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet( psGTIF, ProjScaleAtNatOriginGeoKey, TYPE_DOUBLE, 1, @@ -3009,12 +3035,12 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, CT_CylindricalEqualArea); GTIFKeySet(psGTIF, ProjNatOriginLongGeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_CENTRAL_MERIDIAN, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_CENTRAL_MERIDIAN, 0.0))); GTIFKeySet(psGTIF, ProjStdParallel1GeoKey, TYPE_DOUBLE, 1, - poSRSCompatibleOfWKT1->GetNormProjParm( - SRS_PP_STANDARD_PARALLEL_1, 0.0)); + ConvertAngularParam(poSRSCompatibleOfWKT1->GetNormProjParm( + SRS_PP_STANDARD_PARALLEL_1, 0.0))); GTIFKeySet( psGTIF, ProjFalseEastingGeoKey, TYPE_DOUBLE, 1, @@ -3155,38 +3181,36 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, /* Write angular units. */ /* -------------------------------------------------------------------- */ - const char *angUnitName = ""; if (bHasEllipsoid && (nGCS == KvUserDefined || eVersion == GEOTIFF_VERSION_1_0)) { - double angUnitValue = poSRS->GetAngularUnits(&angUnitName); - if (EQUAL(angUnitName, "Degree")) + if (EQUAL(osAngUnitName.c_str(), "Degree")) GTIFKeySet(psGTIF, GeogAngularUnitsGeoKey, TYPE_SHORT, 1, Angular_Degree); - else if (EQUAL(angUnitName, "arc-second")) + else if (EQUAL(osAngUnitName.c_str(), "arc-second")) GTIFKeySet(psGTIF, GeogAngularUnitsGeoKey, TYPE_SHORT, 1, Angular_Arc_Second); - else if (EQUAL(angUnitName, "arc-minute")) + else if (EQUAL(osAngUnitName.c_str(), "arc-minute")) GTIFKeySet(psGTIF, GeogAngularUnitsGeoKey, TYPE_SHORT, 1, Angular_Arc_Minute); - else if (EQUAL(angUnitName, "grad")) + else if (EQUAL(osAngUnitName.c_str(), "grad")) GTIFKeySet(psGTIF, GeogAngularUnitsGeoKey, TYPE_SHORT, 1, Angular_Grad); - else if (EQUAL(angUnitName, "gon")) + else if (EQUAL(osAngUnitName.c_str(), "gon")) GTIFKeySet(psGTIF, GeogAngularUnitsGeoKey, TYPE_SHORT, 1, Angular_Gon); - else if (EQUAL(angUnitName, "radian")) + else if (EQUAL(osAngUnitName.c_str(), "radian")) GTIFKeySet(psGTIF, GeogAngularUnitsGeoKey, TYPE_SHORT, 1, Angular_Radian); - // else if (EQUAL(angUnitName, "microradian")) + // else if (EQUAL(osAngUnitName.c_str(), "microradian")) // GTIFKeySet(psGTIF, GeogAngularUnitsGeoKey, TYPE_SHORT, 1, // 9109); else { // GeogCitationGeoKey may be rewritten if the gcs is user defined. - oMapAsciiKeys[GeogCitationGeoKey] = angUnitName; + oMapAsciiKeys[GeogCitationGeoKey] = osAngUnitName; GTIFKeySet(psGTIF, GeogAngularUnitSizeGeoKey, TYPE_DOUBLE, 1, - angUnitValue); + dfAngUnitValue); } } @@ -3297,8 +3321,8 @@ int GTIFSetFromOGISDefnEx(GTIF *psGTIF, OGRSpatialReferenceH hSRS, if (nGCS == KvUserDefined && CPLTestBool(CPLGetConfigOption("GTIFF_ESRI_CITATION", "YES"))) { - SetGeogCSCitation(psGTIF, oMapAsciiKeys, poSRS, angUnitName, - nDatum, nSpheroid); + SetGeogCSCitation(psGTIF, oMapAsciiKeys, poSRS, + osAngUnitName.c_str(), nDatum, nSpheroid); } } } From 5ecffdf697be4882b39877cf4dd2a0cb91411812 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 7 Jun 2024 17:39:16 +0200 Subject: [PATCH 0090/1119] Doc: gtiff.rst: document GTIFF_READ_ANGULAR_PARAMS_IN_DEGREE / GTIFF_WRITE_ANGULAR_PARAMS_IN_DEGREE --- doc/source/drivers/raster/gtiff.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/doc/source/drivers/raster/gtiff.rst b/doc/source/drivers/raster/gtiff.rst index 148d0647fcf4..1f62e2f17eab 100644 --- a/doc/source/drivers/raster/gtiff.rst +++ b/doc/source/drivers/raster/gtiff.rst @@ -1124,6 +1124,34 @@ the default behavior of the GTiff driver. expected to be necessary, unless GDAL is incorrectly determining the disk space available on the destination file system. +- .. config:: GTIFF_READ_ANGULAR_PARAMS_IN_DEGREE + :choices: YES, NO + :default: NO + :since: 3.9.1 + + Conformant GeoTIFF files should have the values of angular projection + parameters written in the unit of the GeogAngularUnitsGeoKey. But some + non-conformant implementations, such as GDAL <= 3.9.0, always wrote them + in degrees. + This option can be set to YES when reading such non-conformant GeoTIFF + files (typically using grads), to instruct GDAL (>= 3.9.1) that the projection + parameters are in degrees, instead of being expressed in the unit of the + GeogAngularUnitsGeoKey. + +- .. config:: GTIFF_WRITE_ANGULAR_PARAMS_IN_DEGREE + :choices: YES, NO + :default: NO + :since: 3.9.1 + + Conformant GeoTIFF files should have the values of angular projection + parameters written in the unit of the GeogAngularUnitsGeoKey. But some + non-conformant implementations, such as GDAL >= 3.0 and <= 3.9.0, assumed + those values to be in degree. + This option can be set to YES to force writing such non-conformant GeoTIFF + files. It should *not* be nominally used, except to workaround interoperability + issues. + + Codec Recommendations --------------------- From e509d3438e053ffc7c537a95e89d517013348541 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 7 Jun 2024 18:32:16 +0200 Subject: [PATCH 0091/1119] Internal libgeotiff: resync with https://github.com/OSGeo/libgeotiff/pull/118 --- frmts/gtiff/gt_wkt_srs.cpp | 17 ++++++++++++- frmts/gtiff/libgeotiff/geo_normalize.c | 33 ++++++++++++++++++++++---- frmts/gtiff/libgeotiff/geo_normalize.h | 12 +++++++--- frmts/gtiff/libgeotiff/geotiff.h | 2 +- 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/frmts/gtiff/gt_wkt_srs.cpp b/frmts/gtiff/gt_wkt_srs.cpp index a1ea06dfac60..919829f2d511 100644 --- a/frmts/gtiff/gt_wkt_srs.cpp +++ b/frmts/gtiff/gt_wkt_srs.cpp @@ -1397,7 +1397,8 @@ OGRSpatialReferenceH GTIFGetOGISDefnAsOSR(GTIF *hGTIF, GTIFDefn *psDefn) for (; i < 10; i++) adfParam[i] = 0.0; - // libgeotiff is unfortunately inconsistent. When it synthetizes the +#if LIBGEOTIFF_VERSION <= 1730 + // libgeotiff <= 1.7.3 is unfortunately inconsistent. When it synthetizes the // projection parameters from the EPSG ProjectedCRS code, it returns // them normalized in degrees. But when it gets them from // ProjCoordTransGeoKey and other Proj....GeoKey's it return them in @@ -1418,6 +1419,20 @@ OGRSpatialReferenceH GTIFGetOGISDefnAsOSR(GTIF *hGTIF, GTIFDefn *psDefn) adfParam[2] *= psDefn->UOMAngleInDegrees; adfParam[3] *= psDefn->UOMAngleInDegrees; } +#else + // If GTIFF_READ_ANGULAR_PARAMS_IN_DEGREE=YES (non-nominal case), undo + // the conversion to degrees, that has been done by libgeotiff > 1.7.3 + if (GDALGTIFKeyGetSHORT(hGTIF, ProjCoordTransGeoKey, &tmp, 0, 1) && + psDefn->UOMAngleInDegrees != 0 && psDefn->UOMAngleInDegrees != 1 && + CPLTestBool(CPLGetConfigOption( + "GTIFF_READ_ANGULAR_PARAMS_IN_DEGREE", "NO"))) + { + adfParam[0] /= psDefn->UOMAngleInDegrees; + adfParam[1] /= psDefn->UOMAngleInDegrees; + adfParam[2] /= psDefn->UOMAngleInDegrees; + adfParam[3] /= psDefn->UOMAngleInDegrees; + } +#endif /* -------------------------------------------------------------------- */ diff --git a/frmts/gtiff/libgeotiff/geo_normalize.c b/frmts/gtiff/libgeotiff/geo_normalize.c index 11517462c761..f16b67be4d9d 100644 --- a/frmts/gtiff/libgeotiff/geo_normalize.c +++ b/frmts/gtiff/libgeotiff/geo_normalize.c @@ -2241,15 +2241,16 @@ static void GTIFFetchProjParms( GTIF * psGTIF, GTIFDefn * psDefn ) break; } + for( int iParam = 0; iParam < psDefn->nParms; iParam++ ) + { + switch( psDefn->ProjParmId[iParam] ) + { + /* -------------------------------------------------------------------- */ /* Normalize any linear parameters into meters. In GeoTIFF */ /* the linear projection parameter tags are normally in the */ /* units of the coordinate system described. */ /* -------------------------------------------------------------------- */ - for( int iParam = 0; iParam < psDefn->nParms; iParam++ ) - { - switch( psDefn->ProjParmId[iParam] ) - { case ProjFalseEastingGeoKey: case ProjFalseNorthingGeoKey: case ProjFalseOriginEastingGeoKey: @@ -2263,6 +2264,30 @@ static void GTIFFetchProjParms( GTIF * psGTIF, GTIFDefn * psDefn ) } break; +/* -------------------------------------------------------------------- */ +/* Normalize any angular parameters into degrees. In GeoTIFF */ +/* the angular projection parameter tags are normally in the */ +/* units of GeogAngularUnit. Note: this conversion is only done */ +/* since libgeotiff 1.7.4 */ +/* -------------------------------------------------------------------- */ + + case ProjStdParallel1GeoKey: + case ProjStdParallel2GeoKey: + case ProjNatOriginLongGeoKey: + case ProjNatOriginLatGeoKey: + case ProjFalseOriginLongGeoKey: + case ProjFalseOriginLatGeoKey: + case ProjCenterLongGeoKey: + case ProjCenterLatGeoKey: + case ProjStraightVertPoleLongGeoKey: + case ProjRectifiedGridAngleGeoKey: + if( psDefn->UOMAngleInDegrees != 0 + && psDefn->UOMAngleInDegrees != 1.0 ) + { + psDefn->ProjParm[iParam] *= psDefn->UOMAngleInDegrees; + } + break; + default: break; } diff --git a/frmts/gtiff/libgeotiff/geo_normalize.h b/frmts/gtiff/libgeotiff/geo_normalize.h index 87dd22c7e7df..79f588c813b9 100644 --- a/frmts/gtiff/libgeotiff/geo_normalize.h +++ b/frmts/gtiff/libgeotiff/geo_normalize.h @@ -121,9 +121,15 @@ typedef struct { int nParms; /** Projection parameter value. The identify of this parameter - is established from the corresponding entry in ProjParmId. The - value will be measured in meters, or decimal degrees if it is a - linear or angular measure. */ + is established from the corresponding entry in ProjParmId. + In GeoTIFF keys, the values of the projection parameters are expressed + in the units of ProjLinearUnitsGeoKey (for linear measures) or + GeogAngularUnitsGeoKey (for angular measures). + However, the value returned in ProjParam[] will be normalized to meters + or decimal degrees. + Note: until libgeotiff 1.7.3, the conversion to degrees for angular + measures was *not* done when ProjCoordTransGeoKey is present. + */ double ProjParm[MAX_GTIF_PROJPARMS]; /** Projection parameter identifier. For example ProjFalseEastingGeoKey. diff --git a/frmts/gtiff/libgeotiff/geotiff.h b/frmts/gtiff/libgeotiff/geotiff.h index 29f22917f4c4..75de593c8a0e 100644 --- a/frmts/gtiff/libgeotiff/geotiff.h +++ b/frmts/gtiff/libgeotiff/geotiff.h @@ -47,7 +47,7 @@ #define GEOTIFF_SPEC_1_1_MINOR_REVISION 1 /* Library version */ -#define LIBGEOTIFF_VERSION 1710 +#define LIBGEOTIFF_VERSION 1740 #include "geo_config.h" #include "geokeys.h" From ea4a0229bf3a657aa6301071b30814314ba7a3d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Fischer?= Date: Sat, 8 Jun 2024 13:25:49 +0200 Subject: [PATCH 0092/1119] OGR_PG_SKIP_CONFLICTS: optionally insert with ON CONFLICT DO NOTHING (#10156) --- autotest/ogr/ogr_pg.py | 54 ++++++++++++++++++++++++++ doc/source/drivers/vector/nas.rst | 4 ++ doc/source/drivers/vector/pg.rst | 11 ++++++ ogr/ogrsf_frmts/pg/ogr_pg.h | 1 + ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp | 13 ++++++- 5 files changed, 82 insertions(+), 1 deletion(-) diff --git a/autotest/ogr/ogr_pg.py b/autotest/ogr/ogr_pg.py index 37d03c4a95b0..8a8ad9a75357 100755 --- a/autotest/ogr/ogr_pg.py +++ b/autotest/ogr/ogr_pg.py @@ -6067,3 +6067,57 @@ def test_ogr_pg_no_postgis_GEOMETRY_NAME(pg_ds): gdal.GetLastErrorMsg() == "GEOMETRY_NAME=foo ignored, and set instead to 'wkb_geometry' as it is the only geometry column name recognized for non-PostGIS enabled databases." ) + + +############################################################################### +# Test ignored conflicts + + +@only_without_postgis +def test_ogr_pg_skip_conflicts(pg_ds): + pg_ds.ExecuteSQL( + "CREATE TABLE test_ogr_skip_conflicts(id SERIAL PRIMARY KEY, gml_id character(16), beginnt character(20), UNIQUE(gml_id, beginnt))" + ) + + with gdal.config_option("OGR_PG_SKIP_CONFLICTS", "YES"): + # OGR_PG_SKIP_CONFLICTS and OGR_PG_RETRIEVE_FID cannot be used at the same time + with gdal.config_option("OGR_PG_RETRIEVE_FID", "YES"): + pg_ds = reconnect(pg_ds, update=1) + lyr = pg_ds.GetLayerByName("test_ogr_skip_conflicts") + feat = ogr.Feature(lyr.GetLayerDefn()) + feat["gml_id"] = "DERPLP0300000cG3" + feat["beginnt"] = "2020-07-10T04:48:14Z" + with gdal.quiet_errors(): + assert lyr.CreateFeature(feat) != ogr.OGRERR_NONE + + with gdal.config_option("OGR_PG_RETRIEVE_FID", "NO"): + pg_ds = reconnect(pg_ds, update=1) + lyr = pg_ds.GetLayerByName("test_ogr_skip_conflicts") + + assert lyr.GetFeatureCount() == 0 + + feat = ogr.Feature(lyr.GetLayerDefn()) + feat["gml_id"] = "DERPLP0300000cG3" + feat["beginnt"] = "2020-07-10T04:48:14Z" + assert lyr.CreateFeature(feat) == ogr.OGRERR_NONE + assert lyr.GetFeatureCount() == 1 + + # Insert w/o OGR_PG_SKIP_CONFLICTS=YES succeeds, but doesn't add a feature + with gdal.config_option("OGR_PG_SKIP_CONFLICTS", "YES"): + pg_ds = reconnect(pg_ds, update=1) + lyr = pg_ds.GetLayerByName("test_ogr_skip_conflicts") + + assert lyr.GetFeatureCount() == 1 + + feat = ogr.Feature(lyr.GetLayerDefn()) + feat["gml_id"] = "DERPLP0300000cG3" + feat["beginnt"] = "2020-07-10T04:48:14Z" + assert lyr.CreateFeature(feat) == ogr.OGRERR_NONE + assert lyr.GetFeatureCount() == 1 + + # Other feature succeeds and increments the feature count + feat = ogr.Feature(lyr.GetLayerDefn()) + feat["gml_id"] = "DERPLP0300000cG4" + feat["beginnt"] = "2020-07-10T04:48:14Z" + assert lyr.CreateFeature(feat) == ogr.OGRERR_NONE + assert lyr.GetFeatureCount() == 2 diff --git a/doc/source/drivers/vector/nas.rst b/doc/source/drivers/vector/nas.rst index af04a7c74d3c..e64f09b10a5e 100644 --- a/doc/source/drivers/vector/nas.rst +++ b/doc/source/drivers/vector/nas.rst @@ -45,6 +45,10 @@ population - which was default in ALKIS-Import. The information found there was redundant to the relation fields also contained in original elements/tables. Enabling the option also made progress reporting available. +Duplicate data in datasets will usually causes errors. When importing separate +datasets into PostgreSQL it is useful to enable "OGR_PG_SKIP_CONFLICTS" to skip +conflicting features. + This driver was implemented within the context of the `PostNAS project `__, which has more information on its use and other related projects. diff --git a/doc/source/drivers/vector/pg.rst b/doc/source/drivers/vector/pg.rst index 7c709f76e475..f1f4bac2d085 100644 --- a/doc/source/drivers/vector/pg.rst +++ b/doc/source/drivers/vector/pg.rst @@ -464,6 +464,17 @@ The following configuration options are available: create) the ``ogr_system_tables.metadata`` table to retrieve and store layer metadata. +- .. config:: OGR_PG_SKIP_CONFLICTS + :choices: YES, NO + :default: NO + :since: 3.10 + + If set to "YES" (not the default), + conflicts when inserting features will be skipped + (requires OGR_PG_RETRIEVE_FID to be off and only applies when PG_USE_COPY + is off). + + Examples ~~~~~~~~ diff --git a/ogr/ogrsf_frmts/pg/ogr_pg.h b/ogr/ogrsf_frmts/pg/ogr_pg.h index 393afb5f9dec..213f4e9665d3 100644 --- a/ogr/ogrsf_frmts/pg/ogr_pg.h +++ b/ogr/ogrsf_frmts/pg/ogr_pg.h @@ -339,6 +339,7 @@ class OGRPGTableLayer final : public OGRPGLayer void CheckGeomTypeCompatibility(int iGeomField, OGRGeometry *poGeom); int bRetrieveFID = true; + int bSkipConflicts = false; int bHasWarnedAlreadySetFID = false; char **papszOverrideColumnTypes = nullptr; diff --git a/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp b/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp index f46f8b393c8e..27883d8877b6 100644 --- a/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp +++ b/ogr/ogrsf_frmts/pg/ogrpgtablelayer.cpp @@ -172,7 +172,9 @@ OGRPGTableLayer::OGRPGTableLayer(OGRPGDataSource *poDSIn, // Just in provision for people yelling about broken backward // compatibility. bRetrieveFID( - CPLTestBool(CPLGetConfigOption("OGR_PG_RETRIEVE_FID", "TRUE"))) + CPLTestBool(CPLGetConfigOption("OGR_PG_RETRIEVE_FID", "TRUE"))), + bSkipConflicts( + CPLTestBool(CPLGetConfigOption("OGR_PG_SKIP_CONFLICTS", "FALSE"))) { poDS = poDSIn; pszQueryStatement = nullptr; @@ -2069,10 +2071,19 @@ OGRErr OGRPGTableLayer::CreateFeatureViaInsert(OGRFeature *poFeature) if (bRetrieveFID && pszFIDColumn != nullptr && poFeature->GetFID() == OGRNullFID) { + if (bSkipConflicts) + { + CPLError(CE_Failure, CPLE_AppDefined, + "fid retrieval and skipping conflicts are not supported " + "at the same time."); + return OGRERR_FAILURE; + } bReturnRequested = TRUE; osCommand += " RETURNING "; osCommand += OGRPGEscapeColumnName(pszFIDColumn); } + else if (bSkipConflicts) + osCommand += " ON CONFLICT DO NOTHING"; /* -------------------------------------------------------------------- */ /* Execute the insert. */ From b07245a2ff94921fb5618b297492bb23ba8b0433 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 8 Jun 2024 18:54:23 +0200 Subject: [PATCH 0093/1119] GTiff writer: limit number of GCPs in GeoTIFF tag to 10922 to avoid overflowing uint16_t Fixes #10164 --- autotest/gcore/tiff_write.py | 36 ++++++++++++++++++++++++++++++ frmts/gtiff/gtiffdataset_write.cpp | 32 +++++++++++++++++++++----- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/autotest/gcore/tiff_write.py b/autotest/gcore/tiff_write.py index a3cd2d6cd0c3..11a2dc8e3e0b 100755 --- a/autotest/gcore/tiff_write.py +++ b/autotest/gcore/tiff_write.py @@ -11523,3 +11523,39 @@ def test_tiff_write_copy_mdd(): ds = None gdal.Unlink(filename) + + +############################################################################### +# Test writing more GCPs than supported + + +@pytest.mark.parametrize("with_initial_gcps", [False, True]) +def test_tiff_write_too_many_gcps(tmp_vsimem, with_initial_gcps): + + filename = str(tmp_vsimem / "test.tif") + ds = gdal.GetDriverByName("GTiff").Create(filename, 1, 1) + if with_initial_gcps: + assert ds.SetGCPs([gdal.GCP(0, 1, 2, 3, 4)] * 10, None) == gdal.CE_None + ds.Close() + ds = gdal.Open(filename, gdal.GA_Update) + gcp_count = int(math.ceil(65535 / 6)) + gcps = [gdal.GCP(0, 1, 2, 3, 4)] * gcp_count + with gdal.quiet_errors(): + assert ds.SetGCPs(gcps, None) == gdal.CE_None + assert ( + f"Trying to write {gcp_count} GCPs, whereas the maximum supported in GeoTIFF tag is 10922. Falling back to writing them to PAM" + in gdal.GetLastErrorMsg() + ) + ds = None + + assert gdal.VSIStatL(filename + ".aux.xml") + + ds = gdal.Open(filename) + assert ds.GetGCPCount() == gcp_count + ds = None + + gdal.Unlink(filename + ".aux.xml") + + ds = gdal.Open(filename) + assert ds.GetGCPCount() == 0 + ds = None diff --git a/frmts/gtiff/gtiffdataset_write.cpp b/frmts/gtiff/gtiffdataset_write.cpp index aa42084d1e6a..c4c17ecfba55 100644 --- a/frmts/gtiff/gtiffdataset_write.cpp +++ b/frmts/gtiff/gtiffdataset_write.cpp @@ -73,6 +73,13 @@ static constexpr const char szPROFILE_BASELINE[] = "BASELINE"; static constexpr const char szPROFILE_GeoTIFF[] = "GeoTIFF"; static constexpr const char szPROFILE_GDALGeoTIFF[] = "GDALGeoTIFF"; +// Due to libgeotiff/xtiff.c declaring TIFFTAG_GEOTIEPOINTS with field_readcount +// and field_writecount == -1 == TIFF_VARIABLE, we are limited to writing +// 65535 values in that tag. That could potentially be overcome by changing the tag +// declaration to using TIFF_VARIABLE2 where the count is a uint32_t. +constexpr int knMAX_GCP_COUNT = + static_cast(std::numeric_limits::max() / 6); + enum { ENDIANNESS_NATIVE, @@ -3695,7 +3702,8 @@ void GTiffDataset::WriteGeoTIFFInfo() else if (CPLFetchBool(m_papszCreationOptions, "WORLDFILE", false)) GDALWriteWorldFile(m_pszFilename, "wld", m_adfGeoTransform); } - else if (GetGCPCount() > 0) + else if (GetGCPCount() > 0 && GetGCPCount() <= knMAX_GCP_COUNT && + m_eProfile != GTiffProfile::BASELINE) { m_bNeedsRewrite = true; @@ -3719,9 +3727,8 @@ void GTiffDataset::WriteGeoTIFFInfo() } } - if (m_eProfile != GTiffProfile::BASELINE) - TIFFSetField(m_hTIFF, TIFFTAG_GEOTIEPOINTS, 6 * GetGCPCount(), - padfTiePoints); + TIFFSetField(m_hTIFF, TIFFTAG_GEOTIEPOINTS, 6 * GetGCPCount(), + padfTiePoints); CPLFree(padfTiePoints); } @@ -8468,7 +8475,6 @@ CPLErr GTiffDataset::SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn, m_bGeoTransformValid = false; m_bForceUnsetGTOrGCPs = true; } - if ((m_eProfile == GTiffProfile::BASELINE) && (GetPamFlags() & GPF_DISABLED) == 0) { @@ -8476,7 +8482,21 @@ CPLErr GTiffDataset::SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn, } else { - if (GDALPamDataset::GetGCPCount() > 0) + if (nGCPCountIn > knMAX_GCP_COUNT) + { + if (GDALPamDataset::GetGCPCount() == 0 && !m_aoGCPs.empty()) + { + m_bForceUnsetGTOrGCPs = true; + } + ReportError(CE_Warning, CPLE_AppDefined, + "Trying to write %d GCPs, whereas the maximum " + "supported in GeoTIFF tag is %d. " + "Falling back to writing them to PAM", + nGCPCountIn, knMAX_GCP_COUNT); + eErr = GDALPamDataset::SetGCPs(nGCPCountIn, pasGCPListIn, + poGCPSRS); + } + else if (GDALPamDataset::GetGCPCount() > 0) { // Cancel any existing GCPs from PAM file. GDALPamDataset::SetGCPs( From 6c00216bd9a39b3c5932e107f1a23ba9e392d20d Mon Sep 17 00:00:00 2001 From: Andrea Giudiceandrea Date: Sun, 9 Jun 2024 09:39:13 +0200 Subject: [PATCH 0094/1119] Fix coordinate_epoch.rst since PROJ 9.4 --- doc/source/user/coordinate_epoch.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/source/user/coordinate_epoch.rst b/doc/source/user/coordinate_epoch.rst index 4e9e852377ae..dfe8e2260fae 100644 --- a/doc/source/user/coordinate_epoch.rst +++ b/doc/source/user/coordinate_epoch.rst @@ -169,9 +169,10 @@ from the source SRS when no SRS related options are specified. :program:`gdalwarp` and :program:`ogr2ogr` have a ``-s_coord_epoch`` option to be used together with ``-s_srs`` (resp. ``-t_coord_epoch`` option to be used together with ``-t_srs``) to override/set the -coordinate epoch of the source (resp. target) CRS. ``-s_coord_epoch`` and -``-t_coord_epoch`` are currently mutually exclusive, due to lack of support for -transformations between two dynamic CRS. +coordinate epoch of the source (resp. target) CRS. + +Before PROJ 9.4, ``-s_coord_epoch`` and ``-t_coord_epoch`` were mutually exclusive, due to lack +of support for transformations between two dynamic CRS. :program:`gdalwarp` preserves the coordinate epoch in the output SRS when appropriate. From 622fe3d2d443e53fb088abee0ba25a136adea31c Mon Sep 17 00:00:00 2001 From: Andrea Giudiceandrea Date: Sun, 9 Jun 2024 09:42:18 +0200 Subject: [PATCH 0095/1119] Fix verb tense in ogr2ogr.rst --- doc/source/programs/ogr2ogr.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/programs/ogr2ogr.rst b/doc/source/programs/ogr2ogr.rst index a45c7395a601..c1a4bb45fc0f 100644 --- a/doc/source/programs/ogr2ogr.rst +++ b/doc/source/programs/ogr2ogr.rst @@ -270,7 +270,7 @@ output coordinate system or even reprojecting the features during translation. output SRS is a dynamic CRS. Only taken into account if :option:`-t_srs` is used. It is also mutually exclusive with :option:`-a_coord_epoch`. - Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` are + Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` were mutually exclusive, due to lack of support for transformations between two dynamic CRS. .. option:: -s_srs @@ -332,7 +332,7 @@ output coordinate system or even reprojecting the features during translation. source SRS is a dynamic CRS. Only taken into account if :option:`-s_srs` is used. - Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` are + Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` were mutually exclusive, due to lack of support for transformations between two dynamic CRS. .. option:: -ct From b11018c8985a8de0c7e1943ba5aafd693bee34f9 Mon Sep 17 00:00:00 2001 From: Andrea Giudiceandrea Date: Sun, 9 Jun 2024 09:49:24 +0200 Subject: [PATCH 0096/1119] Fix verb tense in gdalwarp.rst --- doc/source/programs/gdalwarp.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/programs/gdalwarp.rst b/doc/source/programs/gdalwarp.rst index d21f00e50ece..b32bb931aa3a 100644 --- a/doc/source/programs/gdalwarp.rst +++ b/doc/source/programs/gdalwarp.rst @@ -130,7 +130,7 @@ with control information. source SRS is a dynamic CRS. Only taken into account if :option:`-s_srs` is used. - Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` are + Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` were mutually exclusive, due to lack of support for transformations between two dynamic CRS. .. option:: -t_srs @@ -151,7 +151,7 @@ with control information. target SRS is a dynamic CRS. Only taken into account if :option:`-t_srs` is used. - Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` are + Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` were mutually exclusive, due to lack of support for transformations between two dynamic CRS. .. option:: -ct From e88a7c0b59f9cc01a1156e56daf6309869b1d0c4 Mon Sep 17 00:00:00 2001 From: Andrea Giudiceandrea Date: Sun, 9 Jun 2024 09:50:20 +0200 Subject: [PATCH 0097/1119] Fix verb tense in gdaltransform.rst --- doc/source/programs/gdaltransform.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/programs/gdaltransform.rst b/doc/source/programs/gdaltransform.rst index 076bc9f5dd6a..22eb21670c70 100644 --- a/doc/source/programs/gdaltransform.rst +++ b/doc/source/programs/gdaltransform.rst @@ -50,7 +50,7 @@ projection,including GCP-based transformations. source SRS is a dynamic CRS. Only taken into account if :option:`-s_srs` is used. - Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` are + Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` were mutually exclusive, due to lack of support for transformations between two dynamic CRS. .. option:: -t_srs @@ -69,7 +69,7 @@ projection,including GCP-based transformations. output SRS is a dynamic CRS. Only taken into account if :option:`-t_srs` is used. - Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` are + Before PROJ 9.4, :option:`-s_coord_epoch` and :option:`-t_coord_epoch` were mutually exclusive, due to lack of support for transformations between two dynamic CRS. .. option:: -ct From 032e15f04791df1b600552acff750e146ba0be2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A9=8D=E4=B8=B9=E5=B0=BC=20Dan=20Jacobson?= Date: Sun, 9 Jun 2024 22:44:43 +0800 Subject: [PATCH 0098/1119] Update csv.rst adding very first CSV to CSV example (#10169) https://gis.stackexchange.com/questions/482410/how-to-use-ogr2ogr-segmentize-on-a-csv-file/482411 --- doc/source/drivers/vector/csv.rst | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/doc/source/drivers/vector/csv.rst b/doc/source/drivers/vector/csv.rst index 9fdea08e0451..709f0dd479d8 100644 --- a/doc/source/drivers/vector/csv.rst +++ b/doc/source/drivers/vector/csv.rst @@ -524,7 +524,8 @@ Examples :: - ogr2ogr -f CSV -dialect sqlite -sql "select AsGeoJSON(geometry) AS geom, * from input" output.csv input.shp + ogr2ogr -f CSV output.csv input.shp -dialect sqlite -sql \ + "select AsGeoJSON(geometry) AS geom, * from input" - Convert a CSV into a GeoPackage. Specify the names of the coordinate columns and assign a coordinate reference system. @@ -537,6 +538,21 @@ Examples -oo Y_POSSIBLE_NAMES=latitude \ -a_srs 'EPSG:4326' +- Use `ogr2ogr -segmentize` to densify a input geometry being specified in the ``WKT`` special field. Note that one needs to specify the GEOMETRY=AS_WKT layer creation option, otherwise the input geometry would be returned unmodified: + + :: + + $ cat input.csv + WKT,ID,Name + "LINESTRING (-900 -1450,-900 100)",0,900W + + $ ogr2ogr -segmentize 400 -lco GEOMETRY=AS_WKT \ + -sql "SELECT ID, Name FROM input" output.csv input.csv + + $ cat output.csv + WKT,ID,Name + "LINESTRING (-900 -1450,-900 -1062.5,-900 -675,-900 -287.5,-900 100)","0",900W + Particular datasources ---------------------- From e473249eb22eb674d60c3507e31e395da569a2e0 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 9 Jun 2024 21:52:42 +0200 Subject: [PATCH 0099/1119] Parquet: fix Coverity Scan warnings --- ogr/ogrsf_frmts/parquet/ogrparquetdatasetlayer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ogr/ogrsf_frmts/parquet/ogrparquetdatasetlayer.cpp b/ogr/ogrsf_frmts/parquet/ogrparquetdatasetlayer.cpp index e0f76cf999ed..5f6271783f80 100644 --- a/ogr/ogrsf_frmts/parquet/ogrparquetdatasetlayer.cpp +++ b/ogr/ogrsf_frmts/parquet/ogrparquetdatasetlayer.cpp @@ -469,7 +469,7 @@ void OGRParquetDatasetLayer::BuildScanner() { pszUseThreads = "YES"; } - if (CPLTestBool(pszUseThreads)) + if (pszUseThreads && CPLTestBool(pszUseThreads)) { PARQUET_THROW_NOT_OK(scannerBuilder->UseThreads(true)); } @@ -607,9 +607,9 @@ void OGRParquetDatasetLayer::BuildScanner() wkbVariantIso); // Silence 'Using uninitialized value oFieldRef. Field oFieldRef.impl_._M_u is uninitialized when calling FieldRef.' // coverity[uninit_use_in_call] - expression = - cp::call("OGRWKBIntersects", {cp::field_ref(oFieldRef)}, - WKBGeometryOptions(abyFilterGeomWkb)); + expression = cp::call("OGRWKBIntersects", + {cp::field_ref(std::move(oFieldRef))}, + WKBGeometryOptions(abyFilterGeomWkb)); if (expression.is_valid()) { From 1a0e673cec7395e3ef4031a5b7682d74632e0faa Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 10 Jun 2024 14:44:18 +0200 Subject: [PATCH 0100/1119] CI: workaround issue with dnf upgrade with fedora:rawhide --- .github/workflows/fedora_rawhide/Dockerfile.ci | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/fedora_rawhide/Dockerfile.ci b/.github/workflows/fedora_rawhide/Dockerfile.ci index 584c58369f73..cb764b75ae00 100644 --- a/.github/workflows/fedora_rawhide/Dockerfile.ci +++ b/.github/workflows/fedora_rawhide/Dockerfile.ci @@ -1,6 +1,9 @@ FROM fedora:rawhide -RUN dnf upgrade -y +# FIXME: Exclude update of dnf&rpm themselves as this results in a no longer working dnf +# cf https://github.com/OSGeo/gdal/actions/runs/9448190401/job/26021669415?pr=10173 +# Likely a transient issue with Fedora 41 dev cycle +RUN dnf upgrade -y -x dnf -x rpm RUN dnf install -y --setopt=install_weak_deps=False proj-devel RUN dnf install -y clang make diffutils ccache cmake \ libxml2-devel libxslt-devel expat-devel xerces-c-devel \ From d606fe044aa44d05d2972fa53b99244f5a451f00 Mon Sep 17 00:00:00 2001 From: sab56 Date: Mon, 10 Jun 2024 14:08:23 +0100 Subject: [PATCH 0101/1119] Added DTED metadata items Security Control and Security Handling (#10173) --- frmts/dted/dted_api.c | 10 ++++++++++ frmts/dted/dted_api.h | 4 +++- frmts/dted/dteddataset.cpp | 8 ++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/frmts/dted/dted_api.c b/frmts/dted/dted_api.c index eadde1c76916..825d5c68a65d 100644 --- a/frmts/dted/dted_api.c +++ b/frmts/dted/dted_api.c @@ -1009,6 +1009,16 @@ static void DTEDGetMetadataLocation(DTEDInfo *psDInfo, DTEDMetaDataCode eCode, *pnLength = 2; break; + case DTEDMD_SECURITYCONTROL: + *ppszLocation = psDInfo->pachDSIRecord + 4; + *pnLength = 2; + break; + + case DTEDMD_SECURITYHANDLING: + *ppszLocation = psDInfo->pachDSIRecord + 6; + *pnLength = 27; + break; + default: *ppszLocation = NULL; *pnLength = 0; diff --git a/frmts/dted/dted_api.h b/frmts/dted/dted_api.h index 3d0fef89a134..4bf573450bdd 100644 --- a/frmts/dted/dted_api.h +++ b/frmts/dted/dted_api.h @@ -194,7 +194,9 @@ typedef enum DTEDMD_ORIGINLAT = 21, /* UHL 13+7 */ DTEDMD_NIMA_DESIGNATOR = 22, /* DSI 60 + 5 */ DTEDMD_PARTIALCELL_DSI = 23, /* DSI 289 + 2 */ - DTEDMD_MAX = 23 + DTEDMD_SECURITYCONTROL = 24, + DTEDMD_SECURITYHANDLING = 25, + DTEDMD_MAX = 25 } DTEDMetaDataCode; char *DTEDGetMetadata(DTEDInfo *, DTEDMetaDataCode); diff --git a/frmts/dted/dteddataset.cpp b/frmts/dted/dteddataset.cpp index 07cbc576f110..9ed5b38df56f 100644 --- a/frmts/dted/dteddataset.cpp +++ b/frmts/dted/dteddataset.cpp @@ -464,6 +464,14 @@ GDALDataset *DTEDDataset::Open(GDALOpenInfo *poOpenInfo) poDS->SetMetadataItem("DTED_PartialCellIndicator", pszValue); CPLFree(pszValue); + pszValue = DTEDGetMetadata(psDTED, DTEDMD_SECURITYCONTROL); + poDS->SetMetadataItem("DTED_SecurityControl", pszValue); + CPLFree(pszValue); + + pszValue = DTEDGetMetadata(psDTED, DTEDMD_SECURITYHANDLING); + poDS->SetMetadataItem("DTED_SecurityHandling", pszValue); + CPLFree(pszValue); + poDS->SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT); /* -------------------------------------------------------------------- */ From ba24fb29266bb56611d7c5df1d48792810486aaa Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 28 May 2024 17:15:38 +0200 Subject: [PATCH 0102/1119] Add VSIMultipartUploadXXXXX() API for multi-part upload Using this API directly is generally not needed, but in very advanced cases, as VSIFOpenL(..., "wb") + VSIFWriteL(), VSISync(), VSICopyFile() or VSICopyFileRestartable() may be able to leverage it when needed. This is only implemented for the /vsis3/, /vsigs/, /vsiaz/, /vsiadls/ and /vsioss/ virtual file systems. The typical workflow is to do : - VSIMultipartUploadStart() - VSIMultipartUploadAddPart(): several times - VSIMultipartUploadEnd() If VSIMultipartUploadAbort() is supported by the filesystem (VSIMultipartUploadGetCapabilities() can be used to determine it), this function should be called to cancel an upload. This can be needed to avoid extra billing for some cloud storage providers. --- autotest/gcore/vsiadls.py | 53 +++++ autotest/gcore/vsiaz.py | 27 +++ autotest/gcore/vsifile.py | 62 ++++++ autotest/gcore/vsigs.py | 16 ++ autotest/gcore/vsioss.py | 16 ++ autotest/gcore/vsis3.py | 133 +++++++++++++ port/cpl_vsi.h | 23 +++ port/cpl_vsi_virtual.h | 25 +++ port/cpl_vsil.cpp | 270 +++++++++++++++++++++++++- port/cpl_vsil_adls.cpp | 47 ++++- port/cpl_vsil_az.cpp | 19 +- port/cpl_vsil_curl_class.h | 50 +++++ port/cpl_vsil_gs.cpp | 13 +- port/cpl_vsil_oss.cpp | 7 +- port/cpl_vsil_s3.cpp | 129 +++++++++++- swig/include/cpl.i | 80 ++++++++ swig/include/python/gdal_python.i | 22 +++ swig/include/python/typemaps_python.i | 52 +++++ 18 files changed, 1018 insertions(+), 26 deletions(-) diff --git a/autotest/gcore/vsiadls.py b/autotest/gcore/vsiadls.py index ee6995d08a7b..11f3a6ea56a8 100755 --- a/autotest/gcore/vsiadls.py +++ b/autotest/gcore/vsiadls.py @@ -1161,3 +1161,56 @@ def test_vsiadls_fake_metadata(): assert not gdal.SetFileMetadata( "/vsiadls/test/foo.bin", {"x-ms-properties": "foo=bar"}, "PROPERTIES" ) + + +############################################################################### +# Test VSIMultipartUploadXXXX() + + +def test_vsiadls_MultipartUpload(): + + if gdaltest.webserver_port == 0: + pytest.skip() + + # Test MultipartUploadGetCapabilities() + info = gdal.MultipartUploadGetCapabilities("/vsiadls/") + assert info.non_sequential_upload_supported + assert info.parallel_upload_supported + assert not info.abort_supported + assert info.min_part_size == 0 + assert info.max_part_size >= 1024 + assert info.max_part_count > 0 + + # Test MultipartUploadAddPart() + handler = webserver.SequentialHandler() + handler.add( + "PATCH", + "/azure/blob/myaccount/test_multipartupload/test.bin?action=append&position=123456", + 202, + expected_body=b"foo", + ) + with webserver.install_http_handler(handler): + part_id = gdal.MultipartUploadAddPart( + "/vsiadls/test_multipartupload/test.bin", "my_upload_id", 1, 123456, b"foo" + ) + assert part_id == "dummy" + + # Test MultipartUploadEnd() + handler = webserver.SequentialHandler() + handler.add( + "PATCH", + "/azure/blob/myaccount/test_multipartupload/test.bin?action=flush&close=true&position=3", + 200, + ) + with webserver.install_http_handler(handler): + assert gdal.MultipartUploadEnd( + "/vsiadls/test_multipartupload/test.bin", "my_upload_id", ["dummy"], 3 + ) + + # Test unsupported MultipartUploadAbort() + with gdal.ExceptionMgr(useExceptions=True): + with pytest.raises( + Exception, + match=r"MultipartUploadAbort\(\) not supported by this file system", + ): + gdal.MultipartUploadAbort("/vsiadls/foo/bar", "upload_id") diff --git a/autotest/gcore/vsiaz.py b/autotest/gcore/vsiaz.py index bcd16dbe327b..74f44634ceda 100755 --- a/autotest/gcore/vsiaz.py +++ b/autotest/gcore/vsiaz.py @@ -3059,3 +3059,30 @@ def method(request): ) == 0 ) + + +############################################################################### +# Test VSIMultipartUploadXXXX() + + +def test_vsiaz_MultipartUpload(): + + if gdaltest.webserver_port == 0: + pytest.skip() + + # Test MultipartUploadGetCapabilities() + info = gdal.MultipartUploadGetCapabilities("/vsiaz/") + assert info.non_sequential_upload_supported + assert info.parallel_upload_supported + assert not info.abort_supported + assert info.min_part_size == 0 + assert info.max_part_size >= 1024 + assert info.max_part_count == 50000 + + # Test unsupported MultipartUploadAbort() + with gdal.ExceptionMgr(useExceptions=True): + with pytest.raises( + Exception, + match=r"MultipartUploadAbort\(\) not supported by this file system", + ): + gdal.MultipartUploadAbort("/vsiaz/foo/bar", "upload_id") diff --git a/autotest/gcore/vsifile.py b/autotest/gcore/vsifile.py index ec13bbc5ad5e..1aa63cd9082d 100755 --- a/autotest/gcore/vsifile.py +++ b/autotest/gcore/vsifile.py @@ -1696,3 +1696,65 @@ def test_vsifile_stat_directory_trailing_slash(): res = gdal.VSIStatL("data/") assert res assert res.IsDirectory() + + +############################################################################### +# Test VSIMultipartUploadXXXX(), unsupported on regular file systems + + +def test_vsifile_MultipartUpload(): + + with gdal.ExceptionMgr(useExceptions=False): + with gdal.quiet_errors(): + assert gdal.MultipartUploadGetCapabilities("foo") is None + with gdal.ExceptionMgr(useExceptions=True): + with pytest.raises(ValueError): + gdal.MultipartUploadGetCapabilities(None) + + with pytest.raises( + Exception, + match=r"MultipartUploadGetCapabilities\(\) not supported by this file system", + ): + gdal.MultipartUploadGetCapabilities("foo") + + with pytest.raises(ValueError): + gdal.MultipartUploadStart(None) + + with pytest.raises( + Exception, + match=r"MultipartUploadStart\(\) not supported by this file system", + ): + gdal.MultipartUploadStart("foo") + + with pytest.raises(ValueError): + gdal.MultipartUploadAddPart(None, "", 1, 0, b"") + with pytest.raises(ValueError): + gdal.MultipartUploadAddPart("", None, 1, 0, b"") + + with pytest.raises( + Exception, + match=r"MultipartUploadAddPart\(\) not supported by this file system", + ): + gdal.MultipartUploadAddPart("", "", 1, 0, b"") + + with pytest.raises(ValueError): + gdal.MultipartUploadEnd(None, "", [], 0) + with pytest.raises(ValueError): + gdal.MultipartUploadEnd("", None, [], 0) + + with pytest.raises( + Exception, + match=r"MultipartUploadEnd\(\) not supported by this file system", + ): + gdal.MultipartUploadEnd("", "", [], 0) + + with pytest.raises(ValueError): + gdal.MultipartUploadAbort(None, "") + with pytest.raises(ValueError): + gdal.MultipartUploadAbort("", None) + + with pytest.raises( + Exception, + match=r"MultipartUploadAbort\(\) not supported by this file system", + ): + gdal.MultipartUploadAbort("", "") diff --git a/autotest/gcore/vsigs.py b/autotest/gcore/vsigs.py index 56b61088417a..75cbdad3198f 100755 --- a/autotest/gcore/vsigs.py +++ b/autotest/gcore/vsigs.py @@ -1614,6 +1614,22 @@ def my_error_handler(errclass, errno, errmsg): assert not error[0] +############################################################################### +# Test VSIMultipartUploadXXXX() + + +def test_vsigs_MultipartUpload(gs_test_config, webserver_port): + + # Test MultipartUploadGetCapabilities() + info = gdal.MultipartUploadGetCapabilities("/vsigs/") + assert info.non_sequential_upload_supported + assert info.parallel_upload_supported + assert info.abort_supported + assert info.min_part_size == 5 + assert info.max_part_size >= 1024 + assert info.max_part_count == 10000 + + ############################################################################### # Nominal cases (require valid credentials) diff --git a/autotest/gcore/vsioss.py b/autotest/gcore/vsioss.py index a98f3661aa78..9aee95da8d9c 100755 --- a/autotest/gcore/vsioss.py +++ b/autotest/gcore/vsioss.py @@ -1118,6 +1118,22 @@ def method(request): assert gdal.GetLastErrorMsg() != "", filename +############################################################################### +# Test VSIMultipartUploadXXXX() + + +def test_vsioss_MultipartUpload(server): + + # Test MultipartUploadGetCapabilities() + info = gdal.MultipartUploadGetCapabilities("/vsioss/") + assert info.non_sequential_upload_supported + assert info.parallel_upload_supported + assert info.abort_supported + assert info.min_part_size == 5 + assert info.max_part_size >= 1024 + assert info.max_part_count == 10000 + + ############################################################################### # Test Mkdir() / Rmdir() diff --git a/autotest/gcore/vsis3.py b/autotest/gcore/vsis3.py index ad0710057809..f5d42c6030ad 100755 --- a/autotest/gcore/vsis3.py +++ b/autotest/gcore/vsis3.py @@ -6060,6 +6060,139 @@ def test_vsis3_DISABLE_READDIR_ON_OPEN_option(aws_test_config, webserver_port): gdal.VSIFCloseL(f) +############################################################################### +# Test VSIMultipartUploadXXXX() + + +def test_vsis3_MultipartUpload(aws_test_config, webserver_port): + + # Test MultipartUploadGetCapabilities() + info = gdal.MultipartUploadGetCapabilities("/vsis3/") + assert info.non_sequential_upload_supported + assert info.parallel_upload_supported + assert info.abort_supported + assert info.min_part_size == 5 + assert info.max_part_size >= 1024 + assert info.max_part_count == 10000 + + # Test MultipartUploadStart() + handler = webserver.SequentialHandler() + handler.add("POST", "/test_multipartupload/test.bin?uploads", 400) + with webserver.install_http_handler(handler), gdal.quiet_errors(): + upload_id = gdal.MultipartUploadStart("/vsis3/test_multipartupload/test.bin") + assert upload_id is None + + handler = webserver.SequentialHandler() + handler.add( + "POST", + "/test_multipartupload/test.bin?uploads", + 200, + {}, + """xml version="1.0" encoding="UTF-8"?> + + my_upload_id + """, + ) + with webserver.install_http_handler(handler): + upload_id = gdal.MultipartUploadStart("/vsis3/test_multipartupload/test.bin") + assert upload_id == "my_upload_id" + + # Test MultipartUploadAddPart() + handler = webserver.SequentialHandler() + handler.add( + "PUT", + "/test_multipartupload/test.bin?partNumber=1&uploadId=my_upload_id", + 400, + ) + with webserver.install_http_handler(handler), gdal.quiet_errors(): + part_id = gdal.MultipartUploadAddPart( + "/vsis3/test_multipartupload/test.bin", "my_upload_id", 1, 0, b"foo" + ) + assert part_id is None + + handler = webserver.SequentialHandler() + handler.add( + "PUT", + "/test_multipartupload/test.bin?partNumber=1&uploadId=my_upload_id", + 200, + {"ETag": '"my_part_id"', "Content-Length": "0"}, + b"", + expected_body=b"foo", + ) + with webserver.install_http_handler(handler): + part_id = gdal.MultipartUploadAddPart( + "/vsis3/test_multipartupload/test.bin", "my_upload_id", 1, 0, b"foo" + ) + assert part_id == '"my_part_id"' + + # Test MultipartUploadEnd() + + handler = webserver.SequentialHandler() + handler.add("POST", "/test_multipartupload/test.bin?uploadId=my_upload_id", 400) + with webserver.install_http_handler(handler), gdal.quiet_errors(): + assert not gdal.MultipartUploadEnd( + "/vsis3/test_multipartupload/test.bin", "my_upload_id", ['"my_part_id"'], 3 + ) + + handler = webserver.SequentialHandler() + handler.add( + "POST", + "/test_multipartupload/test.bin?uploadId=my_upload_id", + 200, + expected_body=b""" + +1"my_part_id" + +""", + ) + with webserver.install_http_handler(handler): + assert gdal.MultipartUploadEnd( + "/vsis3/test_multipartupload/test.bin", "my_upload_id", ['"my_part_id"'], 3 + ) + + # Test MultipartUploadAbort() + + handler = webserver.SequentialHandler() + handler.add("DELETE", "/test_multipartupload/test.bin?uploadId=my_upload_id", 400) + with webserver.install_http_handler(handler), gdal.quiet_errors(): + assert not gdal.MultipartUploadAbort( + "/vsis3/test_multipartupload/test.bin", "my_upload_id" + ) + + handler = webserver.SequentialHandler() + handler.add("DELETE", "/test_multipartupload/test.bin?uploadId=my_upload_id", 204) + with webserver.install_http_handler(handler): + assert gdal.MultipartUploadAbort( + "/vsis3/test_multipartupload/test.bin", "my_upload_id" + ) + + +############################################################################### +# Test VSIMultipartUploadXXXX() when authentication fails + + +def test_vsis3_MultipartUpload_unauthenticated(): + + options = { + "AWS_SECRET_ACCESS_KEY": "", + "AWS_ACCESS_KEY_ID": "", + } + with gdaltest.config_options(options, thread_local=False), gdal.quiet_errors(): + assert gdal.MultipartUploadStart("/vsis3/test_multipartupload/test.bin") is None + assert ( + gdal.MultipartUploadAddPart( + "/vsis3/test_multipartupload/test.bin", "my_upload_id", 1, 0, b"foo" + ) + is None + ) + assert not gdal.MultipartUploadEnd( + "/vsis3/test_multipartupload/test.bin", "my_upload_id", ['"my_part_id"'], 3 + ) + assert not gdal.MultipartUploadAbort( + "/vsis3/test_multipartupload/test.bin", "my_upload_id" + ) + + ############################################################################### # Nominal cases (require valid credentials) diff --git a/port/cpl_vsi.h b/port/cpl_vsi.h index 0561eef65e8e..634f599451e8 100644 --- a/port/cpl_vsi.h +++ b/port/cpl_vsi.h @@ -465,6 +465,29 @@ int CPL_DLL VSISync(const char *pszSource, const char *pszTarget, const char *const *papszOptions, GDALProgressFunc pProgressFunc, void *pProgressData, char ***ppapszOutputs); + +int CPL_DLL VSIMultipartUploadGetCapabilities( + const char *pszFilename, int *pbNonSequentialUploadSupported, + int *pbParallelUploadSupported, int *pbAbortSupported, + size_t *pnMinPartSize, size_t *pnMaxPartSize, int *pnMaxPartCount); + +char CPL_DLL *VSIMultipartUploadStart(const char *pszFilename, + CSLConstList papszOptions); +char CPL_DLL *VSIMultipartUploadAddPart(const char *pszFilename, + const char *pszUploadId, + int nPartNumber, + vsi_l_offset nFileOffset, + const void *pData, size_t nDataLength, + CSLConstList papszOptions); +int CPL_DLL VSIMultipartUploadEnd(const char *pszFilename, + const char *pszUploadId, size_t nPartIdsCount, + const char *const *apszPartIds, + vsi_l_offset nTotalSize, + CSLConstList papszOptions); +int CPL_DLL VSIMultipartUploadAbort(const char *pszFilename, + const char *pszUploadId, + CSLConstList papszOptions); + int CPL_DLL VSIAbortPendingUploads(const char *pszFilename); char CPL_DLL *VSIStrerror(int); diff --git a/port/cpl_vsi_virtual.h b/port/cpl_vsi_virtual.h index 915ff79a9a87..b223c49eb999 100644 --- a/port/cpl_vsi_virtual.h +++ b/port/cpl_vsi_virtual.h @@ -305,6 +305,31 @@ class CPL_DLL VSIFilesystemHandler const char *pszDomain, CSLConstList papszOptions); + virtual bool + MultipartUploadGetCapabilities(int *pbNonSequentialUploadSupported, + int *pbParallelUploadSupported, + int *pbAbortSupported, size_t *pnMinPartSize, + size_t *pnMaxPartSize, int *pnMaxPartCount); + + virtual char *MultipartUploadStart(const char *pszFilename, + CSLConstList papszOptions); + + virtual char *MultipartUploadAddPart(const char *pszFilename, + const char *pszUploadId, + int nPartNumber, + vsi_l_offset nFileOffset, + const void *pData, size_t nDataLength, + CSLConstList papszOptions); + + virtual bool + MultipartUploadEnd(const char *pszFilename, const char *pszUploadId, + size_t nPartIdsCount, const char *const *apszPartIds, + vsi_l_offset nTotalSize, CSLConstList papszOptions); + + virtual bool MultipartUploadAbort(const char *pszFilename, + const char *pszUploadId, + CSLConstList papszOptions); + virtual bool AbortPendingUploads(const char * /*pszFilename*/) { return true; diff --git a/port/cpl_vsil.cpp b/port/cpl_vsil.cpp index 9d781e3b12bd..61d9a535041e 100644 --- a/port/cpl_vsil.cpp +++ b/port/cpl_vsil.cpp @@ -794,18 +794,286 @@ int VSISync(const char *pszSource, const char *pszTarget, : FALSE; } +/************************************************************************/ +/* VSIMultipartUploadGetCapabilities() */ +/************************************************************************/ + +/** + * \brief Return capabilities for multiple part file upload. + * + * @param pszFilename Filename, or virtual file system prefix, onto which + * capabilities should apply. + * @param[out] pbNonSequentialUploadSupported If not null, + * the pointed value is set if parts can be uploaded in a non-sequential way. + * @param[out] pbParallelUploadSupported If not null, + * the pointed value is set if parts can be uploaded in a parallel way. + * (implies *pbNonSequentialUploadSupported = true) + * @param[out] pbAbortSupported If not null, + * the pointed value is set if VSIMultipartUploadAbort() is implemented. + * @param[out] pnMinPartSize If not null, the pointed value is set to the minimum + * size of parts (but the last one), in MiB. + * @param[out] pnMaxPartSize If not null, the pointed value is set to the maximum + * size of parts, in MiB. + * @param[out] pnMaxPartCount If not null, the pointed value is set to the + * maximum number of parts that can be uploaded. + * + * @return TRUE in case of success, FALSE otherwise. + * + * @since 3.10 + */ +int VSIMultipartUploadGetCapabilities( + const char *pszFilename, int *pbNonSequentialUploadSupported, + int *pbParallelUploadSupported, int *pbAbortSupported, + size_t *pnMinPartSize, size_t *pnMaxPartSize, int *pnMaxPartCount) +{ + VSIFilesystemHandler *poFSHandler = VSIFileManager::GetHandler(pszFilename); + + return poFSHandler->MultipartUploadGetCapabilities( + pbNonSequentialUploadSupported, pbParallelUploadSupported, + pbAbortSupported, pnMinPartSize, pnMaxPartSize, pnMaxPartCount); +} + +/************************************************************************/ +/* VSIMultipartUploadStart() */ +/************************************************************************/ + +/** + * \brief Initiates the upload a (big) file in a piece-wise way. + * + * Using this API directly is generally not needed, but in very advanced cases, + * as VSIFOpenL(..., "wb") + VSIFWriteL(), VSISync(), VSICopyFile() or + * VSICopyFileRestartable() may be able to leverage it when needed. + * + * This is only implemented for the /vsis3/, /vsigs/, /vsiaz/, /vsiadls/ and + * /vsioss/ virtual file systems. + * + * The typical workflow is to do : + * - VSIMultipartUploadStart() + * - VSIMultipartUploadAddPart(): several times + * - VSIMultipartUploadEnd() + * + * If VSIMultipartUploadAbort() is supported by the filesystem (VSIMultipartUploadGetCapabilities() + * can be used to determine it), this function should be called to cancel an + * upload. This can be needed to avoid extra billing for some cloud storage + * providers. + * + * The following options are supported: + *
      + *
    • MIME headers such as Content-Type and Content-Encoding + * are supported for the /vsis3/, /vsigs/, /vsiaz/, /vsiadls/ file systems.
    • + *
    + * + * @param pszFilename Filename to create + * @param papszOptions NULL or null-terminated list of options. + * @return an upload ID to pass to other VSIMultipartUploadXXXXX() functions, + * and to free with CPLFree() once done, or nullptr in case of error. + * + * @since 3.10 + */ +char *VSIMultipartUploadStart(const char *pszFilename, + CSLConstList papszOptions) +{ + VSIFilesystemHandler *poFSHandler = VSIFileManager::GetHandler(pszFilename); + + return poFSHandler->MultipartUploadStart(pszFilename, papszOptions); +} + +/************************************************************************/ +/* VSIMultipartUploadAddPart() */ +/************************************************************************/ + +/** + * \brief Uploads a new part to a multi-part uploaded file. + * + * Cf VSIMultipartUploadStart(). + * + * VSIMultipartUploadGetCapabilities() returns hints on the constraints that + * apply to the upload, in terms of minimum/maximum size of each part, maximum + * number of parts, and whether non-sequential or parallel uploads are + * supported. + * + * @param pszFilename Filename to which to append the new part. Should be the + * same as the one used for VSIMultipartUploadStart() + * @param pszUploadId Value returned by VSIMultipartUploadStart() + * @param nPartNumber Part number, starting at 1. + * @param nFileOffset Offset within the file at which (starts at 0) the passed + * data starts. + * @param pData Pointer to an array of nDataLength bytes. + * @param nDataLength Size in bytes of pData. + * @param papszOptions Unused. Should be nullptr. + * + * @return a part identifier that must be passed into the apszPartIds[] array of + * VSIMultipartUploadEnd(), and to free with CPLFree() once done, or nullptr in + * case of error. + * + * @since 3.10 + */ +char *VSIMultipartUploadAddPart(const char *pszFilename, + const char *pszUploadId, int nPartNumber, + vsi_l_offset nFileOffset, const void *pData, + size_t nDataLength, CSLConstList papszOptions) +{ + VSIFilesystemHandler *poFSHandler = VSIFileManager::GetHandler(pszFilename); + + return poFSHandler->MultipartUploadAddPart(pszFilename, pszUploadId, + nPartNumber, nFileOffset, pData, + nDataLength, papszOptions); +} + +/************************************************************************/ +/* VSIMultipartUploadEnd() */ +/************************************************************************/ + +/** + * \brief Completes a multi-part file upload. + * + * Cf VSIMultipartUploadStart(). + * + * @param pszFilename Filename for which multipart upload should be completed. + * Should be the same as the one used for + * VSIMultipartUploadStart() + * @param pszUploadId Value returned by VSIMultipartUploadStart() + * @param nPartIdsCount Number of parts, andsize of apszPartIds + * @param apszPartIds Array of part identifiers (as returned by + * VSIMultipartUploadAddPart()), that must be ordered in + * the sequential order of parts, and of size nPartIdsCount. + * @param nTotalSize Total size of the file in bytes (must be equal to the sum + * of nDataLength passed to VSIMultipartUploadAddPart()) + * @param papszOptions Unused. Should be nullptr. + * + * @return TRUE in case of success, FALSE in case of failure. + * + * @since 3.10 + */ +int VSIMultipartUploadEnd(const char *pszFilename, const char *pszUploadId, + size_t nPartIdsCount, const char *const *apszPartIds, + vsi_l_offset nTotalSize, CSLConstList papszOptions) +{ + VSIFilesystemHandler *poFSHandler = VSIFileManager::GetHandler(pszFilename); + + return poFSHandler->MultipartUploadEnd(pszFilename, pszUploadId, + nPartIdsCount, apszPartIds, + nTotalSize, papszOptions); +} + +/************************************************************************/ +/* VSIMultipartUploadAbort() */ +/************************************************************************/ + +/** + * \brief Aborts a multi-part file upload. + * + * Cf VSIMultipartUploadStart(). + * + * This function is not implemented for all virtual file systems. + * Use VSIMultipartUploadGetCapabilities() to determine if it is supported. + * + * This can be needed to avoid extra billing for some cloud storage providers. + * + * @param pszFilename Filename for which multipart upload should be completed. + * Should be the same as the one used for + * VSIMultipartUploadStart() + * @param pszUploadId Value returned by VSIMultipartUploadStart() + * @param papszOptions Unused. Should be nullptr. + * + * @return TRUE in case of success, FALSE in case of failure. + * + * @since 3.10 + */ +int VSIMultipartUploadAbort(const char *pszFilename, const char *pszUploadId, + CSLConstList papszOptions) +{ + VSIFilesystemHandler *poFSHandler = VSIFileManager::GetHandler(pszFilename); + + return poFSHandler->MultipartUploadAbort(pszFilename, pszUploadId, + papszOptions); +} + +#ifndef DOXYGEN_SKIP + +/************************************************************************/ +/* MultipartUploadGetCapabilities() */ +/************************************************************************/ + +bool VSIFilesystemHandler::MultipartUploadGetCapabilities(int *, int *, int *, + size_t *, size_t *, + int *) +{ + CPLError( + CE_Failure, CPLE_NotSupported, + "MultipartUploadGetCapabilities() not supported by this file system"); + return false; +} + +/************************************************************************/ +/* MultipartUploadStart() */ +/************************************************************************/ + +char *VSIFilesystemHandler::MultipartUploadStart(const char *, CSLConstList) +{ + CPLError(CE_Failure, CPLE_NotSupported, + "MultipartUploadStart() not supported by this file system"); + return nullptr; +} + +/************************************************************************/ +/* MultipartUploadAddPart() */ +/************************************************************************/ + +char *VSIFilesystemHandler::MultipartUploadAddPart(const char *, const char *, + int, vsi_l_offset, + const void *, size_t, + CSLConstList) +{ + CPLError(CE_Failure, CPLE_NotSupported, + "MultipartUploadAddPart() not supported by this file system"); + return nullptr; +} + +/************************************************************************/ +/* MultipartUploadEnd() */ +/************************************************************************/ + +bool VSIFilesystemHandler::MultipartUploadEnd(const char *, const char *, + size_t, const char *const *, + vsi_l_offset, CSLConstList) +{ + CPLError(CE_Failure, CPLE_NotSupported, + "MultipartUploadEnd() not supported by this file system"); + return FALSE; +} + +/************************************************************************/ +/* MultipartUploadAbort() */ +/************************************************************************/ + +bool VSIFilesystemHandler::MultipartUploadAbort(const char *, const char *, + CSLConstList) +{ + CPLError(CE_Failure, CPLE_NotSupported, + "MultipartUploadAbort() not supported by this file system"); + return FALSE; +} + +#endif + /************************************************************************/ /* VSIAbortPendingUploads() */ /************************************************************************/ /** - * \brief Abort ongoing multi-part uploads. + * \brief Abort all ongoing multi-part uploads. * * Abort ongoing multi-part uploads on AWS S3 and Google Cloud Storage. This * can be used in case a process doing such uploads was killed in a unclean way. * + * This can be needed to avoid extra billing for some cloud storage providers. + * * Without effect on other virtual file systems. * + * VSIMultipartUploadAbort() can also be used to cancel a given upload, if the + * upload ID is known. + * * @param pszFilename filename or prefix of a directory into which multipart * uploads must be aborted. This can be the root directory of a bucket. UTF-8 * encoded. diff --git a/port/cpl_vsil_adls.cpp b/port/cpl_vsil_adls.cpp index 06b1988e8919..3e701b2f1960 100644 --- a/port/cpl_vsil_adls.cpp +++ b/port/cpl_vsil_adls.cpp @@ -159,7 +159,7 @@ struct VSIDIRADLS : public VSIDIR /* VSIADLSFSHandler */ /************************************************************************/ -class VSIADLSFSHandler final : public IVSIS3LikeFSHandler +class VSIADLSFSHandler final : public IVSIS3LikeFSHandlerWithMultipartUpload { CPL_DISALLOW_COPY_ASSIGN(VSIADLSFSHandler) @@ -243,10 +243,6 @@ class VSIADLSFSHandler final : public IVSIS3LikeFSHandler CSLConstList papszOptions); // Multipart upload (mapping of S3 interface) - bool SupportsParallelMultipartUpload() const override - { - return true; - } std::string InitiateMultipartUpload(const std::string &osFilename, @@ -294,6 +290,44 @@ class VSIADLSFSHandler final : public IVSIS3LikeFSHandler return true; } + bool MultipartUploadAbort(const char *, const char *, CSLConstList) override + { + CPLError(CE_Failure, CPLE_NotSupported, + "MultipartUploadAbort() not supported by this file system"); + return false; + } + + bool SupportsMultipartAbort() const override + { + return false; + } + + //! Maximum number of parts for multipart upload + // No limit imposed by the API. Arbitrary one here + int GetMaximumPartCount() override + { + return INT_MAX; + } + + //! Minimum size of a part for multipart upload (except last one), in MiB. + int GetMinimumPartSizeInMiB() override + { + return 0; + } + + //! Maximum size of a part for multipart upload, in MiB. + // No limit imposed by the API. Arbitrary one here + int GetMaximumPartSizeInMiB() override + { +#if SIZEOF_VOIDP == 8 + return 4000; +#else + // Cannot be larger than 4GiB, otherwise integer overflow would occur + // 1 GiB is the maximum reasonable value on a 32-bit machine + return 1024; +#endif + } + IVSIS3LikeHandleHelper *CreateHandleHelper(const char *pszURI, bool bAllowNoObject) override; @@ -1857,10 +1891,12 @@ bool VSIADLSFSHandler::UploadFile( poHandleHelper->ResetQueryParameters(); if (event == Event::CREATE_FILE) { + // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/create?view=rest-storageservices-datalakestoragegen2-2019-12-12 poHandleHelper->AddQueryParameter("resource", "file"); } else if (event == Event::APPEND_DATA) { + // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/update?view=rest-storageservices-datalakestoragegen2-2019-12-12 poHandleHelper->AddQueryParameter("action", "append"); poHandleHelper->AddQueryParameter( "position", @@ -1868,6 +1904,7 @@ bool VSIADLSFSHandler::UploadFile( } else { + // Cf https://learn.microsoft.com/en-us/rest/api/storageservices/datalakestoragegen2/path/update?view=rest-storageservices-datalakestoragegen2-2019-12-12 poHandleHelper->AddQueryParameter("action", "flush"); poHandleHelper->AddQueryParameter("close", "true"); poHandleHelper->AddQueryParameter( diff --git a/port/cpl_vsil_az.cpp b/port/cpl_vsil_az.cpp index 6df71b50cc33..454d1f866cf2 100644 --- a/port/cpl_vsil_az.cpp +++ b/port/cpl_vsil_az.cpp @@ -499,7 +499,7 @@ const VSIDIREntry *VSIDIRAz::NextDirEntry() /* VSIAzureFSHandler */ /************************************************************************/ -class VSIAzureFSHandler final : public IVSIS3LikeFSHandler +class VSIAzureFSHandler final : public IVSIS3LikeFSHandlerWithMultipartUpload { CPL_DISALLOW_COPY_ASSIGN(VSIAzureFSHandler) const std::string m_osPrefix; @@ -607,11 +607,6 @@ class VSIAzureFSHandler final : public IVSIS3LikeFSHandler // Multipart upload (mapping of S3 interface to PutBlock/PutBlockList) - bool SupportsParallelMultipartUpload() const override - { - return true; - } - std::string InitiateMultipartUpload( const std::string & /* osFilename */, IVSIS3LikeHandleHelper *, const CPLHTTPRetryParameters & /* oRetryParameters */, @@ -651,6 +646,18 @@ class VSIAzureFSHandler final : public IVSIS3LikeFSHandler return true; } + bool MultipartUploadAbort(const char *, const char *, CSLConstList) override + { + CPLError(CE_Failure, CPLE_NotSupported, + "MultipartUploadAbort() not supported by this file system"); + return false; + } + + bool SupportsMultipartAbort() const override + { + return false; + } + std::string GetStreamingFilename(const std::string &osFilename) const override; diff --git a/port/cpl_vsil_curl_class.h b/port/cpl_vsil_curl_class.h index 6505dff5bc0d..b2092507ce8e 100644 --- a/port/cpl_vsil_curl_class.h +++ b/port/cpl_vsil_curl_class.h @@ -719,6 +719,56 @@ class IVSIS3LikeFSHandler : public VSICurlFilesystemHandlerBaseWritable } }; +/************************************************************************/ +/* IVSIS3LikeFSHandlerWithMultipartUpload */ +/************************************************************************/ + +class IVSIS3LikeFSHandlerWithMultipartUpload : public IVSIS3LikeFSHandler +{ + CPL_DISALLOW_COPY_ASSIGN(IVSIS3LikeFSHandlerWithMultipartUpload) + + protected: + IVSIS3LikeFSHandlerWithMultipartUpload() = default; + + public: + virtual bool SupportsNonSequentialMultipartUpload() const + { + return true; + } + + bool SupportsParallelMultipartUpload() const override + { + return true; + } + + virtual bool SupportsMultipartAbort() const = 0; + + bool MultipartUploadGetCapabilities(int *pbNonSequentialUploadSupported, + int *pbParallelUploadSupported, + int *pbAbortSupported, + size_t *pnMinPartSize, + size_t *pnMaxPartSize, + int *pnMaxPartCount) override; + + char *MultipartUploadStart(const char *pszFilename, + CSLConstList papszOptions) override; + + char *MultipartUploadAddPart(const char *pszFilename, + const char *pszUploadId, int nPartNumber, + vsi_l_offset nFileOffset, const void *pData, + size_t nDataLength, + CSLConstList papszOptions) override; + + bool MultipartUploadEnd(const char *pszFilename, const char *pszUploadId, + size_t nPartIdsCount, + const char *const *apszPartIds, + vsi_l_offset nTotalSize, + CSLConstList papszOptions) override; + + bool MultipartUploadAbort(const char *pszFilename, const char *pszUploadId, + CSLConstList papszOptions) override; +}; + /************************************************************************/ /* IVSIS3LikeHandle */ /************************************************************************/ diff --git a/port/cpl_vsil_gs.cpp b/port/cpl_vsil_gs.cpp index 7abd254d695d..9a8148d024a5 100644 --- a/port/cpl_vsil_gs.cpp +++ b/port/cpl_vsil_gs.cpp @@ -65,7 +65,7 @@ namespace cpl /* VSIGSFSHandler */ /************************************************************************/ -class VSIGSFSHandler final : public IVSIS3LikeFSHandler +class VSIGSFSHandler final : public IVSIS3LikeFSHandlerWithMultipartUpload { CPL_DISALLOW_COPY_ASSIGN(VSIGSFSHandler) const std::string m_osPrefix; @@ -111,12 +111,6 @@ class VSIGSFSHandler final : public IVSIS3LikeFSHandler char *GetSignedURL(const char *pszFilename, CSLConstList papszOptions) override; - // Multipart upload - bool SupportsParallelMultipartUpload() const override - { - return true; - } - char **GetFileMetadata(const char *pszFilename, const char *pszDomain, CSLConstList papszOptions) override; @@ -134,6 +128,11 @@ class VSIGSFSHandler final : public IVSIS3LikeFSHandler { return new VSIGSFSHandler(pszPrefix); } + + bool SupportsMultipartAbort() const override + { + return true; + } }; /************************************************************************/ diff --git a/port/cpl_vsil_oss.cpp b/port/cpl_vsil_oss.cpp index a9797d384fb4..9f1881dc693e 100644 --- a/port/cpl_vsil_oss.cpp +++ b/port/cpl_vsil_oss.cpp @@ -62,7 +62,7 @@ namespace cpl /* VSIOSSFSHandler */ /************************************************************************/ -class VSIOSSFSHandler final : public IVSIS3LikeFSHandler +class VSIOSSFSHandler final : public IVSIS3LikeFSHandlerWithMultipartUpload { CPL_DISALLOW_COPY_ASSIGN(VSIOSSFSHandler) @@ -103,6 +103,11 @@ class VSIOSSFSHandler final : public IVSIS3LikeFSHandler { return osFilename; } + + bool SupportsMultipartAbort() const override + { + return true; + } }; /************************************************************************/ diff --git a/port/cpl_vsil_s3.cpp b/port/cpl_vsil_s3.cpp index 1062b0318cf2..866e02e67981 100644 --- a/port/cpl_vsil_s3.cpp +++ b/port/cpl_vsil_s3.cpp @@ -635,7 +635,7 @@ bool VSICurlFilesystemHandlerBase::AnalyseS3FileList( /* VSIS3FSHandler */ /************************************************************************/ -class VSIS3FSHandler final : public IVSIS3LikeFSHandler +class VSIS3FSHandler final : public IVSIS3LikeFSHandlerWithMultipartUpload { CPL_DISALLOW_COPY_ASSIGN(VSIS3FSHandler) @@ -699,11 +699,6 @@ class VSIS3FSHandler final : public IVSIS3LikeFSHandler const char *pszDomain, CSLConstList papszOptions) override; - bool SupportsParallelMultipartUpload() const override - { - return true; - } - std::string GetStreamingFilename(const std::string &osFilename) const override; @@ -711,6 +706,11 @@ class VSIS3FSHandler final : public IVSIS3LikeFSHandler { return new VSIS3FSHandler(pszPrefix); } + + bool SupportsMultipartAbort() const override + { + return true; + } }; /************************************************************************/ @@ -5277,6 +5277,123 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, return sJobQueue.ret; } +/************************************************************************/ +/* MultipartUploadGetCapabilities() */ +/************************************************************************/ + +bool IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadGetCapabilities( + int *pbNonSequentialUploadSupported, int *pbParallelUploadSupported, + int *pbAbortSupported, size_t *pnMinPartSize, size_t *pnMaxPartSize, + int *pnMaxPartCount) +{ + if (pbNonSequentialUploadSupported) + *pbNonSequentialUploadSupported = + SupportsNonSequentialMultipartUpload(); + if (pbParallelUploadSupported) + *pbParallelUploadSupported = SupportsParallelMultipartUpload(); + if (pbAbortSupported) + *pbAbortSupported = SupportsMultipartAbort(); + if (pnMinPartSize) + *pnMinPartSize = GetMinimumPartSizeInMiB(); + if (pnMaxPartSize) + *pnMaxPartSize = GetMaximumPartSizeInMiB(); + if (pnMaxPartCount) + *pnMaxPartCount = GetMaximumPartCount(); + return true; +} + +/************************************************************************/ +/* MultipartUploadStart() */ +/************************************************************************/ + +char *IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadStart( + const char *pszFilename, CSLConstList papszOptions) +{ + if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str())) + return nullptr; + auto poHandleHelper = + CreateHandleHelper(pszFilename + GetFSPrefix().size(), false); + if (poHandleHelper == nullptr) + return nullptr; + const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)); + const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions); + + const std::string osRet = InitiateMultipartUpload( + pszFilename, poHandleHelper, oRetryParameters, papszOptions); + if (osRet.empty()) + return nullptr; + return CPLStrdup(osRet.c_str()); +} + +/************************************************************************/ +/* MultipartUploadAddPart() */ +/************************************************************************/ + +char *IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadAddPart( + const char *pszFilename, const char *pszUploadId, int nPartNumber, + vsi_l_offset nFileOffset, const void *pData, size_t nDataLength, + CSLConstList papszOptions) +{ + if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str())) + return nullptr; + auto poHandleHelper = + CreateHandleHelper(pszFilename + GetFSPrefix().size(), false); + if (poHandleHelper == nullptr) + return nullptr; + const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)); + const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions); + + const std::string osRet = + UploadPart(pszFilename, nPartNumber, pszUploadId, nFileOffset, pData, + nDataLength, poHandleHelper, oRetryParameters, papszOptions); + if (osRet.empty()) + return nullptr; + return CPLStrdup(osRet.c_str()); +} + +/************************************************************************/ +/* MultipartUploadEnd() */ +/************************************************************************/ + +bool IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadEnd( + const char *pszFilename, const char *pszUploadId, size_t nPartIdsCount, + const char *const *apszPartIds, vsi_l_offset nTotalSize, CSLConstList) +{ + if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str())) + return false; + auto poHandleHelper = + CreateHandleHelper(pszFilename + GetFSPrefix().size(), false); + if (poHandleHelper == nullptr) + return false; + const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)); + const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions); + + std::vector aosTags; + for (size_t i = 0; i < nPartIdsCount; ++i) + aosTags.emplace_back(apszPartIds[i]); + return CompleteMultipart(pszFilename, pszUploadId, aosTags, nTotalSize, + poHandleHelper, oRetryParameters); +} + +/************************************************************************/ +/* MultipartUploadAbort() */ +/************************************************************************/ + +bool IVSIS3LikeFSHandlerWithMultipartUpload::MultipartUploadAbort( + const char *pszFilename, const char *pszUploadId, CSLConstList) +{ + if (!STARTS_WITH_CI(pszFilename, GetFSPrefix().c_str())) + return false; + auto poHandleHelper = + CreateHandleHelper(pszFilename + GetFSPrefix().size(), false); + if (poHandleHelper == nullptr) + return false; + const CPLStringList aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)); + const CPLHTTPRetryParameters oRetryParameters(aosHTTPOptions); + return AbortMultipart(pszFilename, pszUploadId, poHandleHelper, + oRetryParameters); +} + /************************************************************************/ /* VSIS3Handle() */ /************************************************************************/ diff --git a/swig/include/cpl.i b/swig/include/cpl.i index 50ba9a6470ff..38b3cd2fdbdd 100644 --- a/swig/include/cpl.i +++ b/swig/include/cpl.i @@ -989,3 +989,83 @@ int CPLGetNumCPUs(); %rename (GetUsablePhysicalRAM) CPLGetUsablePhysicalRAM; GIntBig CPLGetUsablePhysicalRAM(); + +#if defined(SWIGPYTHON) + +%apply Pointer NONNULL {const char *pszFilename}; +%apply Pointer NONNULL {const char *pszUploadId}; + +%inline { +void MultipartUploadGetCapabilities( + const char *pszFilename, int* pnRetCode, int *pbNonSequentialUploadSupported, + int *pbParallelUploadSupported, int *pbSupportsAbort, size_t *pnMinPartSize, + size_t *pnMaxPartSize, int *pnMaxPartCount) +{ + *pnRetCode = VSIMultipartUploadGetCapabilities(pszFilename, + pbNonSequentialUploadSupported, + pbParallelUploadSupported, + pbSupportsAbort, + pnMinPartSize, + pnMaxPartSize, + pnMaxPartCount); +} +} + +%inline { +char* MultipartUploadStart(const char *pszFilename, char** options = NULL) +{ + return VSIMultipartUploadStart(pszFilename, options); +} +} + +%apply (size_t nLen, char *pBuf ) { (size_t nDataLength, const char *pData)}; + +%inline { +char* MultipartUploadAddPart(const char *pszFilename, + const char *pszUploadId, + int nPartNumber, + GUIntBig nFileOffset, + size_t nDataLength, const char *pData, + char** options = NULL) +{ + return VSIMultipartUploadAddPart(pszFilename, pszUploadId, + nPartNumber, nFileOffset, + pData, nDataLength, + options); +} +} + +%apply (char **dict) { char ** partIds }; + +%inline { +bool MultipartUploadEnd(const char *pszFilename, + const char *pszUploadId, + char** partIds, + GUIntBig nTotalSize, + char** options = NULL) + +{ + return VSIMultipartUploadEnd(pszFilename, pszUploadId, + CSLCount(partIds), partIds, + nTotalSize, + options); +} +} + +%clear (char ** partIds); + +%inline { +bool MultipartUploadAbort(const char *pszFilename, + const char *pszUploadId, + char** options = NULL) +{ + return VSIMultipartUploadAbort(pszFilename, pszUploadId, options); +} +} + +%clear const char *pszFilename; +%clear const char *pszUploadId; +%clear (size_t nDataLength, const void *pData); + +#endif + diff --git a/swig/include/python/gdal_python.i b/swig/include/python/gdal_python.i index d26dbd535c2e..6321e9cdc50f 100644 --- a/swig/include/python/gdal_python.i +++ b/swig/include/python/gdal_python.i @@ -4911,3 +4911,25 @@ def quiet_errors(): tuple.row_intersection = row_intersection val = tuple %} + + +%feature("pythonappend") MultipartUploadGetCapabilities %{ + if val: + non_sequential_upload_supported, parallel_upload_supported, abort_supported, min_part_size, max_part_size, max_part_count = val + import collections + tuple = collections.namedtuple('MultipartUploadGetCapabilitiesResult', + ['non_sequential_upload_supported', + 'parallel_upload_supported', + 'abort_supported', + 'min_part_size', + 'max_part_size', + 'max_part_count', + ]) + tuple.non_sequential_upload_supported = non_sequential_upload_supported + tuple.parallel_upload_supported = parallel_upload_supported + tuple.abort_supported = abort_supported + tuple.min_part_size = min_part_size + tuple.max_part_size = max_part_size + tuple.max_part_count = max_part_count + val = tuple +%} diff --git a/swig/include/python/typemaps_python.i b/swig/include/python/typemaps_python.i index ffab7c912bbe..410ebd03cb98 100644 --- a/swig/include/python/typemaps_python.i +++ b/swig/include/python/typemaps_python.i @@ -3585,3 +3585,55 @@ OBJECT_LIST_INPUT(GDALMDArrayHS); } $result = t_output_helper($result,r); } + + +%typemap(in,numinputs=0) (int* pnRetCode, + int *pbNonSequentialUploadSupported, + int *pbParallelUploadSupported, + int *pbSupportsAbort, + size_t *pnMinPartSize, + size_t *pnMaxPartSize, + int *pnMaxPartCount) ( + int nRetCode = 0, + int bNonSequentialUploadSupported = 0, + int bParallelUploadSupported = 0, + int bSupportsAbort = 0, + size_t nMinPartSize = 0, + size_t nMaxPartSize = 0, + int nMaxPartCount = 0 ) +{ + $1 = &nRetCode; + $2 = &bNonSequentialUploadSupported; + $3 = &bParallelUploadSupported; + $4 = &bSupportsAbort; + $5 = &nMinPartSize; + $6 = &nMaxPartSize; + $7 = &nMaxPartCount; +} + +%typemap(argout) (int* pnRetCode, + int *pbNonSequentialUploadSupported, + int *pbParallelUploadSupported, + int *pbSupportsAbort, + size_t *pnMinPartSize, + size_t *pnMaxPartSize, + int *pnMaxPartCount) +{ + if( *$1 == 0 ) + { + Py_DECREF($result); + $result = Py_None; + Py_INCREF(Py_None); + } + else + { + PyObject *r = PyTuple_New( 6 ); + PyTuple_SetItem( r, 0, PyBool_FromLong(*$2) ); + PyTuple_SetItem( r, 1, PyBool_FromLong(*$3) ); + PyTuple_SetItem( r, 2, PyBool_FromLong(*$4) ); + PyTuple_SetItem( r, 3, PyLong_FromUnsignedLongLong(*$5) ); + PyTuple_SetItem( r, 4, PyLong_FromUnsignedLongLong(*$6) ); + PyTuple_SetItem( r, 5, PyLong_FromUnsignedLongLong(*$7) ); + $result = t_output_helper($result,r); + } +} From bf29bd4791dec08181d88a43ad0074cff477bb8d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 28 May 2024 20:16:42 +0200 Subject: [PATCH 0103/1119] Split VSIS3WriteHandle into VSIMultipartWriteHandle and VSIChunkedWriteHandle classes --- autotest/gcore/vsiswift.py | 50 +++ port/CMakeLists.txt | 1 + port/cpl_vsil_az.cpp | 4 +- port/cpl_vsil_chunked_write_handle.cpp | 560 +++++++++++++++++++++++++ port/cpl_vsil_curl_class.h | 91 +++- port/cpl_vsil_gs.cpp | 4 +- port/cpl_vsil_oss.cpp | 4 +- port/cpl_vsil_s3.cpp | 371 ++-------------- port/cpl_vsil_swift.cpp | 8 +- 9 files changed, 725 insertions(+), 368 deletions(-) create mode 100644 port/cpl_vsil_chunked_write_handle.cpp diff --git a/autotest/gcore/vsiswift.py b/autotest/gcore/vsiswift.py index 4944bbc4d385..0766a731b607 100755 --- a/autotest/gcore/vsiswift.py +++ b/autotest/gcore/vsiswift.py @@ -799,6 +799,56 @@ def method(request): gdal.VSIFCloseL(f) +############################################################################### +# Test write + + +def test_vsiswift_fake_write_zero_file(server): + + gdal.VSICurlClearCache() + + with gdal.config_options( + { + "SWIFT_AUTH_TOKEN": "my_auth_token", + "SWIFT_AUTH_V1_URL": "", + "SWIFT_KEY": "", + "SWIFT_STORAGE_URL": f"http://127.0.0.1:{server.port}/v1/AUTH_something", + "SWIFT_USER": "", + } + ): + + # Test creation of BlockBob + f = gdal.VSIFOpenL("/vsiswift/test_copy/file.bin", "wb") + assert f is not None + + handler = webserver.SequentialHandler() + + def method(request): + h = request.headers + if ( + "x-auth-token" not in h + or h["x-auth-token"] != "my_auth_token" + or "Content-Length" not in h + or h["Content-Length"] != "0" + ): + sys.stderr.write("Bad headers: %s\n" % str(h)) + request.send_response(403) + return + + request.protocol_version = "HTTP/1.1" + request.wfile.write("HTTP/1.1 100 Continue\r\n\r\n".encode("ascii")) + + request.send_response(200) + request.send_header("Content-Length", 0) + request.end_headers() + + handler.add( + "PUT", "/v1/AUTH_something/test_copy/file.bin", custom_method=method + ) + with webserver.install_http_handler(handler): + assert gdal.VSIFCloseL(f) == 0 + + ############################################################################### # Test Unlink() diff --git a/port/CMakeLists.txt b/port/CMakeLists.txt index 52fe942f48c7..3fce7cb5aa77 100644 --- a/port/CMakeLists.txt +++ b/port/CMakeLists.txt @@ -94,6 +94,7 @@ set(CPL_SOURCES cpl_swift.cpp cpl_vsil_adls.cpp cpl_vsil_az.cpp + cpl_vsil_chunked_write_handle.cpp cpl_vsil_uploadonclose.cpp cpl_vsil_gs.cpp cpl_vsil_webhdfs.cpp diff --git a/port/cpl_vsil_az.cpp b/port/cpl_vsil_az.cpp index 454d1f866cf2..fe1924dc03c2 100644 --- a/port/cpl_vsil_az.cpp +++ b/port/cpl_vsil_az.cpp @@ -769,8 +769,8 @@ VSIAzureFSHandler::CreateWriteHandle(const char *pszFilename, const char *pszBlobType = CSLFetchNameValue(papszOptions, "BLOB_TYPE"); if (pszBlobType && EQUAL(pszBlobType, "BLOCK")) { - auto poHandle = std::make_unique( - this, pszFilename, poHandleHelper, false, papszOptions); + auto poHandle = std::make_unique( + this, pszFilename, poHandleHelper, papszOptions); if (!poHandle->IsOK()) { return nullptr; diff --git a/port/cpl_vsil_chunked_write_handle.cpp b/port/cpl_vsil_chunked_write_handle.cpp new file mode 100644 index 000000000000..25b81a5852b7 --- /dev/null +++ b/port/cpl_vsil_chunked_write_handle.cpp @@ -0,0 +1,560 @@ +/****************************************************************************** + * + * Project: CPL - Common Portability Library + * Purpose: Implement a write-only file handle using PUT chunked writing + * Author: Even Rouault, even.rouault at spatialys.com + * + ****************************************************************************** + * Copyright (c) 2024, Even Rouault + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "cpl_vsil_curl_class.h" + +#ifdef HAVE_CURL + +//! @cond Doxygen_Suppress + +#define unchecked_curl_easy_setopt(handle, opt, param) \ + CPL_IGNORE_RET_VAL(curl_easy_setopt(handle, opt, param)) + +namespace cpl +{ + +/************************************************************************/ +/* VSIChunkedWriteHandle() */ +/************************************************************************/ + +VSIChunkedWriteHandle::VSIChunkedWriteHandle( + IVSIS3LikeFSHandler *poFS, const char *pszFilename, + IVSIS3LikeHandleHelper *poS3HandleHelper, CSLConstList papszOptions) + : m_poFS(poFS), m_osFilename(pszFilename), + m_poS3HandleHelper(poS3HandleHelper), m_aosOptions(papszOptions), + m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)), + m_oRetryParameters(m_aosHTTPOptions) +{ +} + +/************************************************************************/ +/* ~VSIChunkedWriteHandle() */ +/************************************************************************/ + +VSIChunkedWriteHandle::~VSIChunkedWriteHandle() +{ + VSIChunkedWriteHandle::Close(); + delete m_poS3HandleHelper; + + if (m_hCurlMulti) + { + if (m_hCurl) + { + curl_multi_remove_handle(m_hCurlMulti, m_hCurl); + curl_easy_cleanup(m_hCurl); + } + VSICURLMultiCleanup(m_hCurlMulti); + } + CPLFree(m_sWriteFuncHeaderData.pBuffer); +} + +/************************************************************************/ +/* Close() */ +/************************************************************************/ + +int VSIChunkedWriteHandle::Close() +{ + int nRet = 0; + if (!m_bClosed) + { + m_bClosed = true; + if (m_hCurlMulti != nullptr) + { + nRet = FinishChunkedTransfer(); + } + else + { + if (!m_bError && !DoEmptyPUT()) + nRet = -1; + } + } + return nRet; +} + +/************************************************************************/ +/* InvalidateParentDirectory() */ +/************************************************************************/ + +void VSIChunkedWriteHandle::InvalidateParentDirectory() +{ + m_poFS->InvalidateCachedData(m_poS3HandleHelper->GetURL().c_str()); + + std::string osFilenameWithoutSlash(m_osFilename); + if (!osFilenameWithoutSlash.empty() && osFilenameWithoutSlash.back() == '/') + osFilenameWithoutSlash.resize(osFilenameWithoutSlash.size() - 1); + m_poFS->InvalidateDirContent(CPLGetDirname(osFilenameWithoutSlash.c_str())); +} + +/************************************************************************/ +/* Seek() */ +/************************************************************************/ + +int VSIChunkedWriteHandle::Seek(vsi_l_offset nOffset, int nWhence) +{ + if (!((nWhence == SEEK_SET && nOffset == m_nCurOffset) || + (nWhence == SEEK_CUR && nOffset == 0) || + (nWhence == SEEK_END && nOffset == 0))) + { + CPLError(CE_Failure, CPLE_NotSupported, + "Seek not supported on writable %s files", + m_poFS->GetFSPrefix().c_str()); + m_bError = true; + return -1; + } + return 0; +} + +/************************************************************************/ +/* Tell() */ +/************************************************************************/ + +vsi_l_offset VSIChunkedWriteHandle::Tell() +{ + return m_nCurOffset; +} + +/************************************************************************/ +/* Read() */ +/************************************************************************/ + +size_t VSIChunkedWriteHandle::Read(void * /* pBuffer */, size_t /* nSize */, + size_t /* nMemb */) +{ + CPLError(CE_Failure, CPLE_NotSupported, + "Read not supported on writable %s files", + m_poFS->GetFSPrefix().c_str()); + m_bError = true; + return 0; +} + +/************************************************************************/ +/* ReadCallBackBufferChunked() */ +/************************************************************************/ + +size_t VSIChunkedWriteHandle::ReadCallBackBufferChunked(char *buffer, + size_t size, + size_t nitems, + void *instream) +{ + VSIChunkedWriteHandle *poThis = + static_cast(instream); + if (poThis->m_nChunkedBufferSize == 0) + { + // CPLDebug("VSIChunkedWriteHandle", "Writing 0 byte (finish)"); + return 0; + } + const size_t nSizeMax = size * nitems; + size_t nSizeToWrite = nSizeMax; + size_t nChunkedBufferRemainingSize = + poThis->m_nChunkedBufferSize - poThis->m_nChunkedBufferOff; + if (nChunkedBufferRemainingSize < nSizeToWrite) + nSizeToWrite = nChunkedBufferRemainingSize; + memcpy(buffer, + static_cast(poThis->m_pBuffer) + + poThis->m_nChunkedBufferOff, + nSizeToWrite); + poThis->m_nChunkedBufferOff += nSizeToWrite; + // CPLDebug("VSIChunkedWriteHandle", "Writing %d bytes", nSizeToWrite); + return nSizeToWrite; +} + +/************************************************************************/ +/* Write() */ +/************************************************************************/ + +size_t VSIChunkedWriteHandle::Write(const void *pBuffer, size_t nSize, + size_t nMemb) +{ + if (m_bError) + return 0; + + const size_t nBytesToWrite = nSize * nMemb; + if (nBytesToWrite == 0) + return 0; + + if (m_hCurlMulti == nullptr) + { + m_hCurlMulti = curl_multi_init(); + } + + WriteFuncStruct sWriteFuncData; + CPLHTTPRetryContext oRetryContext(m_oRetryParameters); + // We can only easily retry at the first chunk of a transfer + bool bCanRetry = (m_hCurl == nullptr); + bool bRetry; + do + { + bRetry = false; + struct curl_slist *headers = nullptr; + if (m_hCurl == nullptr) + { + CURL *hCurlHandle = curl_easy_init(); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION, + ReadCallBackBufferChunked); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, this); + + VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, + nullptr); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, + &sWriteFuncData); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, + VSICurlHandleWriteFunc); + + VSICURLInitWriteFuncStruct(&m_sWriteFuncHeaderData, nullptr, + nullptr, nullptr); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, + &m_sWriteFuncHeaderData); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, + VSICurlHandleWriteFunc); + + headers = static_cast(CPLHTTPSetOptions( + hCurlHandle, m_poS3HandleHelper->GetURL().c_str(), + m_aosHTTPOptions.List())); + headers = VSICurlSetCreationHeadersFromOptions( + headers, m_aosOptions.List(), m_osFilename.c_str()); + headers = VSICurlMergeHeaders( + headers, m_poS3HandleHelper->GetCurlHeaders("PUT", headers)); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, + headers); + + m_osCurlErrBuf.resize(CURL_ERROR_SIZE + 1); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, + &m_osCurlErrBuf[0]); + + curl_multi_add_handle(m_hCurlMulti, hCurlHandle); + m_hCurl = hCurlHandle; + } + + m_pBuffer = pBuffer; + m_nChunkedBufferOff = 0; + m_nChunkedBufferSize = nBytesToWrite; + + int repeats = 0; + // cppcheck-suppress knownConditionTrueFalse + while (m_nChunkedBufferOff < m_nChunkedBufferSize && !bRetry) + { + int still_running; + + memset(&m_osCurlErrBuf[0], 0, m_osCurlErrBuf.size()); + + while (curl_multi_perform(m_hCurlMulti, &still_running) == + CURLM_CALL_MULTI_PERFORM && + // cppcheck-suppress knownConditionTrueFalse + m_nChunkedBufferOff < m_nChunkedBufferSize) + { + // loop + } + // cppcheck-suppress knownConditionTrueFalse + if (!still_running || m_nChunkedBufferOff == m_nChunkedBufferSize) + break; + + CURLMsg *msg; + do + { + int msgq = 0; + msg = curl_multi_info_read(m_hCurlMulti, &msgq); + if (msg && (msg->msg == CURLMSG_DONE)) + { + CURL *e = msg->easy_handle; + if (e == m_hCurl) + { + long response_code; + curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, + &response_code); + if (response_code != 200 && response_code != 201) + { + // Look if we should attempt a retry + if (bCanRetry && + oRetryContext.CanRetry( + static_cast(response_code), + m_sWriteFuncHeaderData.pBuffer, + m_osCurlErrBuf.c_str())) + { + CPLError(CE_Warning, CPLE_AppDefined, + "HTTP error code: %d - %s. " + "Retrying again in %.1f secs", + static_cast(response_code), + m_poS3HandleHelper->GetURL().c_str(), + oRetryContext.GetCurrentDelay()); + CPLSleep(oRetryContext.GetCurrentDelay()); + bRetry = true; + } + else if (sWriteFuncData.pBuffer != nullptr && + m_poS3HandleHelper->CanRestartOnError( + sWriteFuncData.pBuffer, + m_sWriteFuncHeaderData.pBuffer, false)) + { + bRetry = true; + } + else + { + CPLError(CE_Failure, CPLE_AppDefined, + "Error %d: %s", + static_cast(response_code), + m_osCurlErrBuf.c_str()); + + curl_slist_free_all(headers); + bRetry = false; + } + + curl_multi_remove_handle(m_hCurlMulti, m_hCurl); + curl_easy_cleanup(m_hCurl); + + CPLFree(sWriteFuncData.pBuffer); + CPLFree(m_sWriteFuncHeaderData.pBuffer); + + m_hCurl = nullptr; + sWriteFuncData.pBuffer = nullptr; + m_sWriteFuncHeaderData.pBuffer = nullptr; + if (!bRetry) + return 0; + } + } + } + } while (msg); + + CPLMultiPerformWait(m_hCurlMulti, repeats); + } + + m_nWrittenInPUT += nBytesToWrite; + + curl_slist_free_all(headers); + + m_pBuffer = nullptr; + + if (!bRetry) + { + long response_code; + curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code); + if (response_code != 100) + { + // Look if we should attempt a retry + if (bCanRetry && + oRetryContext.CanRetry(static_cast(response_code), + m_sWriteFuncHeaderData.pBuffer, + m_osCurlErrBuf.c_str())) + { + CPLError(CE_Warning, CPLE_AppDefined, + "HTTP error code: %d - %s. " + "Retrying again in %.1f secs", + static_cast(response_code), + m_poS3HandleHelper->GetURL().c_str(), + oRetryContext.GetCurrentDelay()); + CPLSleep(oRetryContext.GetCurrentDelay()); + bRetry = true; + } + else if (sWriteFuncData.pBuffer != nullptr && + m_poS3HandleHelper->CanRestartOnError( + sWriteFuncData.pBuffer, + m_sWriteFuncHeaderData.pBuffer, false)) + { + bRetry = true; + } + else + { + CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s", + static_cast(response_code), + m_osCurlErrBuf.c_str()); + bRetry = false; + nMemb = 0; + } + + curl_multi_remove_handle(m_hCurlMulti, m_hCurl); + curl_easy_cleanup(m_hCurl); + + CPLFree(sWriteFuncData.pBuffer); + CPLFree(m_sWriteFuncHeaderData.pBuffer); + + m_hCurl = nullptr; + sWriteFuncData.pBuffer = nullptr; + m_sWriteFuncHeaderData.pBuffer = nullptr; + } + } + } while (bRetry); + + m_nCurOffset += nBytesToWrite; + + return nMemb; +} + +/************************************************************************/ +/* FinishChunkedTransfer() */ +/************************************************************************/ + +int VSIChunkedWriteHandle::FinishChunkedTransfer() +{ + if (m_hCurl == nullptr) + return -1; + + NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str()); + NetworkStatisticsFile oContextFile(m_osFilename.c_str()); + NetworkStatisticsAction oContextAction("Write"); + + NetworkStatisticsLogger::LogPUT(m_nWrittenInPUT); + m_nWrittenInPUT = 0; + + m_pBuffer = nullptr; + m_nChunkedBufferOff = 0; + m_nChunkedBufferSize = 0; + + VSICURLMultiPerform(m_hCurlMulti); + + long response_code; + curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code); + if (response_code == 200 || response_code == 201) + { + InvalidateParentDirectory(); + } + else + { + CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s", + static_cast(response_code), m_osCurlErrBuf.c_str()); + return -1; + } + return 0; +} + +/************************************************************************/ +/* DoEmptyPUT() */ +/************************************************************************/ + +bool VSIChunkedWriteHandle::DoEmptyPUT() +{ + bool bSuccess = true; + bool bRetry; + CPLHTTPRetryContext oRetryContext(m_oRetryParameters); + + NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str()); + NetworkStatisticsFile oContextFile(m_osFilename.c_str()); + NetworkStatisticsAction oContextAction("Write"); + + do + { + bRetry = false; + + PutData putData; + putData.pabyData = nullptr; + putData.nOff = 0; + putData.nTotalSize = 0; + + CURL *hCurlHandle = curl_easy_init(); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION, + PutData::ReadCallBackBuffer); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, &putData); + unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_INFILESIZE, 0); + + struct curl_slist *headers = static_cast( + CPLHTTPSetOptions(hCurlHandle, m_poS3HandleHelper->GetURL().c_str(), + m_aosHTTPOptions.List())); + headers = VSICurlSetCreationHeadersFromOptions( + headers, m_aosOptions.List(), m_osFilename.c_str()); + headers = VSICurlMergeHeaders( + headers, m_poS3HandleHelper->GetCurlHeaders("PUT", headers, "", 0)); + headers = curl_slist_append(headers, "Expect: 100-continue"); + + CurlRequestHelper requestHelper; + const long response_code = requestHelper.perform( + hCurlHandle, headers, m_poFS, m_poS3HandleHelper); + + NetworkStatisticsLogger::LogPUT(0); + + if (response_code != 200 && response_code != 201) + { + // Look if we should attempt a retry + if (oRetryContext.CanRetry( + static_cast(response_code), + requestHelper.sWriteFuncHeaderData.pBuffer, + requestHelper.szCurlErrBuf)) + { + CPLError(CE_Warning, CPLE_AppDefined, + "HTTP error code: %d - %s. " + "Retrying again in %.1f secs", + static_cast(response_code), + m_poS3HandleHelper->GetURL().c_str(), + oRetryContext.GetCurrentDelay()); + CPLSleep(oRetryContext.GetCurrentDelay()); + bRetry = true; + } + else if (requestHelper.sWriteFuncData.pBuffer != nullptr && + m_poS3HandleHelper->CanRestartOnError( + requestHelper.sWriteFuncData.pBuffer, + requestHelper.sWriteFuncHeaderData.pBuffer, false)) + { + bRetry = true; + } + else + { + CPLDebug("S3", "%s", + requestHelper.sWriteFuncData.pBuffer + ? requestHelper.sWriteFuncData.pBuffer + : "(null)"); + CPLError(CE_Failure, CPLE_AppDefined, + "DoSinglePartPUT of %s failed", m_osFilename.c_str()); + bSuccess = false; + } + } + else + { + InvalidateParentDirectory(); + } + + if (requestHelper.sWriteFuncHeaderData.pBuffer != nullptr) + { + const char *pzETag = + strstr(requestHelper.sWriteFuncHeaderData.pBuffer, "ETag: \""); + if (pzETag) + { + pzETag += strlen("ETag: \""); + const char *pszEndOfETag = strchr(pzETag, '"'); + if (pszEndOfETag) + { + FileProp oFileProp; + oFileProp.eExists = EXIST_YES; + oFileProp.fileSize = m_nBufferOff; + oFileProp.bHasComputedFileSize = true; + oFileProp.ETag.assign(pzETag, pszEndOfETag - pzETag); + m_poFS->SetCachedFileProp( + m_poFS->GetURLFromFilename(m_osFilename.c_str()) + .c_str(), + oFileProp); + } + } + } + + curl_easy_cleanup(hCurlHandle); + } while (bRetry); + return bSuccess; +} + +} // namespace cpl + +//! @endcond + +#endif // HAVE_CURL diff --git a/port/cpl_vsil_curl_class.h b/port/cpl_vsil_curl_class.h index b2092507ce8e..7be5152bc8af 100644 --- a/port/cpl_vsil_curl_class.h +++ b/port/cpl_vsil_curl_class.h @@ -809,17 +809,16 @@ class IVSIS3LikeHandle : public VSICurlHandle }; /************************************************************************/ -/* VSIS3LikeWriteHandle */ +/* VSIMultipartWriteHandle */ /************************************************************************/ -class VSIS3LikeWriteHandle final : public VSIVirtualHandle +class VSIMultipartWriteHandle final : public VSIVirtualHandle { - CPL_DISALLOW_COPY_ASSIGN(VSIS3LikeWriteHandle) + CPL_DISALLOW_COPY_ASSIGN(VSIMultipartWriteHandle) IVSIS3LikeFSHandler *m_poFS = nullptr; std::string m_osFilename{}; IVSIS3LikeHandleHelper *m_poS3HandleHelper = nullptr; - bool m_bUseChunkedTransfer = false; CPLStringList m_aosOptions{}; CPLStringList m_aosHTTPOptions{}; CPLHTTPRetryParameters m_oRetryParameters; @@ -834,6 +833,69 @@ class VSIS3LikeWriteHandle final : public VSIVirtualHandle std::vector m_aosEtags{}; bool m_bError = false; + WriteFuncStruct m_sWriteFuncHeaderData{}; + + bool UploadPart(); + bool DoSinglePartPUT(); + + void InvalidateParentDirectory(); + + public: + VSIMultipartWriteHandle(IVSIS3LikeFSHandler *poFS, const char *pszFilename, + IVSIS3LikeHandleHelper *poS3HandleHelper, + CSLConstList papszOptions); + ~VSIMultipartWriteHandle() override; + + int Seek(vsi_l_offset nOffset, int nWhence) override; + vsi_l_offset Tell() override; + size_t Read(void *pBuffer, size_t nSize, size_t nMemb) override; + size_t Write(const void *pBuffer, size_t nSize, size_t nMemb) override; + + void ClearErr() override + { + } + + int Error() override + { + return FALSE; + } + + int Eof() override + { + return FALSE; + } + + int Close() override; + + bool IsOK() + { + return m_pabyBuffer != nullptr; + } +}; + +/************************************************************************/ +/* VSIChunkedWriteHandle() */ +/************************************************************************/ + +/** Class with Write() append-only implementation using + * "Transfer-Encoding: chunked" writing + */ +class VSIChunkedWriteHandle final : public VSIVirtualHandle +{ + CPL_DISALLOW_COPY_ASSIGN(VSIChunkedWriteHandle) + + IVSIS3LikeFSHandler *m_poFS = nullptr; + std::string m_osFilename{}; + IVSIS3LikeHandleHelper *m_poS3HandleHelper = nullptr; + CPLStringList m_aosOptions{}; + CPLStringList m_aosHTTPOptions{}; + CPLHTTPRetryParameters m_oRetryParameters; + + vsi_l_offset m_nCurOffset = 0; + size_t m_nBufferOff = 0; + bool m_bError = false; + bool m_bClosed = false; + CURLM *m_hCurlMulti = nullptr; CURL *m_hCurl = nullptr; const void *m_pBuffer = nullptr; @@ -844,21 +906,19 @@ class VSIS3LikeWriteHandle final : public VSIVirtualHandle WriteFuncStruct m_sWriteFuncHeaderData{}; - bool UploadPart(); - bool DoSinglePartPUT(); - static size_t ReadCallBackBufferChunked(char *buffer, size_t size, size_t nitems, void *instream); - size_t WriteChunked(const void *pBuffer, size_t nSize, size_t nMemb); int FinishChunkedTransfer(); + bool DoEmptyPUT(); + void InvalidateParentDirectory(); public: - VSIS3LikeWriteHandle(IVSIS3LikeFSHandler *poFS, const char *pszFilename, - IVSIS3LikeHandleHelper *poS3HandleHelper, - bool bUseChunkedTransfer, CSLConstList papszOptions); - ~VSIS3LikeWriteHandle() override; + VSIChunkedWriteHandle(IVSIS3LikeFSHandler *poFS, const char *pszFilename, + IVSIS3LikeHandleHelper *poS3HandleHelper, + CSLConstList papszOptions); + virtual ~VSIChunkedWriteHandle(); int Seek(vsi_l_offset nOffset, int nWhence) override; vsi_l_offset Tell() override; @@ -880,18 +940,13 @@ class VSIS3LikeWriteHandle final : public VSIVirtualHandle } int Close() override; - - bool IsOK() - { - return m_bUseChunkedTransfer || m_pabyBuffer != nullptr; - } }; /************************************************************************/ /* VSIAppendWriteHandle */ /************************************************************************/ -class VSIAppendWriteHandle : public VSIVirtualHandle +class VSIAppendWriteHandle CPL_NON_FINAL : public VSIVirtualHandle { CPL_DISALLOW_COPY_ASSIGN(VSIAppendWriteHandle) diff --git a/port/cpl_vsil_gs.cpp b/port/cpl_vsil_gs.cpp index 9a8148d024a5..e8350cb2ea87 100644 --- a/port/cpl_vsil_gs.cpp +++ b/port/cpl_vsil_gs.cpp @@ -327,8 +327,8 @@ VSIGSFSHandler::CreateWriteHandle(const char *pszFilename, CreateHandleHelper(pszFilename + GetFSPrefix().size(), false); if (poHandleHelper == nullptr) return nullptr; - auto poHandle = std::make_unique( - this, pszFilename, poHandleHelper, false, papszOptions); + auto poHandle = std::make_unique( + this, pszFilename, poHandleHelper, papszOptions); if (!poHandle->IsOK()) { return nullptr; diff --git a/port/cpl_vsil_oss.cpp b/port/cpl_vsil_oss.cpp index 9f1881dc693e..e2e20a699fae 100644 --- a/port/cpl_vsil_oss.cpp +++ b/port/cpl_vsil_oss.cpp @@ -153,8 +153,8 @@ VSIOSSFSHandler::CreateWriteHandle(const char *pszFilename, CreateHandleHelper(pszFilename + GetFSPrefix().size(), false); if (poHandleHelper == nullptr) return nullptr; - auto poHandle = std::make_unique( - this, pszFilename, poHandleHelper, false, papszOptions); + auto poHandle = std::make_unique( + this, pszFilename, poHandleHelper, papszOptions); if (!poHandle->IsOK()) { return nullptr; diff --git a/port/cpl_vsil_s3.cpp b/port/cpl_vsil_s3.cpp index 866e02e67981..5339bf337a90 100644 --- a/port/cpl_vsil_s3.cpp +++ b/port/cpl_vsil_s3.cpp @@ -741,16 +741,14 @@ class VSIS3Handle final : public IVSIS3LikeHandle }; /************************************************************************/ -/* VSIS3LikeWriteHandle() */ +/* VSIMultipartWriteHandle() */ /************************************************************************/ -VSIS3LikeWriteHandle::VSIS3LikeWriteHandle( +VSIMultipartWriteHandle::VSIMultipartWriteHandle( IVSIS3LikeFSHandler *poFS, const char *pszFilename, - IVSIS3LikeHandleHelper *poS3HandleHelper, bool bUseChunked, - CSLConstList papszOptions) + IVSIS3LikeHandleHelper *poS3HandleHelper, CSLConstList papszOptions) : m_poFS(poFS), m_osFilename(pszFilename), - m_poS3HandleHelper(poS3HandleHelper), m_bUseChunkedTransfer(bUseChunked), - m_aosOptions(papszOptions), + m_poS3HandleHelper(poS3HandleHelper), m_aosOptions(papszOptions), m_aosHTTPOptions(CPLHTTPGetOptionsFromEnv(pszFilename)), m_oRetryParameters(m_aosHTTPOptions) { @@ -759,25 +757,20 @@ VSIS3LikeWriteHandle::VSIS3LikeWriteHandle( // Swift only supports the "Transfer-Encoding: chunked" PUT mechanism. // So two different implementations. - if (!m_bUseChunkedTransfer) - { - const char *pszChunkSize = m_aosOptions.FetchNameValue("CHUNK_SIZE"); - if (pszChunkSize) - m_nBufferSize = poFS->GetUploadChunkSizeInBytes( - pszFilename, - CPLSPrintf(CPL_FRMT_GIB, - CPLAtoGIntBig(pszChunkSize) * MIB_CONSTANT)); - else - m_nBufferSize = - poFS->GetUploadChunkSizeInBytes(pszFilename, nullptr); + const char *pszChunkSize = m_aosOptions.FetchNameValue("CHUNK_SIZE"); + if (pszChunkSize) + m_nBufferSize = poFS->GetUploadChunkSizeInBytes( + pszFilename, CPLSPrintf(CPL_FRMT_GIB, CPLAtoGIntBig(pszChunkSize) * + MIB_CONSTANT)); + else + m_nBufferSize = poFS->GetUploadChunkSizeInBytes(pszFilename, nullptr); - m_pabyBuffer = static_cast(VSIMalloc(m_nBufferSize)); - if (m_pabyBuffer == nullptr) - { - CPLError(CE_Failure, CPLE_AppDefined, - "Cannot allocate working buffer for %s", - m_poFS->GetFSPrefix().c_str()); - } + m_pabyBuffer = static_cast(VSIMalloc(m_nBufferSize)); + if (m_pabyBuffer == nullptr) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Cannot allocate working buffer for %s", + m_poFS->GetFSPrefix().c_str()); } } @@ -846,23 +839,14 @@ size_t IVSIS3LikeFSHandler::GetUploadChunkSizeInBytes( } /************************************************************************/ -/* ~VSIS3LikeWriteHandle() */ +/* ~VSIMultipartWriteHandle() */ /************************************************************************/ -VSIS3LikeWriteHandle::~VSIS3LikeWriteHandle() +VSIMultipartWriteHandle::~VSIMultipartWriteHandle() { - VSIS3LikeWriteHandle::Close(); + VSIMultipartWriteHandle::Close(); delete m_poS3HandleHelper; CPLFree(m_pabyBuffer); - if (m_hCurlMulti) - { - if (m_hCurl) - { - curl_multi_remove_handle(m_hCurlMulti, m_hCurl); - curl_easy_cleanup(m_hCurl); - } - VSICURLMultiCleanup(m_hCurlMulti); - } CPLFree(m_sWriteFuncHeaderData.pBuffer); } @@ -870,7 +854,7 @@ VSIS3LikeWriteHandle::~VSIS3LikeWriteHandle() /* Seek() */ /************************************************************************/ -int VSIS3LikeWriteHandle::Seek(vsi_l_offset nOffset, int nWhence) +int VSIMultipartWriteHandle::Seek(vsi_l_offset nOffset, int nWhence) { if (!((nWhence == SEEK_SET && nOffset == m_nCurOffset) || (nWhence == SEEK_CUR && nOffset == 0) || @@ -889,7 +873,7 @@ int VSIS3LikeWriteHandle::Seek(vsi_l_offset nOffset, int nWhence) /* Tell() */ /************************************************************************/ -vsi_l_offset VSIS3LikeWriteHandle::Tell() +vsi_l_offset VSIMultipartWriteHandle::Tell() { return m_nCurOffset; } @@ -898,8 +882,8 @@ vsi_l_offset VSIS3LikeWriteHandle::Tell() /* Read() */ /************************************************************************/ -size_t VSIS3LikeWriteHandle::Read(void * /* pBuffer */, size_t /* nSize */, - size_t /* nMemb */) +size_t VSIMultipartWriteHandle::Read(void * /* pBuffer */, size_t /* nSize */, + size_t /* nMemb */) { CPLError(CE_Failure, CPLE_NotSupported, "Read not supported on writable %s files", @@ -1017,7 +1001,7 @@ std::string IVSIS3LikeFSHandler::InitiateMultipartUpload( /* UploadPart() */ /************************************************************************/ -bool VSIS3LikeWriteHandle::UploadPart() +bool VSIMultipartWriteHandle::UploadPart() { ++m_nPartNumber; if (m_nPartNumber > m_poFS->GetMaximumPartCount()) @@ -1161,292 +1145,12 @@ IVSIS3LikeFSHandler::UploadPart(const std::string &osFilename, int nPartNumber, return osEtag; } -/************************************************************************/ -/* ReadCallBackBufferChunked() */ -/************************************************************************/ - -size_t VSIS3LikeWriteHandle::ReadCallBackBufferChunked(char *buffer, - size_t size, - size_t nitems, - void *instream) -{ - VSIS3LikeWriteHandle *poThis = - static_cast(instream); - if (poThis->m_nChunkedBufferSize == 0) - { - // CPLDebug("VSIS3LikeWriteHandle", "Writing 0 byte (finish)"); - return 0; - } - const size_t nSizeMax = size * nitems; - size_t nSizeToWrite = nSizeMax; - size_t nChunkedBufferRemainingSize = - poThis->m_nChunkedBufferSize - poThis->m_nChunkedBufferOff; - if (nChunkedBufferRemainingSize < nSizeToWrite) - nSizeToWrite = nChunkedBufferRemainingSize; - memcpy(buffer, - static_cast(poThis->m_pBuffer) + - poThis->m_nChunkedBufferOff, - nSizeToWrite); - poThis->m_nChunkedBufferOff += nSizeToWrite; - // CPLDebug("VSIS3LikeWriteHandle", "Writing %d bytes", nSizeToWrite); - return nSizeToWrite; -} - -/************************************************************************/ -/* WriteChunked() */ -/************************************************************************/ - -size_t VSIS3LikeWriteHandle::WriteChunked(const void *pBuffer, size_t nSize, - size_t nMemb) -{ - const size_t nBytesToWrite = nSize * nMemb; - - if (m_hCurlMulti == nullptr) - { - m_hCurlMulti = curl_multi_init(); - } - - WriteFuncStruct sWriteFuncData; - CPLHTTPRetryContext oRetryContext(m_oRetryParameters); - // We can only easily retry at the first chunk of a transfer - bool bCanRetry = (m_hCurl == nullptr); - bool bRetry; - do - { - bRetry = false; - struct curl_slist *headers = nullptr; - if (m_hCurl == nullptr) - { - CURL *hCurlHandle = curl_easy_init(); - unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_UPLOAD, 1L); - unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READFUNCTION, - ReadCallBackBufferChunked); - unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_READDATA, this); - - VSICURLInitWriteFuncStruct(&sWriteFuncData, nullptr, nullptr, - nullptr); - unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEDATA, - &sWriteFuncData); - unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_WRITEFUNCTION, - VSICurlHandleWriteFunc); - - VSICURLInitWriteFuncStruct(&m_sWriteFuncHeaderData, nullptr, - nullptr, nullptr); - unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERDATA, - &m_sWriteFuncHeaderData); - unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HEADERFUNCTION, - VSICurlHandleWriteFunc); - - headers = static_cast(CPLHTTPSetOptions( - hCurlHandle, m_poS3HandleHelper->GetURL().c_str(), - m_aosHTTPOptions.List())); - headers = VSICurlSetCreationHeadersFromOptions( - headers, m_aosOptions.List(), m_osFilename.c_str()); - headers = VSICurlMergeHeaders( - headers, m_poS3HandleHelper->GetCurlHeaders("PUT", headers)); - unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_HTTPHEADER, - headers); - - m_osCurlErrBuf.resize(CURL_ERROR_SIZE + 1); - unchecked_curl_easy_setopt(hCurlHandle, CURLOPT_ERRORBUFFER, - &m_osCurlErrBuf[0]); - - curl_multi_add_handle(m_hCurlMulti, hCurlHandle); - m_hCurl = hCurlHandle; - } - - m_pBuffer = pBuffer; - m_nChunkedBufferOff = 0; - m_nChunkedBufferSize = nBytesToWrite; - - int repeats = 0; - while (m_nChunkedBufferOff < m_nChunkedBufferSize && !bRetry) - { - int still_running; - - memset(&m_osCurlErrBuf[0], 0, m_osCurlErrBuf.size()); - - while (curl_multi_perform(m_hCurlMulti, &still_running) == - CURLM_CALL_MULTI_PERFORM && - // cppcheck-suppress knownConditionTrueFalse - m_nChunkedBufferOff < m_nChunkedBufferSize) - { - // loop - } - // cppcheck-suppress knownConditionTrueFalse - if (!still_running || m_nChunkedBufferOff == m_nChunkedBufferSize) - break; - - CURLMsg *msg; - do - { - int msgq = 0; - msg = curl_multi_info_read(m_hCurlMulti, &msgq); - if (msg && (msg->msg == CURLMSG_DONE)) - { - CURL *e = msg->easy_handle; - if (e == m_hCurl) - { - long response_code; - curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, - &response_code); - if (response_code != 200 && response_code != 201) - { - // Look if we should attempt a retry - if (bCanRetry && - oRetryContext.CanRetry( - static_cast(response_code), - m_sWriteFuncHeaderData.pBuffer, - m_osCurlErrBuf.c_str())) - { - CPLError(CE_Warning, CPLE_AppDefined, - "HTTP error code: %d - %s. " - "Retrying again in %.1f secs", - static_cast(response_code), - m_poS3HandleHelper->GetURL().c_str(), - oRetryContext.GetCurrentDelay()); - CPLSleep(oRetryContext.GetCurrentDelay()); - bRetry = true; - } - else if (sWriteFuncData.pBuffer != nullptr && - m_poS3HandleHelper->CanRestartOnError( - sWriteFuncData.pBuffer, - m_sWriteFuncHeaderData.pBuffer, false)) - { - bRetry = true; - } - else - { - CPLError(CE_Failure, CPLE_AppDefined, - "Error %d: %s", - static_cast(response_code), - m_osCurlErrBuf.c_str()); - - curl_slist_free_all(headers); - bRetry = false; - } - - curl_multi_remove_handle(m_hCurlMulti, m_hCurl); - curl_easy_cleanup(m_hCurl); - - CPLFree(sWriteFuncData.pBuffer); - CPLFree(m_sWriteFuncHeaderData.pBuffer); - - m_hCurl = nullptr; - sWriteFuncData.pBuffer = nullptr; - m_sWriteFuncHeaderData.pBuffer = nullptr; - if (!bRetry) - return 0; - } - } - } - } while (msg); - - CPLMultiPerformWait(m_hCurlMulti, repeats); - } - - m_nWrittenInPUT += nBytesToWrite; - - curl_slist_free_all(headers); - - m_pBuffer = nullptr; - - if (!bRetry) - { - long response_code; - curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code); - if (response_code != 100) - { - // Look if we should attempt a retry - if (bCanRetry && - oRetryContext.CanRetry(static_cast(response_code), - m_sWriteFuncHeaderData.pBuffer, - m_osCurlErrBuf.c_str())) - { - CPLError(CE_Warning, CPLE_AppDefined, - "HTTP error code: %d - %s. " - "Retrying again in %.1f secs", - static_cast(response_code), - m_poS3HandleHelper->GetURL().c_str(), - oRetryContext.GetCurrentDelay()); - CPLSleep(oRetryContext.GetCurrentDelay()); - bRetry = true; - } - else if (sWriteFuncData.pBuffer != nullptr && - m_poS3HandleHelper->CanRestartOnError( - sWriteFuncData.pBuffer, - m_sWriteFuncHeaderData.pBuffer, false)) - { - bRetry = true; - } - else - { - CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s", - static_cast(response_code), - m_osCurlErrBuf.c_str()); - bRetry = false; - nMemb = 0; - } - - curl_multi_remove_handle(m_hCurlMulti, m_hCurl); - curl_easy_cleanup(m_hCurl); - - CPLFree(sWriteFuncData.pBuffer); - CPLFree(m_sWriteFuncHeaderData.pBuffer); - - m_hCurl = nullptr; - sWriteFuncData.pBuffer = nullptr; - m_sWriteFuncHeaderData.pBuffer = nullptr; - } - } - } while (bRetry); - - return nMemb; -} - -/************************************************************************/ -/* FinishChunkedTransfer() */ -/************************************************************************/ - -int VSIS3LikeWriteHandle::FinishChunkedTransfer() -{ - if (m_hCurl == nullptr) - return -1; - - NetworkStatisticsFileSystem oContextFS(m_poFS->GetFSPrefix().c_str()); - NetworkStatisticsFile oContextFile(m_osFilename.c_str()); - NetworkStatisticsAction oContextAction("Write"); - - NetworkStatisticsLogger::LogPUT(m_nWrittenInPUT); - m_nWrittenInPUT = 0; - - m_pBuffer = nullptr; - m_nChunkedBufferOff = 0; - m_nChunkedBufferSize = 0; - - VSICURLMultiPerform(m_hCurlMulti); - - long response_code; - curl_easy_getinfo(m_hCurl, CURLINFO_RESPONSE_CODE, &response_code); - if (response_code == 200 || response_code == 201) - { - InvalidateParentDirectory(); - } - else - { - CPLError(CE_Failure, CPLE_AppDefined, "Error %d: %s", - static_cast(response_code), m_osCurlErrBuf.c_str()); - return -1; - } - return 0; -} - /************************************************************************/ /* Write() */ /************************************************************************/ -size_t VSIS3LikeWriteHandle::Write(const void *pBuffer, size_t nSize, - size_t nMemb) +size_t VSIMultipartWriteHandle::Write(const void *pBuffer, size_t nSize, + size_t nMemb) { if (m_bError) return 0; @@ -1455,11 +1159,6 @@ size_t VSIS3LikeWriteHandle::Write(const void *pBuffer, size_t nSize, if (nBytesToWrite == 0) return 0; - if (m_bUseChunkedTransfer) - { - return WriteChunked(pBuffer, nSize, nMemb); - } - const GByte *pabySrcBuffer = reinterpret_cast(pBuffer); while (nBytesToWrite > 0) { @@ -1498,7 +1197,7 @@ size_t VSIS3LikeWriteHandle::Write(const void *pBuffer, size_t nSize, /* InvalidateParentDirectory() */ /************************************************************************/ -void VSIS3LikeWriteHandle::InvalidateParentDirectory() +void VSIMultipartWriteHandle::InvalidateParentDirectory() { m_poFS->InvalidateCachedData(m_poS3HandleHelper->GetURL().c_str()); @@ -1512,7 +1211,7 @@ void VSIS3LikeWriteHandle::InvalidateParentDirectory() /* DoSinglePartPUT() */ /************************************************************************/ -bool VSIS3LikeWriteHandle::DoSinglePartPUT() +bool VSIMultipartWriteHandle::DoSinglePartPUT() { bool bSuccess = true; bool bRetry; @@ -2019,17 +1718,13 @@ bool IVSIS3LikeFSHandler::AbortPendingUploads(const char *pszFilename) /* Close() */ /************************************************************************/ -int VSIS3LikeWriteHandle::Close() +int VSIMultipartWriteHandle::Close() { int nRet = 0; if (!m_bClosed) { m_bClosed = true; - if (m_bUseChunkedTransfer && m_hCurlMulti != nullptr) - { - nRet = FinishChunkedTransfer(); - } - else if (m_osUploadID.empty()) + if (m_osUploadID.empty()) { if (!m_bError && !DoSinglePartPUT()) nRet = -1; @@ -2070,8 +1765,8 @@ VSIS3FSHandler::CreateWriteHandle(const char *pszFilename, CreateHandleHelper(pszFilename + GetFSPrefix().size(), false); if (poHandleHelper == nullptr) return nullptr; - auto poHandle = std::make_unique( - this, pszFilename, poHandleHelper, false, papszOptions); + auto poHandle = std::make_unique( + this, pszFilename, poHandleHelper, papszOptions); if (!poHandle->IsOK()) { return nullptr; diff --git a/port/cpl_vsil_swift.cpp b/port/cpl_vsil_swift.cpp index 2cb35817170f..6cddce26fffe 100644 --- a/port/cpl_vsil_swift.cpp +++ b/port/cpl_vsil_swift.cpp @@ -301,12 +301,8 @@ VSISwiftFSHandler::CreateWriteHandle(const char *pszFilename, CreateHandleHelper(pszFilename + GetFSPrefix().size(), false); if (poHandleHelper == nullptr) return nullptr; - auto poHandle = std::make_unique( - this, pszFilename, poHandleHelper, true, papszOptions); - if (!poHandle->IsOK()) - { - return nullptr; - } + auto poHandle = std::make_unique( + this, pszFilename, poHandleHelper, papszOptions); return VSIVirtualHandleUniquePtr(poHandle.release()); } From 88673a83dc5f1898d3c2a4f8ca145081a4a8618d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 28 May 2024 21:11:35 +0200 Subject: [PATCH 0104/1119] Move more methods to IVSIS3LikeFSHandlerWithMultipartUpload --- autotest/gcore/vsioss.py | 25 -------- port/cpl_vsil_curl_class.h | 108 ++++++++++++++++--------------- port/cpl_vsil_s3.cpp | 127 +++++++++++++++++++++---------------- 3 files changed, 125 insertions(+), 135 deletions(-) diff --git a/autotest/gcore/vsioss.py b/autotest/gcore/vsioss.py index 9aee95da8d9c..be75e9506ce5 100755 --- a/autotest/gcore/vsioss.py +++ b/autotest/gcore/vsioss.py @@ -1244,31 +1244,6 @@ def test_vsioss_8(server): assert stat.S_ISDIR(gdal.VSIStatL("/vsioss/vsioss_8/test/").mode) -############################################################################### -# Test gdal.CopyFileRestartable() with fallback to regular copy - - -def test_vsioss_CopyFileRestartable_fallback_to_regular_copy(tmp_vsimem, server): - - gdal.VSICurlClearCache() - - srcfilename = str(tmp_vsimem / "foo") - gdal.FileFromMemBuffer(srcfilename, "foo\n") - - dstfilename = "/vsioss/test_bucket/foo" - - handler = webserver.SequentialHandler() - handler.add("PUT", "/test_bucket/foo", 200, expected_body=b"foo\n") - - with webserver.install_http_handler(handler): - ret_code, restart_payload = gdal.CopyFileRestartable( - srcfilename, - dstfilename, - None, # input payload - ) - assert ret_code == 0 - - ############################################################################### # Nominal cases (require valid credentials) diff --git a/port/cpl_vsil_curl_class.h b/port/cpl_vsil_curl_class.h index 7be5152bc8af..90120b069f2c 100644 --- a/port/cpl_vsil_curl_class.h +++ b/port/cpl_vsil_curl_class.h @@ -627,14 +627,6 @@ class IVSIS3LikeFSHandler : public VSICurlFilesystemHandlerBaseWritable GDALProgressFunc pProgressFunc, void *pProgressData) override; - virtual int CopyFileRestartable(const char *pszSource, - const char *pszTarget, - const char *pszInputPayload, - char **ppszOutputPayload, - CSLConstList papszOptions, - GDALProgressFunc pProgressFunc, - void *pProgressData) override; - virtual int DeleteObject(const char *pszFilename); virtual int *DeleteObjectBatch(CSLConstList papszFilesOrDirs); @@ -645,40 +637,43 @@ class IVSIS3LikeFSHandler : public VSICurlFilesystemHandlerBaseWritable VSIDIR *OpenDir(const char *pszPath, int nRecurseDepth, const char *const *papszOptions) override; +}; - // Multipart upload - virtual bool SupportsParallelMultipartUpload() const +/************************************************************************/ +/* IVSIS3LikeFSHandlerWithMultipartUpload */ +/************************************************************************/ + +class IVSIS3LikeFSHandlerWithMultipartUpload : public IVSIS3LikeFSHandler +{ + CPL_DISALLOW_COPY_ASSIGN(IVSIS3LikeFSHandlerWithMultipartUpload) + + protected: + IVSIS3LikeFSHandlerWithMultipartUpload() = default; + + public: + virtual bool SupportsNonSequentialMultipartUpload() const { - return false; + return true; } - virtual std::string - InitiateMultipartUpload(const std::string &osFilename, - IVSIS3LikeHandleHelper *poS3HandleHelper, - const CPLHTTPRetryParameters &oRetryParameters, - CSLConstList papszOptions); - virtual std::string - UploadPart(const std::string &osFilename, int nPartNumber, - const std::string &osUploadID, vsi_l_offset nPosition, - const void *pabyBuffer, size_t nBufferSize, - IVSIS3LikeHandleHelper *poS3HandleHelper, - const CPLHTTPRetryParameters &oRetryParameters, - CSLConstList papszOptions); - virtual bool CompleteMultipart( - const std::string &osFilename, const std::string &osUploadID, - const std::vector &aosEtags, vsi_l_offset nTotalSize, - IVSIS3LikeHandleHelper *poS3HandleHelper, - const CPLHTTPRetryParameters &oRetryParameters); - virtual bool AbortMultipart(const std::string &osFilename, - const std::string &osUploadID, - IVSIS3LikeHandleHelper *poS3HandleHelper, - const CPLHTTPRetryParameters &oRetryParameters); + virtual bool SupportsParallelMultipartUpload() const + { + return true; + } - bool AbortPendingUploads(const char *pszFilename) override; + virtual bool SupportsMultipartAbort() const = 0; size_t GetUploadChunkSizeInBytes(const char *pszFilename, const char *pszSpecifiedValInBytes); + virtual int CopyFileRestartable(const char *pszSource, + const char *pszTarget, + const char *pszInputPayload, + char **ppszOutputPayload, + CSLConstList papszOptions, + GDALProgressFunc pProgressFunc, + void *pProgressData) override; + //! Maximum number of parts for multipart upload // Limit currently used by S3 and GS. // Cf https://docs.aws.amazon.com/AmazonS3/latest/userguide/qfacts.html @@ -717,31 +712,33 @@ class IVSIS3LikeFSHandler : public VSICurlFilesystemHandlerBaseWritable { return 50; } -}; - -/************************************************************************/ -/* IVSIS3LikeFSHandlerWithMultipartUpload */ -/************************************************************************/ -class IVSIS3LikeFSHandlerWithMultipartUpload : public IVSIS3LikeFSHandler -{ - CPL_DISALLOW_COPY_ASSIGN(IVSIS3LikeFSHandlerWithMultipartUpload) + virtual std::string + InitiateMultipartUpload(const std::string &osFilename, + IVSIS3LikeHandleHelper *poS3HandleHelper, + const CPLHTTPRetryParameters &oRetryParameters, + CSLConstList papszOptions); - protected: - IVSIS3LikeFSHandlerWithMultipartUpload() = default; + virtual std::string + UploadPart(const std::string &osFilename, int nPartNumber, + const std::string &osUploadID, vsi_l_offset nPosition, + const void *pabyBuffer, size_t nBufferSize, + IVSIS3LikeHandleHelper *poS3HandleHelper, + const CPLHTTPRetryParameters &oRetryParameters, + CSLConstList papszOptions); - public: - virtual bool SupportsNonSequentialMultipartUpload() const - { - return true; - } + virtual bool CompleteMultipart( + const std::string &osFilename, const std::string &osUploadID, + const std::vector &aosEtags, vsi_l_offset nTotalSize, + IVSIS3LikeHandleHelper *poS3HandleHelper, + const CPLHTTPRetryParameters &oRetryParameters); - bool SupportsParallelMultipartUpload() const override - { - return true; - } + virtual bool AbortMultipart(const std::string &osFilename, + const std::string &osUploadID, + IVSIS3LikeHandleHelper *poS3HandleHelper, + const CPLHTTPRetryParameters &oRetryParameters); - virtual bool SupportsMultipartAbort() const = 0; + bool AbortPendingUploads(const char *pszFilename) override; bool MultipartUploadGetCapabilities(int *pbNonSequentialUploadSupported, int *pbParallelUploadSupported, @@ -816,7 +813,7 @@ class VSIMultipartWriteHandle final : public VSIVirtualHandle { CPL_DISALLOW_COPY_ASSIGN(VSIMultipartWriteHandle) - IVSIS3LikeFSHandler *m_poFS = nullptr; + IVSIS3LikeFSHandlerWithMultipartUpload *m_poFS = nullptr; std::string m_osFilename{}; IVSIS3LikeHandleHelper *m_poS3HandleHelper = nullptr; CPLStringList m_aosOptions{}; @@ -841,7 +838,8 @@ class VSIMultipartWriteHandle final : public VSIVirtualHandle void InvalidateParentDirectory(); public: - VSIMultipartWriteHandle(IVSIS3LikeFSHandler *poFS, const char *pszFilename, + VSIMultipartWriteHandle(IVSIS3LikeFSHandlerWithMultipartUpload *poFS, + const char *pszFilename, IVSIS3LikeHandleHelper *poS3HandleHelper, CSLConstList papszOptions); ~VSIMultipartWriteHandle() override; diff --git a/port/cpl_vsil_s3.cpp b/port/cpl_vsil_s3.cpp index 5339bf337a90..ccc1b467c55e 100644 --- a/port/cpl_vsil_s3.cpp +++ b/port/cpl_vsil_s3.cpp @@ -745,7 +745,7 @@ class VSIS3Handle final : public IVSIS3LikeHandle /************************************************************************/ VSIMultipartWriteHandle::VSIMultipartWriteHandle( - IVSIS3LikeFSHandler *poFS, const char *pszFilename, + IVSIS3LikeFSHandlerWithMultipartUpload *poFS, const char *pszFilename, IVSIS3LikeHandleHelper *poS3HandleHelper, CSLConstList papszOptions) : m_poFS(poFS), m_osFilename(pszFilename), m_poS3HandleHelper(poS3HandleHelper), m_aosOptions(papszOptions), @@ -778,7 +778,7 @@ VSIMultipartWriteHandle::VSIMultipartWriteHandle( /* GetUploadChunkSizeInBytes() */ /************************************************************************/ -size_t IVSIS3LikeFSHandler::GetUploadChunkSizeInBytes( +size_t IVSIS3LikeFSHandlerWithMultipartUpload::GetUploadChunkSizeInBytes( const char *pszFilename, const char *pszSpecifiedValInBytes) { size_t nChunkSize = 0; @@ -896,7 +896,7 @@ size_t VSIMultipartWriteHandle::Read(void * /* pBuffer */, size_t /* nSize */, /* InitiateMultipartUpload() */ /************************************************************************/ -std::string IVSIS3LikeFSHandler::InitiateMultipartUpload( +std::string IVSIS3LikeFSHandlerWithMultipartUpload::InitiateMultipartUpload( const std::string &osFilename, IVSIS3LikeHandleHelper *poS3HandleHelper, const CPLHTTPRetryParameters &oRetryParameters, CSLConstList papszOptions) { @@ -1029,14 +1029,13 @@ bool VSIMultipartWriteHandle::UploadPart() return !osEtag.empty(); } -std::string -IVSIS3LikeFSHandler::UploadPart(const std::string &osFilename, int nPartNumber, - const std::string &osUploadID, - vsi_l_offset /* nPosition */, - const void *pabyBuffer, size_t nBufferSize, - IVSIS3LikeHandleHelper *poS3HandleHelper, - const CPLHTTPRetryParameters &oRetryParameters, - CSLConstList /* papszOptions */) +std::string IVSIS3LikeFSHandlerWithMultipartUpload::UploadPart( + const std::string &osFilename, int nPartNumber, + const std::string &osUploadID, vsi_l_offset /* nPosition */, + const void *pabyBuffer, size_t nBufferSize, + IVSIS3LikeHandleHelper *poS3HandleHelper, + const CPLHTTPRetryParameters &oRetryParameters, + CSLConstList /* papszOptions */) { NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str()); NetworkStatisticsFile oContextFile(osFilename.c_str()); @@ -1326,7 +1325,7 @@ bool VSIMultipartWriteHandle::DoSinglePartPUT() /* CompleteMultipart() */ /************************************************************************/ -bool IVSIS3LikeFSHandler::CompleteMultipart( +bool IVSIS3LikeFSHandlerWithMultipartUpload::CompleteMultipart( const std::string &osFilename, const std::string &osUploadID, const std::vector &aosEtags, vsi_l_offset /* nTotalSize */, IVSIS3LikeHandleHelper *poS3HandleHelper, @@ -1438,7 +1437,7 @@ bool IVSIS3LikeFSHandler::CompleteMultipart( /* AbortMultipart() */ /************************************************************************/ -bool IVSIS3LikeFSHandler::AbortMultipart( +bool IVSIS3LikeFSHandlerWithMultipartUpload::AbortMultipart( const std::string &osFilename, const std::string &osUploadID, IVSIS3LikeHandleHelper *poS3HandleHelper, const CPLHTTPRetryParameters &oRetryParameters) @@ -1521,7 +1520,8 @@ bool IVSIS3LikeFSHandler::AbortMultipart( /* AbortPendingUploads() */ /************************************************************************/ -bool IVSIS3LikeFSHandler::AbortPendingUploads(const char *pszFilename) +bool IVSIS3LikeFSHandlerWithMultipartUpload::AbortPendingUploads( + const char *pszFilename) { NetworkStatisticsFileSystem oContextFS(GetFSPrefix().c_str()); NetworkStatisticsFile oContextFile(pszFilename); @@ -3459,7 +3459,7 @@ static int GetRequestedNumThreadsForCopy(CSLConstList papszOptions) /* CopyFileRestartable() */ /************************************************************************/ -int IVSIS3LikeFSHandler::CopyFileRestartable( +int IVSIS3LikeFSHandlerWithMultipartUpload::CopyFileRestartable( const char *pszSource, const char *pszTarget, const char *pszInputPayload, char **ppszOutputPayload, CSLConstList papszOptions, GDALProgressFunc pProgressFunc, void *pProgressData) @@ -4130,11 +4130,12 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, std::set aoSetDirsToCreate; const char *pszChunkSize = CSLFetchNameValue(papszOptions, "CHUNK_SIZE"); const int nRequestedThreads = GetRequestedNumThreadsForCopy(papszOptions); - auto poTargetFSHandler = dynamic_cast( - VSIFileManager::GetHandler(pszTarget)); + auto poTargetFSMultipartHandler = + dynamic_cast( + VSIFileManager::GetHandler(pszTarget)); const bool bSupportsParallelMultipartUpload = - bUploadFromLocalToNetwork && poTargetFSHandler != nullptr && - poTargetFSHandler->SupportsParallelMultipartUpload(); + bUploadFromLocalToNetwork && poTargetFSMultipartHandler != nullptr && + poTargetFSMultipartHandler->SupportsParallelMultipartUpload(); const bool bSimulateThreading = CPLTestBool(CPLGetConfigOption("VSIS3_SIMULATE_THREADING", "NO")); const int nMinSizeChunk = @@ -4154,14 +4155,15 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, // Filter x-amz- options when outputting to /vsis3/ CPLStringList aosObjectCreationOptions; - if (poTargetFSHandler != nullptr && papszOptions != nullptr) + if (poTargetFSMultipartHandler != nullptr && papszOptions != nullptr) { for (auto papszIter = papszOptions; *papszIter != nullptr; ++papszIter) { char *pszKey = nullptr; const char *pszValue = CPLParseNameValue(*papszIter, &pszKey); if (pszKey && pszValue && - poTargetFSHandler->IsAllowedHeaderForObjectCreation(pszKey)) + poTargetFSMultipartHandler->IsAllowedHeaderForObjectCreation( + pszKey)) { aosObjectCreationOptions.SetNameValue(pszKey, pszValue); } @@ -4187,12 +4189,12 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, // Cleanup pending uploads in case of early exit struct CleanupPendingUploads { - IVSIS3LikeFSHandler *m_poFS; + IVSIS3LikeFSHandlerWithMultipartUpload *m_poFS; std::map &m_oMapMultiPartDefs; const CPLHTTPRetryParameters &m_oRetryParameters; CleanupPendingUploads( - IVSIS3LikeFSHandler *poFSIn, + IVSIS3LikeFSHandlerWithMultipartUpload *poFSIn, std::map &oMapMultiPartDefsIn, const CPLHTTPRetryParameters &oRetryParametersIn) : m_poFS(poFSIn), m_oMapMultiPartDefs(oMapMultiPartDefsIn), @@ -4202,17 +4204,21 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, ~CleanupPendingUploads() { - for (const auto &kv : m_oMapMultiPartDefs) + if (m_poFS) { - auto poS3HandleHelper = std::unique_ptr( - m_poFS->CreateHandleHelper(kv.first.c_str() + - m_poFS->GetFSPrefix().size(), - false)); - if (poS3HandleHelper) + for (const auto &kv : m_oMapMultiPartDefs) { - m_poFS->AbortMultipart(kv.first, kv.second.osUploadID, - poS3HandleHelper.get(), - m_oRetryParameters); + auto poS3HandleHelper = + std::unique_ptr( + m_poFS->CreateHandleHelper( + kv.first.c_str() + m_poFS->GetFSPrefix().size(), + false)); + if (poS3HandleHelper) + { + m_poFS->AbortMultipart(kv.first, kv.second.osUploadID, + poS3HandleHelper.get(), + m_oRetryParameters); + } } } } @@ -4222,8 +4228,8 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, operator=(const CleanupPendingUploads &) = delete; }; - const CleanupPendingUploads cleanupPendingUploads(this, oMapMultiPartDefs, - oRetryParameters); + const CleanupPendingUploads cleanupPendingUploads( + poTargetFSMultipartHandler, oMapMultiPartDefs, oRetryParameters); std::string osTargetDir; // set in the VSI_ISDIR(sSource.st_mode) case std::string osTarget; // set in the !(VSI_ISDIR(sSource.st_mode)) case @@ -4454,9 +4460,11 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, if (poS3HandleHelper == nullptr) return false; - const auto osUploadID = InitiateMultipartUpload( - osSubTarget, poS3HandleHelper.get(), - oRetryParameters, aosObjectCreationOptions.List()); + const auto osUploadID = + poTargetFSMultipartHandler->InitiateMultipartUpload( + osSubTarget, poS3HandleHelper.get(), + oRetryParameters, + aosObjectCreationOptions.List()); if (osUploadID.empty()) { return false; @@ -4663,9 +4671,11 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, if (poS3HandleHelper == nullptr) return false; - const auto osUploadID = InitiateMultipartUpload( - osTarget, poS3HandleHelper.get(), oRetryParameters, - aosObjectCreationOptions.List()); + const auto osUploadID = + poTargetFSMultipartHandler->InitiateMultipartUpload( + osTarget, poS3HandleHelper.get(), + oRetryParameters, + aosObjectCreationOptions.List()); if (osUploadID.empty()) { return false; @@ -4711,6 +4721,7 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, struct JobQueue { IVSIS3LikeFSHandler *poFS; + IVSIS3LikeFSHandlerWithMultipartUpload *poTargetFSMultipartHandler; const std::vector &aoChunksToCopy; const std::vector &anIndexToCopy; std::map &oMapMultiPartDefs; @@ -4729,6 +4740,8 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, const CPLStringList &aosObjectCreationOptions; JobQueue(IVSIS3LikeFSHandler *poFSIn, + IVSIS3LikeFSHandlerWithMultipartUpload + *poTargetFSMultipartHandlerIn, const std::vector &aoChunksToCopyIn, const std::vector &anIndexToCopyIn, std::map &oMapMultiPartDefsIn, @@ -4739,8 +4752,9 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, size_t nMaxChunkSizeIn, const CPLHTTPRetryParameters &oRetryParametersIn, const CPLStringList &aosObjectCreationOptionsIn) - : poFS(poFSIn), aoChunksToCopy(aoChunksToCopyIn), - anIndexToCopy(anIndexToCopyIn), + : poFS(poFSIn), + poTargetFSMultipartHandler(poTargetFSMultipartHandlerIn), + aoChunksToCopy(aoChunksToCopyIn), anIndexToCopy(anIndexToCopyIn), oMapMultiPartDefs(oMapMultiPartDefsIn), osSourceDir(osSourceDirIn), osTargetDir(osTargetDirIn), osSource(osSourceIn), osTarget(osTargetIn), @@ -4832,11 +4846,13 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, ? 0 /* shouldn't happen */ : static_cast(chunk.nStartOffset / queue->nMaxChunkSize)); - const std::string osEtag = queue->poFS->UploadPart( - osSubTarget, nPartNumber, iter->second.osUploadID, - chunk.nStartOffset, pBuffer, nSizeToRead, - poS3HandleHelper.get(), queue->oRetryParameters, - queue->aosObjectCreationOptions.List()); + const std::string osEtag = + queue->poTargetFSMultipartHandler->UploadPart( + osSubTarget, nPartNumber, + iter->second.osUploadID, chunk.nStartOffset, + pBuffer, nSizeToRead, poS3HandleHelper.get(), + queue->oRetryParameters, + queue->aosObjectCreationOptions.List()); if (!osEtag.empty()) { std::lock_guard lock(queue->sMutex); @@ -4885,11 +4901,11 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, } }; - JobQueue sJobQueue(this, aoChunksToCopy, anIndexToCopy, oMapMultiPartDefs, - osSourceWithoutSlash, osTargetDir, osSourceWithoutSlash, - osTarget, bSupportsParallelMultipartUpload, - nMaxChunkSize, oRetryParameters, - aosObjectCreationOptions); + JobQueue sJobQueue(this, poTargetFSMultipartHandler, aoChunksToCopy, + anIndexToCopy, oMapMultiPartDefs, osSourceWithoutSlash, + osTargetDir, osSourceWithoutSlash, osTarget, + bSupportsParallelMultipartUpload, nMaxChunkSize, + oRetryParameters, aosObjectCreationOptions); if (CPLTestBool(CPLGetConfigOption("VSIS3_SYNC_MULTITHREADING", "YES"))) { @@ -4951,9 +4967,10 @@ bool IVSIS3LikeFSHandler::Sync(const char *pszSource, const char *pszTarget, { CPLAssert(kv.second.nCountValidETags == kv.second.nExpectedCount); - if (CompleteMultipart(kv.first, kv.second.osUploadID, - kv.second.aosEtags, kv.second.nTotalSize, - poS3HandleHelper.get(), oRetryParameters)) + if (poTargetFSMultipartHandler->CompleteMultipart( + kv.first, kv.second.osUploadID, kv.second.aosEtags, + kv.second.nTotalSize, poS3HandleHelper.get(), + oRetryParameters)) { sJobQueue.ret = true; oSetKeysToRemove.insert(kv.first); From bb73f93edc36d72baff35053880ceb4a6f9e8fb3 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 28 May 2024 21:38:04 +0200 Subject: [PATCH 0105/1119] Fix test_vsis3_sync_timestamp() to be robust --- autotest/gcore/vsis3.py | 31 +++++++++++++------------------ port/cpl_vsil_s3.cpp | 4 +++- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/autotest/gcore/vsis3.py b/autotest/gcore/vsis3.py index f5d42c6030ad..fbb090749aa1 100755 --- a/autotest/gcore/vsis3.py +++ b/autotest/gcore/vsis3.py @@ -3471,13 +3471,9 @@ def test_vsis3_sync_timestamp(tmp_vsimem, aws_test_config, webserver_port): @gdaltest.enable_exceptions() -@pytest.mark.skipif( - gdaltest.is_ci(), - reason="test skipped on CI due to it not being reliable (also fails randomly when run locally)", -) def test_vsis3_sync_failed(tmp_vsimem, aws_test_config, webserver_port): - gdal.FileFromMemBuffer(tmp_vsimem / "testsync.txt", "foo") + gdal.FileFromMemBuffer(tmp_vsimem / "testsync.txt", "x" * 30000) # S3 to local: S3 file is older -> download gdal.VSICurlClearCache() @@ -3487,18 +3483,11 @@ def test_vsis3_sync_failed(tmp_vsimem, aws_test_config, webserver_port): "/out/testsync.txt", 206, { - "Content-Length": "3", - "Content-Range": "bytes 0-2/3", + "Content-Length": "16384", + "Content-Range": "bytes 0-16383/30000", "Last-Modified": "Mon, 01 Jan 1970 00:00:01 GMT", }, - "xyz", - ) - handler.add( - "GET", - "/out/testsync.txt", - 200, - {"Content-Length": "3", "Last-Modified": "Mon, 01 Jan 1970 00:00:01 GMT"}, - "xy", # only returns 2 bytes instead of 30, {} + "x" * 16384, ) handler.add( "GET", @@ -3511,15 +3500,21 @@ def test_vsis3_sync_failed(tmp_vsimem, aws_test_config, webserver_port): testsync.txt 1970-01-01T00:00:01.000Z - 3 + 30000 """, ) - with webserver.install_http_handler(handler): + handler.add("GET", "/out/testsync.txt", 400) + # Do not use /vsicurl_streaming/ as source, otherwise errors may be + # emitted in worker thread, which isn't properly handled (should ideally + # be fixed) + with gdal.config_option( + "VSIS3_COPYFILE_USE_STREAMING_SOURCE", "NO" + ), webserver.install_http_handler(handler): with pytest.raises( Exception, - match=f"Copying of /vsis3/out/testsync.txt to {tmp_vsimem}/testsync.txt failed: 2 bytes were copied whereas 3 were expected", + match=f"Copying of /vsis3/out/testsync.txt to {tmp_vsimem}/testsync.txt failed: 0 bytes were copied whereas 30000 were expected", ): gdal.Sync("/vsis3/out/testsync.txt", tmp_vsimem) diff --git a/port/cpl_vsil_s3.cpp b/port/cpl_vsil_s3.cpp index ccc1b467c55e..6be5177127e0 100644 --- a/port/cpl_vsil_s3.cpp +++ b/port/cpl_vsil_s3.cpp @@ -3384,7 +3384,9 @@ int IVSIS3LikeFSHandler::CopyFile(const char *pszSource, const char *pszTarget, bool bUsingStreaming = false; if (!fpSource) { - if (STARTS_WITH(pszSource, osPrefix.c_str())) + if (STARTS_WITH(pszSource, osPrefix.c_str()) && + CPLTestBool(CPLGetConfigOption( + "VSIS3_COPYFILE_USE_STREAMING_SOURCE", "YES"))) { // Try to get a streaming path from the source path auto poSourceFSHandler = dynamic_cast( From 94223dd63b6df6ec27ac9871f9a9d4145c1bfa36 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 29 May 2024 03:05:30 +0200 Subject: [PATCH 0106/1119] cpl_vsil_gzip.cpp: fix potential build issue --- port/cpl_vsil_gzip.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/port/cpl_vsil_gzip.cpp b/port/cpl_vsil_gzip.cpp index 0c1ad3570ae5..6bf160e7feeb 100644 --- a/port/cpl_vsil_gzip.cpp +++ b/port/cpl_vsil_gzip.cpp @@ -139,6 +139,11 @@ constexpr int gz_magic[2] = {0x1f, 0x8b}; // gzip magic header CPLError(CE_Failure, CPLE_AppDefined, "In file %s, at line %d, return %d", \ __FILE__, __LINE__, ret) +// To avoid aliasing to CopyFile to CopyFileA on Windows +#ifdef CopyFile +#undef CopyFile +#endif + // #define ENABLE_DEBUG 1 /************************************************************************/ From 4060abcc10c49c26395c44ba36378d3efffa6867 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 3 Jun 2024 21:18:15 +0200 Subject: [PATCH 0107/1119] GeoJSON: avoid false-positive identification as TopoJSON --- .../feature_with_type_Topology_property.json | 7 ++ .../ogr/data/geojson/point_with_utf8bom.json | 2 +- autotest/ogr/ogr_geojson.py | 10 ++ ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp | 118 ++++++++++++------ 4 files changed, 101 insertions(+), 36 deletions(-) create mode 100644 autotest/ogr/data/geojson/feature_with_type_Topology_property.json diff --git a/autotest/ogr/data/geojson/feature_with_type_Topology_property.json b/autotest/ogr/data/geojson/feature_with_type_Topology_property.json new file mode 100644 index 000000000000..9b9f0bbd6dcf --- /dev/null +++ b/autotest/ogr/data/geojson/feature_with_type_Topology_property.json @@ -0,0 +1,7 @@ +{ +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:EPSG::27700" } }, +"features": [ +{ "properties": { "type": "Topology" }, "geometry": null, "type": "Feature" } +], +"type": "FeatureCollection" +} diff --git a/autotest/ogr/data/geojson/point_with_utf8bom.json b/autotest/ogr/data/geojson/point_with_utf8bom.json index e9596eb9425d..28c794825fe6 100644 --- a/autotest/ogr/data/geojson/point_with_utf8bom.json +++ b/autotest/ogr/data/geojson/point_with_utf8bom.json @@ -1 +1 @@ -{ "geometry": { "type": "Point", "coordinates": [ 100.0, 0.0 ] } } \ No newline at end of file +{ "geometry": { "type": "Point", "coordinates": [ 100.0, 0.0 ] }, "type": "Feature" } diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index f905273b2939..46ddb7b0081b 100755 --- a/autotest/ogr/ogr_geojson.py +++ b/autotest/ogr/ogr_geojson.py @@ -5201,3 +5201,13 @@ def test_ogr_geojson_identify_jsonfg_with_geojson(): "data/jsonfg/crs_none.json", allowed_drivers=["GeoJSON", "JSONFG"] ) assert drv.GetDescription() == "JSONFG" + + +############################################################################### +# Test opening a file that has a "type: "Topology" feature property + + +def test_ogr_geojson_feature_with_type_Topology_property(): + + ds = gdal.OpenEx("data/geojson/feature_with_type_Topology_property.json") + assert ds.GetDriver().GetDescription() == "GeoJSON" diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp index 5a7ef0c48fff..c9db8c128ca8 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp @@ -44,6 +44,18 @@ const char szESRIJSonFeaturesGeometryRings[] = // Cf https://github.com/OSGeo/gdal/issues/9996#issuecomment-2129845692 const char szESRIJSonFeaturesAttributes[] = "{\"features\":[{\"attributes\":{"; +/************************************************************************/ +/* SkipUTF8BOM() */ +/************************************************************************/ + +static void SkipUTF8BOM(const char *&pszText) +{ + /* Skip UTF-8 BOM (#5630) */ + const GByte *pabyData = reinterpret_cast(pszText); + if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF) + pszText += 3; +} + /************************************************************************/ /* IsJSONObject() */ /************************************************************************/ @@ -53,10 +65,7 @@ static bool IsJSONObject(const char *pszText) if (nullptr == pszText) return false; - /* Skip UTF-8 BOM (#5630) */ - const GByte *pabyData = reinterpret_cast(pszText); - if (pabyData[0] == 0xEF && pabyData[1] == 0xBB && pabyData[2] == 0xBF) - pszText += 3; + SkipUTF8BOM(pszText); /* -------------------------------------------------------------------- */ /* This is a primitive test, but we need to perform it fast. */ @@ -81,30 +90,71 @@ static bool IsJSONObject(const char *pszText) } /************************************************************************/ -/* IsTypeSomething() */ +/* GetTopLevelType() */ /************************************************************************/ -static bool IsTypeSomething(const char *pszText, const char *pszTypeValue) +static std::string GetTopLevelType(const char *pszText) { - const char *pszIter = pszText; - while (true) + if (!strstr(pszText, "\"type\"")) + return std::string(); + + SkipUTF8BOM(pszText); + + struct MyParser : public CPLJSonStreamingParser { - pszIter = strstr(pszIter, "\"type\""); - if (pszIter == nullptr) - return false; - pszIter += strlen("\"type\""); - while (isspace(static_cast(*pszIter))) - pszIter++; - if (*pszIter != ':') - return false; - pszIter++; - while (isspace(static_cast(*pszIter))) - pszIter++; - CPLString osValue; - osValue.Printf("\"%s\"", pszTypeValue); - if (STARTS_WITH(pszIter, osValue.c_str())) - return true; - } + std::string m_osLevel{}; + bool m_bInTopLevelType = false; + std::string m_osTopLevelTypeValue{}; + + void StartObjectMember(const char *pszKey, size_t nLength) override + { + m_bInTopLevelType = false; + if (nLength == strlen("type") && strcmp(pszKey, "type") == 0 && + m_osLevel == "{") + { + m_bInTopLevelType = true; + } + } + + void String(const char *pszValue, size_t nLength) override + { + if (m_bInTopLevelType) + { + m_osTopLevelTypeValue.assign(pszValue, nLength); + StopParsing(); + } + } + + void StartObject() override + { + m_osLevel += '{'; + m_bInTopLevelType = false; + } + + void EndObject() override + { + if (!m_osLevel.empty()) + m_osLevel.pop_back(); + m_bInTopLevelType = false; + } + + void StartArray() override + { + m_osLevel += '['; + m_bInTopLevelType = false; + } + + void EndArray() override + { + if (!m_osLevel.empty()) + m_osLevel.pop_back(); + m_bInTopLevelType = false; + } + }; + + MyParser oParser; + oParser.Parse(pszText, strlen(pszText), true); + return oParser.m_osTopLevelTypeValue; } /************************************************************************/ @@ -169,7 +219,8 @@ static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence, if (!IsJSONObject(pszText)) return false; - if (IsTypeSomething(pszText, "Topology")) + const std::string osTopLevelType = GetTopLevelType(pszText); + if (osTopLevelType == "Topology") return false; if ((!papszAllowedDrivers || @@ -179,7 +230,7 @@ static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence, return false; } - if (IsTypeSomething(pszText, "FeatureCollection")) + if (osTopLevelType == "FeatureCollection") { return true; } @@ -211,14 +262,11 @@ static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence, return true; } - if (IsTypeSomething(pszText, "Feature") || - IsTypeSomething(pszText, "Point") || - IsTypeSomething(pszText, "LineString") || - IsTypeSomething(pszText, "Polygon") || - IsTypeSomething(pszText, "MultiPoint") || - IsTypeSomething(pszText, "MultiLineString") || - IsTypeSomething(pszText, "MultiPolygon") || - IsTypeSomething(pszText, "GeometryCollection")) + if (osTopLevelType == "Feature" || osTopLevelType == "Point" || + osTopLevelType == "LineString" || osTopLevelType == "Polygon" || + osTopLevelType == "MultiPoint" || osTopLevelType == "MultiLineString" || + osTopLevelType == "MultiPolygon" || + osTopLevelType == "GeometryCollection") { bMightBeSequence = true; return true; @@ -287,7 +335,7 @@ bool TopoJSONIsObject(const char *pszText) if (!IsJSONObject(pszText)) return false; - return IsTypeSomething(pszText, "Topology"); + return GetTopLevelType(pszText) == "Topology"; } /************************************************************************/ From 30e23036d5545f3d1ae4c82e4f0036356cce853e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 3 Jun 2024 22:54:16 +0200 Subject: [PATCH 0108/1119] Add GDALOpenInfo::IsSingleAllowedDriver() --- gcore/gdal_priv.h | 2 ++ gcore/gdalopeninfo.cpp | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/gcore/gdal_priv.h b/gcore/gdal_priv.h index e277c3f440c6..6e6126c50f4b 100644 --- a/gcore/gdal_priv.h +++ b/gcore/gdal_priv.h @@ -339,6 +339,8 @@ class CPL_DLL GDALOpenInfo char **StealSiblingFiles(); bool AreSiblingFilesLoaded() const; + bool IsSingleAllowedDriver(const char *pszDriverName) const; + private: CPL_DISALLOW_COPY_ASSIGN(GDALOpenInfo) }; diff --git a/gcore/gdalopeninfo.cpp b/gcore/gdalopeninfo.cpp index a93d52eebbed..e4220e5b6862 100644 --- a/gcore/gdalopeninfo.cpp +++ b/gcore/gdalopeninfo.cpp @@ -487,3 +487,22 @@ int GDALOpenInfo::TryToIngest(int nBytes) return TRUE; } + +/************************************************************************/ +/* IsSingleAllowedDriver() */ +/************************************************************************/ + +/** Returns true if the driver name is the single in the list of allowed + * drivers. + * + * @param pszDriverName Driver name to test. + * @return true if the driver name is the single in the list of allowed + * drivers. + * @since GDAL 3.10 + */ +bool GDALOpenInfo::IsSingleAllowedDriver(const char *pszDriverName) const +{ + return papszAllowedDrivers && papszAllowedDrivers[0] && + !papszAllowedDrivers[1] && + EQUAL(papszAllowedDrivers[0], pszDriverName); +} From 21b66800a59b14f296ac784ea3bf1e416f440b49 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 3 Jun 2024 23:49:24 +0200 Subject: [PATCH 0109/1119] GeoJSON/GeoJSONSeq/TopoJSON/ESRIJSON/JSONFG: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/ogr/ogr_esrijson.py | 31 ++++ autotest/ogr/ogr_geojson.py | 39 ++++ autotest/ogr/ogr_geojsonseq.py | 25 +++ autotest/ogr/ogr_jsonfg.py | 24 +++ autotest/ogr/ogr_topojson.py | 40 ++++- doc/source/drivers/vector/esrijson.rst | 7 +- doc/source/drivers/vector/geojson.rst | 6 +- doc/source/drivers/vector/geojsonseq.rst | 6 +- doc/source/drivers/vector/jsonfg.rst | 6 +- doc/source/drivers/vector/topojson.rst | 6 +- ogr/ogrsf_frmts/geojson/ogresrijsondriver.cpp | 10 +- .../geojson/ogrgeojsondatasource.cpp | 16 +- ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp | 13 +- .../geojson/ogrgeojsonseqdriver.cpp | 12 +- ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp | 170 ++++++++++++------ ogr/ogrsf_frmts/geojson/ogrgeojsonutils.h | 10 +- ogr/ogrsf_frmts/geojson/ogrtopojsondriver.cpp | 10 +- ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp | 4 +- ogr/ogrsf_frmts/jsonfg/ogrjsonfgdriver.cpp | 10 +- 19 files changed, 351 insertions(+), 94 deletions(-) diff --git a/autotest/ogr/ogr_esrijson.py b/autotest/ogr/ogr_esrijson.py index 3b93528c5141..c8f77e96f1b2 100755 --- a/autotest/ogr/ogr_esrijson.py +++ b/autotest/ogr/ogr_esrijson.py @@ -704,3 +704,34 @@ def test_ogr_esrijson_read_CadastralSpecialServices(): f = lyr.GetNextFeature() assert f["landdescription"] == "WA330160N0260E0SN070" assert f.GetGeometryRef().GetGeometryType() == ogr.wkbPolygon + + +############################################################################### +# Test force opening a ESRIJSON file + + +def test_ogr_esrijson_force_opening(tmp_vsimem): + + filename = str(tmp_vsimem / "test.json") + + with open("data/esrijson/esripoint.json", "rb") as fsrc: + with gdaltest.vsi_open(filename, "wb") as fdest: + fdest.write(fsrc.read(1)) + fdest.write(b" " * (1000 * 1000)) + fdest.write(fsrc.read()) + + with pytest.raises(Exception): + gdal.OpenEx(filename) + + ds = gdal.OpenEx(filename, allowed_drivers=["ESRIJSON"]) + assert ds.GetDriver().GetDescription() == "ESRIJSON" + + +############################################################################### +# Test force opening a URL as ESRIJSON + + +def test_ogr_esrijson_force_opening_url(): + + drv = gdal.IdentifyDriverEx("http://example.com", allowed_drivers=["ESRIJSON"]) + assert drv.GetDescription() == "ESRIJSON" diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index 46ddb7b0081b..010b5d57098d 100755 --- a/autotest/ogr/ogr_geojson.py +++ b/autotest/ogr/ogr_geojson.py @@ -5211,3 +5211,42 @@ def test_ogr_geojson_feature_with_type_Topology_property(): ds = gdal.OpenEx("data/geojson/feature_with_type_Topology_property.json") assert ds.GetDriver().GetDescription() == "GeoJSON" + + +############################################################################### +# Test force opening a GeoJSON file + + +def test_ogr_geojson_force_opening(tmp_vsimem): + + filename = str(tmp_vsimem / "test.json") + + with gdaltest.vsi_open(filename, "wb") as f: + f.write( + b"{" + + b" " * (1000 * 1000) + + b' "type": "FeatureCollection", "features":[]}' + ) + + with pytest.raises(Exception): + gdal.OpenEx(filename) + + ds = gdal.OpenEx(filename, allowed_drivers=["GeoJSON"]) + assert ds.GetDriver().GetDescription() == "GeoJSON" + + drv = gdal.IdentifyDriverEx("http://example.com", allowed_drivers=["GeoJSON"]) + assert drv.GetDescription() == "GeoJSON" + + +############################################################################### +# Test force opening a STACTA file with GeoJSON + + +def test_ogr_geojson_force_opening_stacta(): + + if gdal.GetDriverByName("STACTA"): + ds = gdal.OpenEx("../gdrivers/data/stacta/test.json") + assert ds.GetDriver().GetDescription() == "STACTA" + + ds = gdal.OpenEx("../gdrivers/data/stacta/test.json", allowed_drivers=["GeoJSON"]) + assert ds.GetDriver().GetDescription() == "GeoJSON" diff --git a/autotest/ogr/ogr_geojsonseq.py b/autotest/ogr/ogr_geojsonseq.py index b60a8969aa11..5d0862999e21 100755 --- a/autotest/ogr/ogr_geojsonseq.py +++ b/autotest/ogr/ogr_geojsonseq.py @@ -490,3 +490,28 @@ def test_ogr_geojsonseq_geom_coord_precision_not_4326(tmp_vsimem): gdal.VSIFCloseL(f) assert b'"coordinates": [ 2.363925, 45.151706, 9.877 ]' in data + + +############################################################################### +# Test force opening a GeoJSONSeq file + + +def test_ogr_geojsonseq_force_opening(tmp_vsimem): + + filename = str(tmp_vsimem / "test.json") + + with gdaltest.vsi_open(filename, "wb") as f: + f.write( + b"{" + + b" " * (1000 * 1000) + + b' "type": "Feature", "properties":{},"geometry":null}\n' + ) + + with pytest.raises(Exception): + gdal.OpenEx(filename) + + ds = gdal.OpenEx(filename, allowed_drivers=["GeoJSONSeq"]) + assert ds.GetDriver().GetDescription() == "GeoJSONSeq" + + drv = gdal.IdentifyDriverEx("http://example.com", allowed_drivers=["GeoJSONSeq"]) + assert drv.GetDescription() == "GeoJSONSeq" diff --git a/autotest/ogr/ogr_jsonfg.py b/autotest/ogr/ogr_jsonfg.py index 55db4212ce79..beab0559103a 100755 --- a/autotest/ogr/ogr_jsonfg.py +++ b/autotest/ogr/ogr_jsonfg.py @@ -1292,3 +1292,27 @@ def test_ogr_jsonfg_geom_coord_precision(tmp_vsimem, single_layer): prec = geom_fld.GetCoordinatePrecision() assert prec.GetXYResolution() == 1e-2 assert prec.GetZResolution() == 1e-3 + + +############################################################################### +# Test force opening a GeoJSON file with JSONFG + + +def test_ogr_jsonfg_force_opening(): + + if ogr.GetDriverByName("GeoJSON"): + ds = gdal.OpenEx("data/geojson/featuretype.json") + assert ds.GetDriver().GetDescription() == "GeoJSON" + + ds = gdal.OpenEx("data/geojson/featuretype.json", allowed_drivers=["JSONFG"]) + assert ds.GetDriver().GetDescription() == "JSONFG" + + +############################################################################### +# Test force opening a URL as JSONFG + + +def test_ogr_jsonfg_force_opening_url(): + + drv = gdal.IdentifyDriverEx("http://example.com", allowed_drivers=["JSONFG"]) + assert drv.GetDescription() == "JSONFG" diff --git a/autotest/ogr/ogr_topojson.py b/autotest/ogr/ogr_topojson.py index e892dd1204ca..1580ca7d7c9f 100755 --- a/autotest/ogr/ogr_topojson.py +++ b/autotest/ogr/ogr_topojson.py @@ -29,16 +29,17 @@ # DEALINGS IN THE SOFTWARE. ############################################################################### +import gdaltest import ogrtest import pytest -from osgeo import ogr +from osgeo import gdal, ogr ############################################################################### # Test TopoJSON -def test_ogr_toposjon_objects_is_array(): +def test_ogr_topojson_objects_is_array(): ds = ogr.Open("data/topojson/topojson1.topojson") lyr = ds.GetLayer(0) @@ -123,7 +124,7 @@ def test_ogr_toposjon_objects_is_array(): ds = None -def test_ogr_toposjon_objects_is_dict(): +def test_ogr_topojson_objects_is_dict(): ds = ogr.Open("data/topojson/topojson2.topojson") lyr = ds.GetLayer(0) @@ -144,7 +145,7 @@ def test_ogr_toposjon_objects_is_dict(): ds = None -def test_ogr_toposjon_no_transform(): +def test_ogr_topojson_no_transform(): ds = ogr.Open("data/topojson/topojson3.topojson") lyr = ds.GetLayer(0) @@ -157,3 +158,34 @@ def test_ogr_toposjon_no_transform(): feat = lyr.GetNextFeature() ogrtest.check_feature_geometry(feat, "LINESTRING (0 0,10 0,0 10,10 0,0 0)") ds = None + + +############################################################################### +# Test force opening a TopoJSON file + + +def test_ogr_topojson_force_opening(tmp_vsimem): + + filename = str(tmp_vsimem / "test.json") + + with open("data/topojson/topojson1.topojson", "rb") as fsrc: + with gdaltest.vsi_open(filename, "wb") as fdest: + fdest.write(fsrc.read(1)) + fdest.write(b" " * (1000 * 1000)) + fdest.write(fsrc.read()) + + with pytest.raises(Exception): + gdal.OpenEx(filename) + + ds = gdal.OpenEx(filename, allowed_drivers=["TopoJSON"]) + assert ds.GetDriver().GetDescription() == "TopoJSON" + + +############################################################################### +# Test force opening a URL as TopoJSON + + +def test_ogr_topojson_force_opening_url(): + + drv = gdal.IdentifyDriverEx("http://example.com", allowed_drivers=["TopoJSON"]) + assert drv.GetDescription() == "TopoJSON" diff --git a/doc/source/drivers/vector/esrijson.rst b/doc/source/drivers/vector/esrijson.rst index 5883e2ccbd8b..280e15bb547f 100644 --- a/doc/source/drivers/vector/esrijson.rst +++ b/doc/source/drivers/vector/esrijson.rst @@ -49,7 +49,12 @@ The driver accepts three types of sources of data: - Text passed directly and encoded in ESRI JSON Starting with GDAL 2.3, the URL/filename/text might be prefixed with -ESRIJSON: to avoid any ambiguity with other drivers. +ESRIJSON: to avoid any ambiguity with other drivers. Alternatively, starting +with GDAL 3.10, specifying the ``-if ESRIJSON`` option to command line utilities +accepting it, or ``ESRIJSON`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +URL/filename/text. + Open options ------------ diff --git a/doc/source/drivers/vector/geojson.rst b/doc/source/drivers/vector/geojson.rst index b1f9d06be97d..0621e70791a9 100644 --- a/doc/source/drivers/vector/geojson.rst +++ b/doc/source/drivers/vector/geojson.rst @@ -48,7 +48,11 @@ The OGR GeoJSON driver accepts three types of sources of data: - Text passed directly and encoded in GeoJSON Starting with GDAL 2.3, the URL/filename/text might be prefixed with -GeoJSON: to avoid any ambiguity with other drivers. +GeoJSON: to avoid any ambiguity with other drivers. Alternatively, starting +with GDAL 3.10, specifying the ``-if GeoJSON`` option to command line utilities +accepting it, or ``GeoJSON`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +URL/filename/text. Layer ----- diff --git a/doc/source/drivers/vector/geojsonseq.rst b/doc/source/drivers/vector/geojsonseq.rst index 1701d06dc9a0..83256ac24743 100644 --- a/doc/source/drivers/vector/geojsonseq.rst +++ b/doc/source/drivers/vector/geojsonseq.rst @@ -46,7 +46,11 @@ The driver accepts three types of sources of data: - Text passed directly as filename, and encoded as GeoJSON sequences The URL/filename/text might be prefixed with GeoJSONSeq: to avoid any -ambiguity with other drivers. +ambiguity with other drivers. Alternatively, starting +with GDAL 3.10, specifying the ``-if GeoJSONSeq`` option to command line utilities +accepting it, or ``GeoJSONSeq`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +URL/filename/text. Configuration options --------------------- diff --git a/doc/source/drivers/vector/jsonfg.rst b/doc/source/drivers/vector/jsonfg.rst index cffa11a8b6f9..b25120a7bf88 100644 --- a/doc/source/drivers/vector/jsonfg.rst +++ b/doc/source/drivers/vector/jsonfg.rst @@ -50,7 +50,11 @@ The JSON-FG driver accepts three types of sources of data: - Text passed directly and encoded in JSON-FG The URL/filename/text might be prefixed with -``JSONFG:`` to avoid any ambiguity with other drivers. +``JSONFG:`` to avoid any ambiguity with other drivers. Alternatively, starting +with GDAL 3.10, specifying the ``-if JSONFG`` option to command line utilities +accepting it, or ``JSONFG`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +URL/filename/text. Time support ------------ diff --git a/doc/source/drivers/vector/topojson.rst b/doc/source/drivers/vector/topojson.rst index 36879bae6fd3..2604be2aa6c5 100644 --- a/doc/source/drivers/vector/topojson.rst +++ b/doc/source/drivers/vector/topojson.rst @@ -33,7 +33,11 @@ The driver accepts three types of sources of data: - Text passed directly and encoded in Topo JSON Starting with GDAL 2.3, the URL/filename/text might be prefixed with -TopoJSON: to avoid any ambiguity with other drivers. +TopoJSON: to avoid any ambiguity with other drivers. Alternatively, starting +with GDAL 3.10, specifying the ``-if TopoJSON`` option to command line utilities +accepting it, or ``TopoJSON`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +URL/filename/text. See Also -------- diff --git a/ogr/ogrsf_frmts/geojson/ogresrijsondriver.cpp b/ogr/ogrsf_frmts/geojson/ogresrijsondriver.cpp index ce6d18211551..6a386e701581 100644 --- a/ogr/ogrsf_frmts/geojson/ogresrijsondriver.cpp +++ b/ogr/ogrsf_frmts/geojson/ogresrijsondriver.cpp @@ -48,10 +48,14 @@ static int OGRESRIJSONDriverIdentify(GDALOpenInfo *poOpenInfo) GeoJSONSourceType nSrcType = ESRIJSONDriverGetSourceType(poOpenInfo); if (nSrcType == eGeoJSONSourceUnknown) return FALSE; - if (nSrcType == eGeoJSONSourceService && - !STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:")) + if (nSrcType == eGeoJSONSourceService) { - return -1; + if (poOpenInfo->IsSingleAllowedDriver("ESRIJSON")) + return TRUE; + if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "ESRIJSON:")) + { + return -1; + } } return TRUE; } diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp index ad83a8672983..0a8b0ed23a67 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsondatasource.cpp @@ -815,8 +815,9 @@ int OGRGeoJSONDataSource::ReadFromService(GDALOpenInfo *poOpenInfo, if (pszStoredContent != nullptr) { if ((osJSonFlavor_ == "ESRIJSON" && - ESRIJSONIsObject(pszStoredContent)) || - (osJSonFlavor_ == "TopoJSON" && TopoJSONIsObject(pszStoredContent))) + ESRIJSONIsObject(pszStoredContent, poOpenInfo)) || + (osJSonFlavor_ == "TopoJSON" && + TopoJSONIsObject(pszStoredContent, poOpenInfo))) { pszGeoData_ = pszStoredContent; nGeoDataLen_ = strlen(pszGeoData_); @@ -882,11 +883,12 @@ int OGRGeoJSONDataSource::ReadFromService(GDALOpenInfo *poOpenInfo, /* -------------------------------------------------------------------- */ if (EQUAL(pszSource, poOpenInfo->pszFilename) && osJSonFlavor_ == "GeoJSON") { - if (!GeoJSONIsObject(pszGeoData_, poOpenInfo->papszAllowedDrivers)) + if (!GeoJSONIsObject(pszGeoData_, poOpenInfo)) { - if (ESRIJSONIsObject(pszGeoData_) || - TopoJSONIsObject(pszGeoData_) || - GeoJSONSeqIsObject(pszGeoData_) || JSONFGIsObject(pszGeoData_)) + if (ESRIJSONIsObject(pszGeoData_, poOpenInfo) || + TopoJSONIsObject(pszGeoData_, poOpenInfo) || + GeoJSONSeqIsObject(pszGeoData_, poOpenInfo) || + JSONFGIsObject(pszGeoData_, poOpenInfo)) { OGRGeoJSONDriverStoreContent(pszSource, pszGeoData_); pszGeoData_ = nullptr; @@ -1003,7 +1005,7 @@ void OGRGeoJSONDataSource::LoadLayers(GDALOpenInfo *poOpenInfo, oOpenInfo.fpL = nullptr; } - if (!GeoJSONIsObject(pszGeoData_, poOpenInfo->papszAllowedDrivers)) + if (!GeoJSONIsObject(pszGeoData_, poOpenInfo)) { CPLDebug(pszJSonFlavor, "No valid %s data found in source '%s'", pszJSonFlavor, pszName_); diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp index 74504d7d6edf..4ee508888a8b 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp @@ -473,10 +473,15 @@ static int OGRGeoJSONDriverIdentifyInternal(GDALOpenInfo *poOpenInfo, return FALSE; } - if (nSrcType == eGeoJSONSourceService && - !STARTS_WITH_CI(poOpenInfo->pszFilename, "GeoJSON:")) + + if (nSrcType == eGeoJSONSourceService) { - return -1; + if (poOpenInfo->IsSingleAllowedDriver("GeoJSON")) + return TRUE; + if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "GeoJSON:")) + { + return -1; + } } // If this looks like a file that can be handled by the STACTA driver, @@ -488,6 +493,8 @@ static int OGRGeoJSONDriverIdentifyInternal(GDALOpenInfo *poOpenInfo, strstr(pszHeader, "\"tiled-assets\"") != nullptr && GDALGetDriverByName("STACTA") != nullptr) { + if (poOpenInfo->IsSingleAllowedDriver("GeoJSON")) + return TRUE; return FALSE; } diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp index cb7b008e0ada..782c3fc2bd61 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonseqdriver.cpp @@ -831,7 +831,7 @@ bool OGRGeoJSONSeqDataSource::Open(GDALOpenInfo *poOpenInfo, OGRGeoJSONDriverStealStoredContent(pszUnprefixedFilename); if (pszStoredContent) { - if (!GeoJSONSeqIsObject(pszStoredContent)) + if (!GeoJSONSeqIsObject(pszStoredContent, poOpenInfo)) { OGRGeoJSONDriverStoreContent(poOpenInfo->pszFilename, pszStoredContent); @@ -953,10 +953,14 @@ static int OGRGeoJSONSeqDriverIdentifyInternal(GDALOpenInfo *poOpenInfo, nSrcType = GeoJSONSeqGetSourceType(poOpenInfo); if (nSrcType == eGeoJSONSourceUnknown) return FALSE; - if (nSrcType == eGeoJSONSourceService && - !STARTS_WITH_CI(poOpenInfo->pszFilename, "GeoJSONSeq:")) + if (nSrcType == eGeoJSONSourceService) { - return -1; + if (poOpenInfo->IsSingleAllowedDriver("GeoJSONSeq")) + return TRUE; + if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "GeoJSONSeq:")) + { + return -1; + } } return TRUE; } diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp index c9db8c128ca8..6a0655b3fae0 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.cpp @@ -210,8 +210,8 @@ static CPLString GetCompactJSon(const char *pszText, size_t nMaxSize) /************************************************************************/ static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence, - bool &bReadMoreBytes, - CSLConstList papszAllowedDrivers) + bool &bReadMoreBytes, GDALOpenInfo *poOpenInfo, + const char *pszExpectedDriverName) { bMightBeSequence = false; bReadMoreBytes = false; @@ -223,9 +223,15 @@ static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence, if (osTopLevelType == "Topology") return false; - if ((!papszAllowedDrivers || - CSLFindString(papszAllowedDrivers, "JSONFG") >= 0) && - GDALGetDriverByName("JSONFG") && JSONFGIsObject(pszText)) + if (poOpenInfo->IsSingleAllowedDriver(pszExpectedDriverName) && + GDALGetDriverByName(pszExpectedDriverName)) + { + return true; + } + + if ((!poOpenInfo->papszAllowedDrivers || + CSLFindString(poOpenInfo->papszAllowedDrivers, "JSONFG") >= 0) && + GDALGetDriverByName("JSONFG") && JSONFGIsObject(pszText, poOpenInfo)) { return false; } @@ -251,7 +257,7 @@ static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence, // "{"bbox":...,"features":[..." if (osWithoutSpace.find(",\"features\":[") != std::string::npos) { - return !ESRIJSONIsObject(pszText); + return !ESRIJSONIsObject(pszText, poOpenInfo); } // See https://github.com/OSGeo/gdal/issues/2720 @@ -283,23 +289,30 @@ static bool IsGeoJSONLikeObject(const char *pszText, bool &bMightBeSequence, return false; } -static bool IsGeoJSONLikeObject(const char *pszText) +static bool IsGeoJSONLikeObject(const char *pszText, GDALOpenInfo *poOpenInfo, + const char *pszExpectedDriverName) { bool bMightBeSequence; bool bReadMoreBytes; return IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes, - nullptr); + poOpenInfo, pszExpectedDriverName); } /************************************************************************/ /* ESRIJSONIsObject() */ /************************************************************************/ -bool ESRIJSONIsObject(const char *pszText) +bool ESRIJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo) { if (!IsJSONObject(pszText)) return false; + if (poOpenInfo->IsSingleAllowedDriver("ESRIJSON") && + GDALGetDriverByName("ESRIJSON")) + { + return true; + } + if ( // ESRI Json geometry (strstr(pszText, "\"geometryType\"") != nullptr && strstr(pszText, "\"esriGeometry") != nullptr) @@ -330,11 +343,17 @@ bool ESRIJSONIsObject(const char *pszText) /* TopoJSONIsObject() */ /************************************************************************/ -bool TopoJSONIsObject(const char *pszText) +bool TopoJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo) { if (!IsJSONObject(pszText)) return false; + if (poOpenInfo->IsSingleAllowedDriver("TopoJSON") && + GDALGetDriverByName("TopoJSON")) + { + return true; + } + return GetTopLevelType(pszText) == "Topology"; } @@ -342,9 +361,9 @@ bool TopoJSONIsObject(const char *pszText) /* IsLikelyNewlineSequenceGeoJSON() */ /************************************************************************/ -static bool IsLikelyNewlineSequenceGeoJSON(VSILFILE *fpL, - const GByte *pabyHeader, - const char *pszFileContent) +static GDALIdentifyEnum +IsLikelyNewlineSequenceGeoJSON(VSILFILE *fpL, const GByte *pabyHeader, + const char *pszFileContent) { const size_t nBufferSize = 4096 * 10; std::vector abyBuffer; @@ -353,7 +372,6 @@ static bool IsLikelyNewlineSequenceGeoJSON(VSILFILE *fpL, int nCurlLevel = 0; bool bInString = false; bool bLastIsEscape = false; - bool bCompatibleOfSequence = true; bool bFirstIter = true; bool bEOLFound = false; int nCountObject = 0; @@ -399,8 +417,7 @@ static bool IsLikelyNewlineSequenceGeoJSON(VSILFILE *fpL, } else if (!isspace(static_cast(abyBuffer[i]))) { - bCompatibleOfSequence = false; - break; + return GDAL_IDENTIFY_FALSE; } } else if (bInString) @@ -431,10 +448,12 @@ static bool IsLikelyNewlineSequenceGeoJSON(VSILFILE *fpL, nCurlLevel--; } } - if (!fpL || bEnd || !bCompatibleOfSequence || nCountObject == 2) + if (!fpL || bEnd || nCountObject == 2) break; } - return bCompatibleOfSequence && bEOLFound && nCountObject == 2; + if (bEOLFound && nCountObject == 2) + return GDAL_IDENTIFY_TRUE; + return GDAL_IDENTIFY_UNKNOWN; } /************************************************************************/ @@ -456,40 +475,40 @@ static bool GeoJSONFileIsObject(GDALOpenInfo *poOpenInfo) bool bReadMoreBytes = false; if (!IsGeoJSONLikeObject( reinterpret_cast(poOpenInfo->pabyHeader), - bMightBeSequence, bReadMoreBytes, poOpenInfo->papszAllowedDrivers)) + bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSON")) { if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 && poOpenInfo->TryToIngest(1000 * 1000) && !IsGeoJSONLikeObject( reinterpret_cast(poOpenInfo->pabyHeader), - bMightBeSequence, bReadMoreBytes, - poOpenInfo->papszAllowedDrivers))) + bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSON"))) { return false; } } - return !(bMightBeSequence && - IsLikelyNewlineSequenceGeoJSON(poOpenInfo->fpL, - poOpenInfo->pabyHeader, nullptr)); + return !(bMightBeSequence && IsLikelyNewlineSequenceGeoJSON( + poOpenInfo->fpL, poOpenInfo->pabyHeader, + nullptr) == GDAL_IDENTIFY_TRUE); } /************************************************************************/ /* GeoJSONIsObject() */ /************************************************************************/ -bool GeoJSONIsObject(const char *pszText, CSLConstList papszAllowedDrivers) +bool GeoJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo) { bool bMightBeSequence = false; bool bReadMoreBytes = false; if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes, - papszAllowedDrivers)) + poOpenInfo, "GeoJSON")) { return false; } return !(bMightBeSequence && - IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText)); + IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText) == + GDAL_IDENTIFY_TRUE); } /************************************************************************/ @@ -510,44 +529,60 @@ static bool GeoJSONSeqFileIsObject(GDALOpenInfo *poOpenInfo) const char *pszText = reinterpret_cast(poOpenInfo->pabyHeader); if (pszText[0] == '\x1e') - return IsGeoJSONLikeObject(pszText + 1); + return IsGeoJSONLikeObject(pszText + 1, poOpenInfo, "GeoJSONSeq"); bool bMightBeSequence = false; bool bReadMoreBytes = false; if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes, - poOpenInfo->papszAllowedDrivers)) + poOpenInfo, "GeoJSONSeq")) { if (!(bReadMoreBytes && poOpenInfo->nHeaderBytes >= 6000 && poOpenInfo->TryToIngest(1000 * 1000) && IsGeoJSONLikeObject( reinterpret_cast(poOpenInfo->pabyHeader), - bMightBeSequence, bReadMoreBytes, - poOpenInfo->papszAllowedDrivers))) + bMightBeSequence, bReadMoreBytes, poOpenInfo, "GeoJSONSeq"))) { return false; } } - return bMightBeSequence && - IsLikelyNewlineSequenceGeoJSON(poOpenInfo->fpL, - poOpenInfo->pabyHeader, nullptr); + if (poOpenInfo->IsSingleAllowedDriver("GeoJSONSeq") && + IsLikelyNewlineSequenceGeoJSON(poOpenInfo->fpL, poOpenInfo->pabyHeader, + nullptr) != GDAL_IDENTIFY_FALSE && + GDALGetDriverByName("GeoJSONSeq")) + { + return true; + } + + return bMightBeSequence && IsLikelyNewlineSequenceGeoJSON( + poOpenInfo->fpL, poOpenInfo->pabyHeader, + nullptr) == GDAL_IDENTIFY_TRUE; } -bool GeoJSONSeqIsObject(const char *pszText) +bool GeoJSONSeqIsObject(const char *pszText, GDALOpenInfo *poOpenInfo) { if (pszText[0] == '\x1e') - return IsGeoJSONLikeObject(pszText + 1); + return IsGeoJSONLikeObject(pszText + 1, poOpenInfo, "GeoJSONSeq"); bool bMightBeSequence = false; bool bReadMoreBytes = false; if (!IsGeoJSONLikeObject(pszText, bMightBeSequence, bReadMoreBytes, - nullptr)) + poOpenInfo, "GeoJSONSeq")) { return false; } + if (poOpenInfo->IsSingleAllowedDriver("GeoJSONSeq") && + IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText) != + GDAL_IDENTIFY_FALSE && + GDALGetDriverByName("GeoJSONSeq")) + { + return true; + } + return bMightBeSequence && - IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText); + IsLikelyNewlineSequenceGeoJSON(nullptr, nullptr, pszText) == + GDAL_IDENTIFY_TRUE; } /************************************************************************/ @@ -564,14 +599,20 @@ static bool JSONFGFileIsObject(GDALOpenInfo *poOpenInfo) const char *pszText = reinterpret_cast(poOpenInfo->pabyHeader); - return JSONFGIsObject(pszText); + return JSONFGIsObject(pszText, poOpenInfo); } -bool JSONFGIsObject(const char *pszText) +bool JSONFGIsObject(const char *pszText, GDALOpenInfo *poOpenInfo) { if (!IsJSONObject(pszText)) return false; + if (poOpenInfo->IsSingleAllowedDriver("JSONFG") && + GDALGetDriverByName("JSONFG")) + { + return true; + } + const std::string osWithoutSpace = GetCompactJSon(pszText, strlen(pszText)); // In theory, conformsTo should be required, but let be lax... @@ -709,6 +750,10 @@ GeoJSONSourceType GeoJSONGetSourceType(GDALOpenInfo *poOpenInfo) STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") || STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://")) { + if (poOpenInfo->IsSingleAllowedDriver("GeoJSON")) + { + return eGeoJSONSourceService; + } if ((strstr(poOpenInfo->pszFilename, "SERVICE=WFS") || strstr(poOpenInfo->pszFilename, "service=WFS") || strstr(poOpenInfo->pszFilename, "service=wfs")) && @@ -730,12 +775,11 @@ GeoJSONSourceType GeoJSONGetSourceType(GDALOpenInfo *poOpenInfo) return eGeoJSONSourceFile; } const char *pszText = poOpenInfo->pszFilename + strlen("GeoJSON:"); - if (GeoJSONIsObject(pszText, poOpenInfo->papszAllowedDrivers)) + if (GeoJSONIsObject(pszText, poOpenInfo)) return eGeoJSONSourceText; return eGeoJSONSourceUnknown; } - else if (GeoJSONIsObject(poOpenInfo->pszFilename, - poOpenInfo->papszAllowedDrivers)) + else if (GeoJSONIsObject(poOpenInfo->pszFilename, poOpenInfo)) { srcType = eGeoJSONSourceText; } @@ -763,6 +807,10 @@ GeoJSONSourceType ESRIJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo) STARTS_WITH(poOpenInfo->pszFilename, "https://") || STARTS_WITH(poOpenInfo->pszFilename, "ftp://")) { + if (poOpenInfo->IsSingleAllowedDriver("ESRIJSON")) + { + return eGeoJSONSourceService; + } if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename)) { return eGeoJSONSourceService; @@ -779,7 +827,7 @@ GeoJSONSourceType ESRIJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo) return eGeoJSONSourceFile; } const char *pszText = poOpenInfo->pszFilename + strlen("ESRIJSON:"); - if (ESRIJSONIsObject(pszText)) + if (ESRIJSONIsObject(pszText, poOpenInfo)) return eGeoJSONSourceText; return eGeoJSONSourceUnknown; } @@ -787,7 +835,7 @@ GeoJSONSourceType ESRIJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo) if (poOpenInfo->fpL == nullptr) { const char *pszText = poOpenInfo->pszFilename; - if (ESRIJSONIsObject(pszText)) + if (ESRIJSONIsObject(pszText, poOpenInfo)) return eGeoJSONSourceText; return eGeoJSONSourceUnknown; } @@ -801,8 +849,8 @@ GeoJSONSourceType ESRIJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo) } if (poOpenInfo->pabyHeader != nullptr && - ESRIJSONIsObject( - reinterpret_cast(poOpenInfo->pabyHeader))) + ESRIJSONIsObject(reinterpret_cast(poOpenInfo->pabyHeader), + poOpenInfo)) { return eGeoJSONSourceFile; } @@ -825,6 +873,10 @@ GeoJSONSourceType TopoJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo) STARTS_WITH(poOpenInfo->pszFilename, "https://") || STARTS_WITH(poOpenInfo->pszFilename, "ftp://")) { + if (poOpenInfo->IsSingleAllowedDriver("TOPOJSON")) + { + return eGeoJSONSourceService; + } if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename)) { return eGeoJSONSourceUnknown; @@ -841,7 +893,7 @@ GeoJSONSourceType TopoJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo) return eGeoJSONSourceFile; } const char *pszText = poOpenInfo->pszFilename + strlen("TopoJSON:"); - if (TopoJSONIsObject(pszText)) + if (TopoJSONIsObject(pszText, poOpenInfo)) return eGeoJSONSourceText; return eGeoJSONSourceUnknown; } @@ -849,7 +901,7 @@ GeoJSONSourceType TopoJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo) if (poOpenInfo->fpL == nullptr) { const char *pszText = poOpenInfo->pszFilename; - if (TopoJSONIsObject(pszText)) + if (TopoJSONIsObject(pszText, poOpenInfo)) return eGeoJSONSourceText; return eGeoJSONSourceUnknown; } @@ -863,8 +915,8 @@ GeoJSONSourceType TopoJSONDriverGetSourceType(GDALOpenInfo *poOpenInfo) } if (poOpenInfo->pabyHeader != nullptr && - TopoJSONIsObject( - reinterpret_cast(poOpenInfo->pabyHeader))) + TopoJSONIsObject(reinterpret_cast(poOpenInfo->pabyHeader), + poOpenInfo)) { return eGeoJSONSourceFile; } @@ -889,6 +941,10 @@ GeoJSONSourceType GeoJSONSeqGetSourceType(GDALOpenInfo *poOpenInfo) STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") || STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://")) { + if (poOpenInfo->IsSingleAllowedDriver("GeoJSONSeq")) + { + return eGeoJSONSourceService; + } if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename)) { return eGeoJSONSourceUnknown; @@ -904,11 +960,11 @@ GeoJSONSourceType GeoJSONSeqGetSourceType(GDALOpenInfo *poOpenInfo) return eGeoJSONSourceFile; } const char *pszText = poOpenInfo->pszFilename + strlen("GEOJSONSeq:"); - if (GeoJSONSeqIsObject(pszText)) + if (GeoJSONSeqIsObject(pszText, poOpenInfo)) return eGeoJSONSourceText; return eGeoJSONSourceUnknown; } - else if (GeoJSONSeqIsObject(poOpenInfo->pszFilename)) + else if (GeoJSONSeqIsObject(poOpenInfo->pszFilename, poOpenInfo)) { srcType = eGeoJSONSourceText; } @@ -938,6 +994,10 @@ GeoJSONSourceType JSONFGDriverGetSourceType(GDALOpenInfo *poOpenInfo) STARTS_WITH_CI(poOpenInfo->pszFilename, "https://") || STARTS_WITH_CI(poOpenInfo->pszFilename, "ftp://")) { + if (poOpenInfo->IsSingleAllowedDriver("JSONFG")) + { + return eGeoJSONSourceService; + } if (IsLikelyESRIJSONURL(poOpenInfo->pszFilename)) { return eGeoJSONSourceUnknown; @@ -953,11 +1013,11 @@ GeoJSONSourceType JSONFGDriverGetSourceType(GDALOpenInfo *poOpenInfo) return eGeoJSONSourceFile; } const char *pszText = poOpenInfo->pszFilename + nJSONFGPrefixLen; - if (JSONFGIsObject(pszText)) + if (JSONFGIsObject(pszText, poOpenInfo)) return eGeoJSONSourceText; return eGeoJSONSourceUnknown; } - else if (JSONFGIsObject(poOpenInfo->pszFilename)) + else if (JSONFGIsObject(poOpenInfo->pszFilename, poOpenInfo)) { srcType = eGeoJSONSourceText; } diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.h b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.h index 37940f161447..b2c9ebb98eeb 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.h +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonutils.h @@ -59,11 +59,11 @@ GeoJSONSourceType JSONFGDriverGetSourceType(GDALOpenInfo *poOpenInfo); /* GeoJSONIsObject */ /************************************************************************/ -bool GeoJSONIsObject(const char *pszText, CSLConstList papszAllowedDrivers); -bool GeoJSONSeqIsObject(const char *pszText); -bool ESRIJSONIsObject(const char *pszText); -bool TopoJSONIsObject(const char *pszText); -bool JSONFGIsObject(const char *pszText); +bool GeoJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo); +bool GeoJSONSeqIsObject(const char *pszText, GDALOpenInfo *poOpenInfo); +bool ESRIJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo); +bool TopoJSONIsObject(const char *pszText, GDALOpenInfo *poOpenInfo); +bool JSONFGIsObject(const char *pszText, GDALOpenInfo *poOpenInfo); /************************************************************************/ /* GeoJSONPropertyToFieldType */ diff --git a/ogr/ogrsf_frmts/geojson/ogrtopojsondriver.cpp b/ogr/ogrsf_frmts/geojson/ogrtopojsondriver.cpp index 8073dbc61a22..481eb2e22746 100644 --- a/ogr/ogrsf_frmts/geojson/ogrtopojsondriver.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrtopojsondriver.cpp @@ -48,10 +48,14 @@ static int OGRTopoJSONDriverIdentify(GDALOpenInfo *poOpenInfo) GeoJSONSourceType nSrcType = TopoJSONDriverGetSourceType(poOpenInfo); if (nSrcType == eGeoJSONSourceUnknown) return FALSE; - if (nSrcType == eGeoJSONSourceService && - !STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:")) + if (nSrcType == eGeoJSONSourceService) { - return -1; + if (poOpenInfo->IsSingleAllowedDriver("TopoJSON")) + return TRUE; + if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "TopoJSON:")) + { + return -1; + } } return TRUE; } diff --git a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp index d5017c07b081..2b4d8b65ebe2 100644 --- a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp +++ b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdataset.cpp @@ -435,7 +435,7 @@ bool OGRJSONFGDataset::ReadFromService(GDALOpenInfo *poOpenInfo, char *pszStoredContent = OGRGeoJSONDriverStealStoredContent(pszSource); if (pszStoredContent != nullptr) { - if (JSONFGIsObject(pszStoredContent)) + if (JSONFGIsObject(pszStoredContent, poOpenInfo)) { pszGeoData_ = pszStoredContent; nGeoDataLen_ = strlen(pszGeoData_); @@ -501,7 +501,7 @@ bool OGRJSONFGDataset::ReadFromService(GDALOpenInfo *poOpenInfo, /* -------------------------------------------------------------------- */ if (EQUAL(pszSource, poOpenInfo->pszFilename)) { - if (!JSONFGIsObject(pszGeoData_)) + if (!JSONFGIsObject(pszGeoData_, poOpenInfo)) { OGRGeoJSONDriverStoreContent(pszSource, pszGeoData_); pszGeoData_ = nullptr; diff --git a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdriver.cpp b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdriver.cpp index a00e47e6cccc..e33ff6ab50c2 100644 --- a/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdriver.cpp +++ b/ogr/ogrsf_frmts/jsonfg/ogrjsonfgdriver.cpp @@ -41,10 +41,14 @@ static int OGRJSONFGDriverIdentify(GDALOpenInfo *poOpenInfo) GeoJSONSourceType nSrcType = JSONFGDriverGetSourceType(poOpenInfo); if (nSrcType == eGeoJSONSourceUnknown) return FALSE; - if (nSrcType == eGeoJSONSourceService && - !STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:")) + if (nSrcType == eGeoJSONSourceService) { - return -1; + if (poOpenInfo->IsSingleAllowedDriver("JSONFG")) + return TRUE; + if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "JSONFG:")) + { + return -1; + } } return TRUE; } From 7be4e0153cd97de4d62cea2e4d3658e448b2c51a Mon Sep 17 00:00:00 2001 From: Andrew Bell Date: Mon, 10 Jun 2024 14:48:47 -0400 Subject: [PATCH 0110/1119] Viewshed: create current extent (no functional change) (#10184) Make a normalized window so that accessing start and stop is consistent with nX. --- alg/viewshed.cpp | 20 +++++++++++--------- alg/viewshed.h | 12 ++++++++++-- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/alg/viewshed.cpp b/alg/viewshed.cpp index 1c95701278f3..2d29f0d2f677 100644 --- a/alg/viewshed.cpp +++ b/alg/viewshed.cpp @@ -373,9 +373,9 @@ bool Viewshed::writeLine(int nLine, std::vector &vResult) /// @return True on success, false otherwise. bool Viewshed::lineProgress() { - if (nLineCount < oOutExtent.ySize()) + if (nLineCount < oCurExtent.ySize()) nLineCount++; - return emitProgress(nLineCount / static_cast(oOutExtent.ySize())); + return emitProgress(nLineCount / static_cast(oCurExtent.ySize())); } /// Emit progress information saying that a fraction of work has been completed. @@ -404,7 +404,7 @@ std::pair Viewshed::adjustHeight(int nYOffset, int nX, double *const pdfNx) { int nLeft = 0; - int nRight = oOutExtent.xSize(); + int nRight = oCurExtent.xSize(); // If there is a height adjustment factor other than zero or a max distance, // calculate the adjusted height of the cell, stopping if we've exceeded the max @@ -430,7 +430,7 @@ std::pair Viewshed::adjustHeight(int nYOffset, int nX, } pdfHeight = pdfNx + 1; - for (int nXOffset = 1; nXOffset < oOutExtent.xSize() - nX; + for (int nXOffset = 1; nXOffset < oCurExtent.xSize() - nX; nXOffset++, pdfHeight++) { double dfX = adfTransform[1] * nXOffset + dfLineX; @@ -447,7 +447,7 @@ std::pair Viewshed::adjustHeight(int nYOffset, int nX, else { double *pdfHeight = pdfNx - nX; - for (int i = 0; i < oOutExtent.xSize(); ++i) + for (int i = 0; i < oCurExtent.xSize(); ++i) { *pdfHeight -= dfZObserver; pdfHeight++; @@ -664,7 +664,7 @@ bool Viewshed::processFirstLine(int nX, int nY, int nLine, vResult[nX] = oOpts.visibleVal; if (nX - 1 >= 0) vResult[nX - 1] = oOpts.visibleVal; - if (nX + 1 < oOutExtent.xSize()) + if (nX + 1 < oCurExtent.xSize()) vResult[nX + 1] = oOpts.visibleVal; } @@ -816,6 +816,8 @@ bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress, return false; // normalize horizontal index to [ 0, oOutExtent.xSize() ) + oCurExtent = oOutExtent; + oCurExtent.shiftX(-oOutExtent.xStart); nX -= oOutExtent.xStart; // create the output dataset @@ -824,7 +826,7 @@ bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress, oZcalc = doLine; - std::vector vFirstLineVal(oOutExtent.xSize()); + std::vector vFirstLineVal(oCurExtent.xSize()); if (!processFirstLine(nX, nY, nY, vFirstLineVal)) return false; @@ -846,7 +848,7 @@ bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress, std::vector vLastLineVal = vFirstLineVal; for (int nLine = nY - 1; - nLine >= oOutExtent.yStart && !err; nLine--) + nLine >= oCurExtent.yStart && !err; nLine--) if (!processLine(nX, nY, nLine, vLastLineVal)) err = true; }); @@ -858,7 +860,7 @@ bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress, { std::vector vLastLineVal = vFirstLineVal; - for (int nLine = nY + 1; nLine < oOutExtent.yStop && !err; nLine++) + for (int nLine = nY + 1; nLine < oCurExtent.yStop && !err; nLine++) if (!processLine(nX, nY, nLine, vLastLineVal)) err = true; }); diff --git a/alg/viewshed.h b/alg/viewshed.h index 857ec6225096..d862adebf237 100644 --- a/alg/viewshed.h +++ b/alg/viewshed.h @@ -97,6 +97,13 @@ class Viewshed { return yStop - yStart; } + + /// \brief Shift the X dimension by nShift + void shiftX(int nShift) + { + xStart += nShift; + xStop += nShift; + } }; /** @@ -130,8 +137,8 @@ class Viewshed * @param opts Options to use when calculating viewshed. */ CPL_DLL explicit Viewshed(const Options &opts) - : oOpts{opts}, oOutExtent{}, dfMaxDistance2{opts.maxDistance * - opts.maxDistance}, + : oOpts{opts}, oOutExtent{}, oCurExtent{}, + dfMaxDistance2{opts.maxDistance * opts.maxDistance}, dfZObserver{0}, poDstDS{}, pSrcBand{}, pDstBand{}, dfHeightAdjFactor{0}, nLineCount{0}, adfTransform{0, 1, 0, 0, 0, 1}, adfInvTransform{}, oProgress{}, oZcalc{}, oMutex{}, iMutex{} @@ -159,6 +166,7 @@ class Viewshed private: Options oOpts; Window oOutExtent; + Window oCurExtent; double dfMaxDistance2; double dfZObserver; std::unique_ptr poDstDS; From e43ccca8b6b5b1d4231813eea3d782db02066a86 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 10 Jun 2024 23:03:40 +0200 Subject: [PATCH 0111/1119] swig/python/CMakeLists.txt: remove trace of code path only valid for CMake < 3.14 (we require 3.16+) --- swig/python/CMakeLists.txt | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/swig/python/CMakeLists.txt b/swig/python/CMakeLists.txt index 866cf0214578..c626b790931e 100644 --- a/swig/python/CMakeLists.txt +++ b/swig/python/CMakeLists.txt @@ -426,26 +426,20 @@ if (Python_Interpreter_FOUND) endif () if (ONLY_GENERATE_FOR_NON_DEBUG) - if (POLICY CMP0087) - # install(SCRIPT) can use generator expressions, since CMake 3.14 - cmake_policy(SET CMP0087 NEW) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/install_python.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/install_python.cmake.tmp - @ONLY) - file( - GENERATE - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/install_python_$.cmake - INPUT ${CMAKE_CURRENT_BINARY_DIR}/install_python.cmake.tmp - ) - file( - GENERATE - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/install_binding_$.cmake - CONTENT - "execute_process(COMMAND $>,${CMAKE_COMMAND} ${WERROR_DEV_FLAG} -P \"${CMAKE_CURRENT_BINARY_DIR}/install_python_$.cmake\",${CMAKE_COMMAND} -E echo \"setup.py install only run in configuration != Debug\"> - WORKING_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}\")") - install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/install_binding_$.cmake) - else () - message(WARNING "Installing Python bindings with a multi generator requires CMake >= 3.14") - endif () + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/install_python.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/install_python.cmake.tmp + @ONLY) + file( + GENERATE + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/install_python_$.cmake + INPUT ${CMAKE_CURRENT_BINARY_DIR}/install_python.cmake.tmp + ) + file( + GENERATE + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/install_binding_$.cmake + CONTENT + "execute_process(COMMAND $>,${CMAKE_COMMAND} ${WERROR_DEV_FLAG} -P \"${CMAKE_CURRENT_BINARY_DIR}/install_python_$.cmake\",${CMAKE_COMMAND} -E echo \"setup.py install only run in configuration != Debug\"> + WORKING_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}\")") + install(SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/install_binding_$.cmake) else () configure_file("${CMAKE_CURRENT_SOURCE_DIR}/install_python.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/install_python.cmake" @ONLY) From e295a0e9714ab6f52c8fec6fc115a6d216c5b150 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 00:20:33 +0200 Subject: [PATCH 0112/1119] STACTA: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/gdrivers/stacta.py | 44 ++++++++++++++++++++++++++++ doc/source/drivers/raster/stacta.rst | 5 ++++ frmts/stacta/stactadataset.cpp | 18 +++++++++++- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/autotest/gdrivers/stacta.py b/autotest/gdrivers/stacta.py index f0452ffb31ed..564cfc5fbd5d 100755 --- a/autotest/gdrivers/stacta.py +++ b/autotest/gdrivers/stacta.py @@ -398,3 +398,47 @@ def test_stacta_with_raster_extension_errors(): with gdaltest.tempfile("/vsimem/test.json", json.dumps(j)): with gdal.quiet_errors(): assert gdal.Open("/vsimem/test.json") is not None + + +############################################################################### +# Test force opening a STACTA file + + +def test_stacta_force_opening(tmp_vsimem): + + filename = str(tmp_vsimem / "test.foo") + + with open("data/stacta/test.json", "rb") as fsrc: + with gdaltest.vsi_open(filename, "wb") as fdest: + fdest.write(fsrc.read(1)) + fdest.write(b" " * (1000 * 1000)) + fdest.write(fsrc.read()) + + with pytest.raises(Exception): + gdal.OpenEx(filename) + + with gdaltest.vsi_open(tmp_vsimem / "WorldCRS84Quad/0/0/0.tif", "wb") as fdest: + fdest.write(open("data/stacta/WorldCRS84Quad/0/0/0.tif", "rb").read()) + + ds = gdal.OpenEx(filename, allowed_drivers=["STACTA"]) + assert ds.GetDriver().GetDescription() == "STACTA" + + +############################################################################### +# Test force opening a URL as STACTA + + +def test_stacta_force_opening_url(): + + drv = gdal.IdentifyDriverEx("http://example.com", allowed_drivers=["STACTA"]) + assert drv.GetDescription() == "STACTA" + + +############################################################################### +# Test force opening, but provided file is still not recognized (for good reasons) + + +def test_stacta_force_opening_no_match(): + + drv = gdal.IdentifyDriverEx("data/byte.tif", allowed_drivers=["STACTA"]) + assert drv is None diff --git a/doc/source/drivers/raster/stacta.rst b/doc/source/drivers/raster/stacta.rst index 538d109b3755..14f466eb8131 100644 --- a/doc/source/drivers/raster/stacta.rst +++ b/doc/source/drivers/raster/stacta.rst @@ -47,6 +47,11 @@ STACTA datasets/subdatasets can be accessed with one of the following syntaxes: The root of the JSON file must be of type ``Feature``. +Starting with GDAL 3.10, specifying the ``-if STACTA`` option to command line utilities +accepting it, or ``STACTA`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +filename or URL. + Open options ------------ diff --git a/frmts/stacta/stactadataset.cpp b/frmts/stacta/stactadataset.cpp index 0383e1a785a4..5e9982d0c677 100644 --- a/frmts/stacta/stactadataset.cpp +++ b/frmts/stacta/stactadataset.cpp @@ -650,9 +650,17 @@ int STACTADataset::Identify(GDALOpenInfo *poOpenInfo) return true; } + const bool bIsSingleDriver = poOpenInfo->IsSingleAllowedDriver("STACTA"); + if (bIsSingleDriver && (STARTS_WITH(poOpenInfo->pszFilename, "http://") || + STARTS_WITH(poOpenInfo->pszFilename, "https://"))) + { + return true; + } + if ( #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - !EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "json") || + (!bIsSingleDriver && + !EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "json")) || #endif poOpenInfo->nHeaderBytes == 0) { @@ -665,6 +673,14 @@ int STACTADataset::Identify(GDALOpenInfo *poOpenInfo) // before the loop. const char *pszHeader = reinterpret_cast(poOpenInfo->pabyHeader); + while (*pszHeader != 0 && + std::isspace(static_cast(*pszHeader))) + ++pszHeader; + if (bIsSingleDriver) + { + return pszHeader[0] == '{'; + } + if (strstr(pszHeader, "\"stac_extensions\"") != nullptr && (strstr(pszHeader, "\"tiled-assets\"") != nullptr || strstr(pszHeader, From d502c29537bb463bc505515c6ec83157208d0d52 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 00:30:02 +0200 Subject: [PATCH 0113/1119] STACIT: correctly return the STACIT driver as the dataset's driver, instead of the VRT one (bugfix) --- autotest/gdrivers/stacit.py | 1 + frmts/stacit/stacitdataset.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/autotest/gdrivers/stacit.py b/autotest/gdrivers/stacit.py index 5b7732b3b156..39c811de24d5 100755 --- a/autotest/gdrivers/stacit.py +++ b/autotest/gdrivers/stacit.py @@ -42,6 +42,7 @@ def test_stacit_basic(): ds = gdal.Open("data/stacit/test.json") assert ds is not None + assert ds.GetDriver().GetDescription() == "STACIT" assert ds.RasterCount == 1 assert ds.RasterXSize == 40 assert ds.RasterYSize == 20 diff --git a/frmts/stacit/stacitdataset.cpp b/frmts/stacit/stacitdataset.cpp index a8bc5abf358a..c63a2fded187 100644 --- a/frmts/stacit/stacitdataset.cpp +++ b/frmts/stacit/stacitdataset.cpp @@ -98,6 +98,7 @@ class STACITDataset final : public VRTDataset STACITDataset::STACITDataset() : VRTDataset(0, 0) { + poDriver = nullptr; // cancel what the VRTDataset did SetWritable(false); } From c61d95838f9dbc43fb6bfc010a1f68fb1bbd8c4e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 00:31:13 +0200 Subject: [PATCH 0114/1119] STACIT: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/gdrivers/stacit.py | 42 ++++++++++++++++++++++++++++ doc/source/drivers/raster/stacit.rst | 5 ++++ frmts/stacit/stacitdataset.cpp | 15 ++++++++++ 3 files changed, 62 insertions(+) diff --git a/autotest/gdrivers/stacit.py b/autotest/gdrivers/stacit.py index 39c811de24d5..8e25ce10ec8f 100755 --- a/autotest/gdrivers/stacit.py +++ b/autotest/gdrivers/stacit.py @@ -30,6 +30,7 @@ import json +import gdaltest import pytest import webserver @@ -319,3 +320,44 @@ def test_stacit_post_paging(tmp_vsimem, webserver_port): assert ds.GetGeoTransform() == pytest.approx( [440720.0, 60.0, 0.0, 3751320.0, 0.0, -60.0], rel=1e-8 ) + + +############################################################################### +# Test force opening a STACIT file + + +def test_stacit_force_opening(tmp_vsimem): + + filename = str(tmp_vsimem / "test.foo") + + with open("data/stacit/test.json", "rb") as fsrc: + with gdaltest.vsi_open(filename, "wb") as fdest: + fdest.write(fsrc.read(1)) + fdest.write(b" " * (1000 * 1000)) + fdest.write(fsrc.read()) + + with pytest.raises(Exception): + gdal.OpenEx(filename) + + ds = gdal.OpenEx(filename, allowed_drivers=["STACIT"]) + assert ds.GetDriver().GetDescription() == "STACIT" + + +############################################################################### +# Test force opening a URL as STACIT + + +def test_stacit_force_opening_url(): + + drv = gdal.IdentifyDriverEx("http://example.com", allowed_drivers=["STACIT"]) + assert drv.GetDescription() == "STACIT" + + +############################################################################### +# Test force opening, but provided file is still not recognized (for good reasons) + + +def test_stacit_force_opening_no_match(): + + drv = gdal.IdentifyDriverEx("data/byte.tif", allowed_drivers=["STACIT"]) + assert drv is None diff --git a/doc/source/drivers/raster/stacit.rst b/doc/source/drivers/raster/stacit.rst index 0c1e0d012e76..86ebb77f7f91 100644 --- a/doc/source/drivers/raster/stacit.rst +++ b/doc/source/drivers/raster/stacit.rst @@ -33,6 +33,11 @@ STACIT datasets/subdatasets can be accessed with one of the following syntaxes: * ``STACIT:"filename.json":collection=my_collect,asset=my_asset,crs=my_crs``: specify a collection, asset, and limit to items in a given CRS +Starting with GDAL 3.10, specifying the ``-if STACIT`` option to command line utilities +accepting it, or ``STACIT`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +filename or URL. + Open options ------------ diff --git a/frmts/stacit/stacitdataset.cpp b/frmts/stacit/stacitdataset.cpp index c63a2fded187..3522b70108cf 100644 --- a/frmts/stacit/stacitdataset.cpp +++ b/frmts/stacit/stacitdataset.cpp @@ -113,6 +113,13 @@ int STACITDataset::Identify(GDALOpenInfo *poOpenInfo) return true; } + const bool bIsSingleDriver = poOpenInfo->IsSingleAllowedDriver("STACIT"); + if (bIsSingleDriver && (STARTS_WITH(poOpenInfo->pszFilename, "http://") || + STARTS_WITH(poOpenInfo->pszFilename, "https://"))) + { + return true; + } + if (poOpenInfo->nHeaderBytes == 0) { return false; @@ -124,6 +131,14 @@ int STACITDataset::Identify(GDALOpenInfo *poOpenInfo) // before the loop. const char *pszHeader = reinterpret_cast(poOpenInfo->pabyHeader); + while (*pszHeader != 0 && + std::isspace(static_cast(*pszHeader))) + ++pszHeader; + if (bIsSingleDriver) + { + return pszHeader[0] == '{'; + } + if (strstr(pszHeader, "\"stac_version\"") != nullptr && strstr(pszHeader, "\"proj:transform\"") != nullptr) { From 059368a79e782a6dfa56f277eb8b0ef6060d164a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 00:52:55 +0200 Subject: [PATCH 0115/1119] netCDFIdentifyFormat(): remove reference to no longer existing GMT raster driver --- frmts/netcdf/netcdfdrivercore.cpp | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/frmts/netcdf/netcdfdrivercore.cpp b/frmts/netcdf/netcdfdrivercore.cpp index bb339c6ad85b..833889eb2e82 100644 --- a/frmts/netcdf/netcdfdrivercore.cpp +++ b/frmts/netcdf/netcdfdrivercore.cpp @@ -87,33 +87,6 @@ NetCDFFormatEnum netCDFIdentifyFormat(GDALOpenInfo *poOpenInfo, bool bCheckExt) if (STARTS_WITH_CI(pszHeader, "CDF\001")) { - // In case the netCDF driver is registered before the GMT driver, - // avoid opening GMT files. - if (GDALGetDriverByName("GMT") != nullptr) - { - bool bFoundZ = false; - bool bFoundDimension = false; - constexpr const char *DIMENSION = "dimension"; - constexpr int DIMENSION_LEN = - int(std::char_traits::length(DIMENSION)); - static_assert(DIMENSION_LEN == 9); - const std::string_view header(pszHeader, poOpenInfo->nHeaderBytes); - for (int i = 0; - i < static_cast(header.size()) - (1 + DIMENSION_LEN + 1); - i++) - { - if (header[i] == 1 && header[i + 1] == 'z' && - header[i + 2] == 0) - bFoundZ = true; - else if (header[i] == DIMENSION_LEN && - header.substr(i + 1, DIMENSION_LEN) == DIMENSION && - header[i + DIMENSION_LEN + 1] == 0) - bFoundDimension = true; - } - if (bFoundZ && bFoundDimension) - return NCDF_FORMAT_UNKNOWN; - } - return NCDF_FORMAT_NC; } From 31b3580efa4a399fe2639e190389e3370d052d85 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 01:19:11 +0200 Subject: [PATCH 0116/1119] netCDF: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/gdrivers/netcdf.py | 23 +++++++++++++++++++++ doc/source/drivers/raster/netcdf.rst | 6 ++++++ frmts/netcdf/netcdfdataset.cpp | 15 ++++++++++++-- frmts/netcdf/netcdfdrivercore.cpp | 31 +++++++++++++++++++++++----- 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/autotest/gdrivers/netcdf.py b/autotest/gdrivers/netcdf.py index ff21d67235b5..a30c8eaf40df 100755 --- a/autotest/gdrivers/netcdf.py +++ b/autotest/gdrivers/netcdf.py @@ -6519,3 +6519,26 @@ def test_netcdf_create_metadata_with_equal_sign(tmp_path): ds = gdal.Open(fname) assert ds.GetRasterBand(1).GetMetadataItem("long_name") == value + + +############################################################################### +# Test force opening a HDF55 file with netCDF driver + + +def test_netcdf_force_opening_hdf5_file(tmp_vsimem): + + ds = gdal.OpenEx("data/hdf5/groups.h5", allowed_drivers=["netCDF"]) + assert ds.GetDriver().GetDescription() == "netCDF" + + ds = gdal.Open(ds.GetSubDatasets()[0][0]) + assert ds.GetDriver().GetDescription() == "netCDF" + + +############################################################################### +# Test force opening, but provided file is still not recognized (for good reasons) + + +def test_netcdf_force_opening_no_match(): + + drv = gdal.IdentifyDriverEx("data/byte.tif", allowed_drivers=["netCDF"]) + assert drv is None diff --git a/doc/source/drivers/raster/netcdf.rst b/doc/source/drivers/raster/netcdf.rst index 5e1561260538..df960742a559 100644 --- a/doc/source/drivers/raster/netcdf.rst +++ b/doc/source/drivers/raster/netcdf.rst @@ -222,6 +222,12 @@ should be reported as multiple bands of a same dataset. scale_factor=0.1 +Starting with GDAL 3.10, specifying the ``-if netCDF`` option to command line utilities +accepting it, or ``netCDF`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +filename, when it is not using subdataset syntax (it can typically be used to +force open a HDF5 file that would be nominally recognized by the HDF5 driver). + Dimension --------- diff --git a/frmts/netcdf/netcdfdataset.cpp b/frmts/netcdf/netcdfdataset.cpp index 6ee312b5213f..395186bf9af6 100644 --- a/frmts/netcdf/netcdfdataset.cpp +++ b/frmts/netcdf/netcdfdataset.cpp @@ -8008,9 +8008,20 @@ GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo) #endif // Note: not calling Identify() directly, because we want the file type. // Only support NCDF_FORMAT* formats. - if (!(NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat || - NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat)) + if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat || + NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat) + { + // ok + } + else if (eTmpFormat == NCDF_FORMAT_HDF4 && + poOpenInfo->IsSingleAllowedDriver("netCDF")) + { + // ok + } + else + { return nullptr; + } } else { diff --git a/frmts/netcdf/netcdfdrivercore.cpp b/frmts/netcdf/netcdfdrivercore.cpp index 833889eb2e82..84c273922364 100644 --- a/frmts/netcdf/netcdfdrivercore.cpp +++ b/frmts/netcdf/netcdfdrivercore.cpp @@ -104,9 +104,15 @@ NetCDFFormatEnum netCDFIdentifyFormat(GDALOpenInfo *poOpenInfo, bool bCheckExt) (poOpenInfo->nHeaderBytes > HDF5_SIG_OFFSET + HDF5_SIG_LEN && memcmp(pszHeader + HDF5_SIG_OFFSET, HDF5_SIG, HDF5_SIG_LEN) == 0)) { + // If only the netCDF driver is allowed, immediately recognize the file + if (poOpenInfo->IsSingleAllowedDriver("netCDF")) + { + return NCDF_FORMAT_NC4; + } + // Requires netCDF-4/HDF5 support in libnetcdf (not just libnetcdf-v4). // If HDF5 is not supported in GDAL, this driver will try to open the - // file Else, make sure this driver does not try to open HDF5 files If + // file. Else, make sure this driver does not try to open HDF5 files. If // user really wants to open with this driver, use NETCDF:file.h5 // format. This check should be relaxed, but there is no clear way to // make a difference. @@ -134,7 +140,16 @@ NetCDFFormatEnum netCDFIdentifyFormat(GDALOpenInfo *poOpenInfo, bool bCheckExt) } else if (STARTS_WITH_CI(pszHeader, "\016\003\023\001")) { - // Requires HDF4 support in libnetcdf, but if HF4 is supported by GDAL + // Check for HDF4 support in libnetcdf. +#ifdef NETCDF_HAS_HDF4 + // If only the netCDF driver is allowed, immediately recognize the file + if (poOpenInfo->IsSingleAllowedDriver("netCDF")) + { + return NCDF_FORMAT_HDF4; + } +#endif + + // Requires HDF4 support in libnetcdf, but if HDF4 is supported by GDAL // don't try to open. // If user really wants to open with this driver, use NETCDF:file.hdf // syntax. @@ -151,9 +166,9 @@ NetCDFFormatEnum netCDFIdentifyFormat(GDALOpenInfo *poOpenInfo, bool bCheckExt) // Check for HDF4 support in libnetcdf. #ifdef NETCDF_HAS_HDF4 - return NCDF_FORMAT_NC4; -#else return NCDF_FORMAT_HDF4; +#else + return NCDF_FORMAT_NONE; #endif } @@ -162,7 +177,8 @@ NetCDFFormatEnum netCDFIdentifyFormat(GDALOpenInfo *poOpenInfo, bool bCheckExt) const char *pszExtension = CPLGetExtension(poOpenInfo->pszFilename); if (poOpenInfo->fpL != nullptr && (!bCheckExt || EQUAL(pszExtension, "nc") || - EQUAL(pszExtension, "cdf") || EQUAL(pszExtension, "nc4"))) + EQUAL(pszExtension, "cdf") || EQUAL(pszExtension, "nc4") || + poOpenInfo->IsSingleAllowedDriver("netCDF"))) { vsi_l_offset nOffset = HDF5_SIG_OFFSET; for (int i = 0; i < 64; i++) @@ -202,6 +218,11 @@ static int netCDFDatasetIdentify(GDALOpenInfo *poOpenInfo) if (NCDF_FORMAT_NC == eTmpFormat || NCDF_FORMAT_NC2 == eTmpFormat || NCDF_FORMAT_NC4 == eTmpFormat || NCDF_FORMAT_NC4C == eTmpFormat) return TRUE; + if (eTmpFormat == NCDF_FORMAT_HDF4 && + poOpenInfo->IsSingleAllowedDriver("netCDF")) + { + return TRUE; + } return FALSE; } From ebd21a19f0db3ada9b9089564093281b81e89c5f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 23:15:01 +0200 Subject: [PATCH 0117/1119] HDF5: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/gdrivers/hdf5.py | 25 +++++++++++++++++++++++++ doc/source/drivers/raster/hdf5.rst | 6 ++++++ frmts/hdf5/hdf5drivercore.cpp | 29 +++++++++++++++++------------ 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/autotest/gdrivers/hdf5.py b/autotest/gdrivers/hdf5.py index 25fe8d22c970..b10dbf3d5609 100755 --- a/autotest/gdrivers/hdf5.py +++ b/autotest/gdrivers/hdf5.py @@ -1602,3 +1602,28 @@ def test_hdf5_read_netcdf_nodata_scale_offset(): assert band.GetNoDataValue() == pytest.approx(9.96921e36, rel=1e-7) assert band.GetOffset() == 1.5 assert band.GetScale() == 0.01 + + +############################################################################### +# Test force opening a netCDF file with HDF5 driver + + +def test_hdf5_force_opening_netcdf_file(): + + ds = gdal.OpenEx("data/netcdf/trmm-nc4.nc", allowed_drivers=["HDF5"]) + assert ds.GetDriver().GetDescription() == "HDF5Image" + + ds = gdal.OpenEx( + "data/netcdf/byte_hdf5_starting_at_offset_1024.nc", allowed_drivers=["HDF5"] + ) + assert ds.GetDriver().GetDescription() == "HDF5Image" + + +############################################################################### +# Test force opening, but provided file is still not recognized (for good reasons) + + +def test_hdf5_force_opening_no_match(): + + drv = gdal.IdentifyDriverEx("data/byte.tif", allowed_drivers=["HDF5"]) + assert drv is None diff --git a/doc/source/drivers/raster/hdf5.rst b/doc/source/drivers/raster/hdf5.rst index 2f4f6bf3e3a0..4a3764080b02 100644 --- a/doc/source/drivers/raster/hdf5.rst +++ b/doc/source/drivers/raster/hdf5.rst @@ -49,6 +49,12 @@ like this: | *subdataset* is the dataset name of the array to use (for internal use in GDAL). +Starting with GDAL 3.10, specifying the ``-if HDF5`` option to command line utilities +accepting it, or ``HDF5`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +filename, when it is not using subdataset syntax (it can typically be used to +force open a netCDF file that would be nominally recognized by the netCDF driver). + On the second step you should provide this name for **gdalinfo** or **gdal_translate** for actual reading of the data. diff --git a/frmts/hdf5/hdf5drivercore.cpp b/frmts/hdf5/hdf5drivercore.cpp index fc5086281e51..a858b1b1f1ab 100644 --- a/frmts/hdf5/hdf5drivercore.cpp +++ b/frmts/hdf5/hdf5drivercore.cpp @@ -59,17 +59,12 @@ int HDF5DatasetIdentify(GDALOpenInfo *poOpenInfo) GDALGetDriverByName("netCDF") != nullptr) { const char *const apszAllowedDriver[] = {"netCDF", nullptr}; - CPLPushErrorHandler(CPLQuietErrorHandler); - GDALDatasetH hDS = GDALOpenEx( - poOpenInfo->pszFilename, - GDAL_OF_RASTER | GDAL_OF_MULTIDIM_RASTER | GDAL_OF_VECTOR, - apszAllowedDriver, nullptr, nullptr); - CPLPopErrorHandler(); - if (hDS) - { - GDALClose(hDS); - return true; - } + CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); + return std::unique_ptr(GDALDataset::Open( + poOpenInfo->pszFilename, + GDAL_OF_RASTER | GDAL_OF_MULTIDIM_RASTER | + GDAL_OF_VECTOR, + apszAllowedDriver, nullptr, nullptr)) != nullptr; } return false; }; @@ -78,6 +73,11 @@ int HDF5DatasetIdentify(GDALOpenInfo *poOpenInfo) (poOpenInfo->nHeaderBytes > 512 + 8 && memcmp(poOpenInfo->pabyHeader + 512, achSignature, 8) == 0)) { + if (poOpenInfo->IsSingleAllowedDriver("HDF5")) + { + return TRUE; + } + // The tests to avoid opening KEA and BAG drivers are not // necessary when drivers are built in the core lib, as they // are registered after HDF5, but in the case of plugins, we @@ -113,7 +113,8 @@ int HDF5DatasetIdentify(GDALOpenInfo *poOpenInfo) // The HDF5 signature can be at offsets 512, 1024, 2048, etc. if (poOpenInfo->fpL != nullptr && (EQUAL(osExt, "h5") || EQUAL(osExt, "hdf5") || EQUAL(osExt, "nc") || - EQUAL(osExt, "cdf") || EQUAL(osExt, "nc4"))) + EQUAL(osExt, "cdf") || EQUAL(osExt, "nc4") || + poOpenInfo->IsSingleAllowedDriver("HDF5"))) { vsi_l_offset nOffset = 512; for (int i = 0; i < 64; i++) @@ -126,6 +127,10 @@ int HDF5DatasetIdentify(GDALOpenInfo *poOpenInfo) } if (memcmp(abyBuf, achSignature, 8) == 0) { + if (poOpenInfo->IsSingleAllowedDriver("HDF5")) + { + return TRUE; + } // Avoid opening NC files if the netCDF driver is available and // they are recognized by it. if (IsRecognizedByNetCDFDriver()) From f088e66baa54b04c1744627526e592ee048ae44d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 23:18:33 +0200 Subject: [PATCH 0118/1119] BAG: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/gdrivers/bag.py | 20 ++++++++++++++++++++ frmts/hdf5/hdf5drivercore.cpp | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/autotest/gdrivers/bag.py b/autotest/gdrivers/bag.py index a1580be1a2c7..b08119b6ca85 100755 --- a/autotest/gdrivers/bag.py +++ b/autotest/gdrivers/bag.py @@ -1193,3 +1193,23 @@ def test_bag_write_values_at_nodata(): ds = None gdal.Unlink(tmpfilename) + + +############################################################################### +# Test force opening + + +def test_bag_force_opening(): + + drv = gdal.IdentifyDriverEx("data/netcdf/trmm-nc4.nc", allowed_drivers=["BAG"]) + assert drv.GetDescription() == "BAG" + + +############################################################################### +# Test force opening, but provided file is still not recognized (for good reasons) + + +def test_bag_force_opening_no_match(): + + drv = gdal.IdentifyDriverEx("data/byte.tif", allowed_drivers=["BAG"]) + assert drv is None diff --git a/frmts/hdf5/hdf5drivercore.cpp b/frmts/hdf5/hdf5drivercore.cpp index a858b1b1f1ab..271ecbb3ba2f 100644 --- a/frmts/hdf5/hdf5drivercore.cpp +++ b/frmts/hdf5/hdf5drivercore.cpp @@ -359,7 +359,13 @@ int BAGDatasetIdentify(GDALOpenInfo *poOpenInfo) // Does it have the extension .bag? if (!EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "bag")) + { + if (poOpenInfo->IsSingleAllowedDriver("BAG")) + { + return TRUE; + } return FALSE; + } return TRUE; } From c2c67de19f584174cbd2618a1c940e186d6c8b3a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 4 Jun 2024 23:22:54 +0200 Subject: [PATCH 0119/1119] S102/S104/S111: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/gdrivers/s102.py | 20 ++++++++++++++++++++ frmts/hdf5/hdf5drivercore.cpp | 29 ++++++++++++++++------------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/autotest/gdrivers/s102.py b/autotest/gdrivers/s102.py index c704d230f68c..9d7a37ec92ca 100755 --- a/autotest/gdrivers/s102.py +++ b/autotest/gdrivers/s102.py @@ -310,3 +310,23 @@ def test_s102_QualityOfSurvey_multidim(): x_data = struct.unpack("d" * x.GetDimensions()[0].GetSize(), x.Read()) assert x_data[0] == 2.0 assert x_data[-1] == 2.8 + + +############################################################################### +# Test force opening + + +def test_s102_force_opening(): + + drv = gdal.IdentifyDriverEx("data/hdf5/groups.h5", allowed_drivers=["S102"]) + assert drv.GetDescription() == "S102" + + +############################################################################### +# Test force opening, but provided file is still not recognized (for good reasons) + + +def test_s102_force_opening_no_match(): + + drv = gdal.IdentifyDriverEx("data/byte.tif", allowed_drivers=["S102"]) + assert drv is None diff --git a/frmts/hdf5/hdf5drivercore.cpp b/frmts/hdf5/hdf5drivercore.cpp index 271ecbb3ba2f..7eec7b3ac904 100644 --- a/frmts/hdf5/hdf5drivercore.cpp +++ b/frmts/hdf5/hdf5drivercore.cpp @@ -252,9 +252,14 @@ static GDALSubdatasetInfo *HDF5DriverGetSubdatasetInfo(const char *pszFileName) /* IdentifySxx() */ /************************************************************************/ -static bool IdentifySxx(GDALOpenInfo *poOpenInfo, const char *pszConfigOption, +static bool IdentifySxx(GDALOpenInfo *poOpenInfo, const char *pszDriverName, + const char *pszConfigOption, const char *pszMainGroupName) { + if (STARTS_WITH(poOpenInfo->pszFilename, pszDriverName) && + poOpenInfo->pszFilename[strlen(pszDriverName)] == ':') + return TRUE; + // Is it an HDF5 file? static const char achSignature[] = "\211HDF\r\n\032\n"; @@ -262,6 +267,11 @@ static bool IdentifySxx(GDALOpenInfo *poOpenInfo, const char *pszConfigOption, memcmp(poOpenInfo->pabyHeader, achSignature, 8) != 0) return FALSE; + if (poOpenInfo->IsSingleAllowedDriver(pszDriverName)) + { + return TRUE; + } + // GDAL_Sxxx_IDENTIFY can be set to NO only for tests, to test that // HDF5Dataset::Open() can redirect to Sxxx if the below logic fails if (CPLTestBool(CPLGetConfigOption(pszConfigOption, "YES"))) @@ -308,10 +318,8 @@ static bool IdentifySxx(GDALOpenInfo *poOpenInfo, const char *pszConfigOption, int S102DatasetIdentify(GDALOpenInfo *poOpenInfo) { - if (STARTS_WITH(poOpenInfo->pszFilename, "S102:")) - return TRUE; - - return IdentifySxx(poOpenInfo, "GDAL_S102_IDENTIFY", "BathymetryCoverage"); + return IdentifySxx(poOpenInfo, "S102", "GDAL_S102_IDENTIFY", + "BathymetryCoverage"); } /************************************************************************/ @@ -321,10 +329,7 @@ int S102DatasetIdentify(GDALOpenInfo *poOpenInfo) int S104DatasetIdentify(GDALOpenInfo *poOpenInfo) { - if (STARTS_WITH(poOpenInfo->pszFilename, "S104:")) - return TRUE; - - return IdentifySxx(poOpenInfo, "GDAL_S104_IDENTIFY", "WaterLevel"); + return IdentifySxx(poOpenInfo, "S104", "GDAL_S104_IDENTIFY", "WaterLevel"); } /************************************************************************/ @@ -334,10 +339,8 @@ int S104DatasetIdentify(GDALOpenInfo *poOpenInfo) int S111DatasetIdentify(GDALOpenInfo *poOpenInfo) { - if (STARTS_WITH(poOpenInfo->pszFilename, "S111:")) - return TRUE; - - return IdentifySxx(poOpenInfo, "GDAL_S111_IDENTIFY", "SurfaceCurrent"); + return IdentifySxx(poOpenInfo, "S111", "GDAL_S111_IDENTIFY", + "SurfaceCurrent"); } /************************************************************************/ From cce69e53b5fcf98b076a5a5f0202070a4ca94569 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 00:40:15 +0200 Subject: [PATCH 0120/1119] WMTS: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/gdrivers/wmts.py | 80 ++++++++++++++++++++++++++++++ doc/source/drivers/raster/wmts.rst | 6 +++ frmts/wmts/wmtsdataset.cpp | 65 ++++++++++++------------ frmts/wmts/wmtsdrivercore.cpp | 31 +++++++++--- 4 files changed, 143 insertions(+), 39 deletions(-) diff --git a/autotest/gdrivers/wmts.py b/autotest/gdrivers/wmts.py index 532ccf027bc2..e941167ad9ee 100755 --- a/autotest/gdrivers/wmts.py +++ b/autotest/gdrivers/wmts.py @@ -34,6 +34,7 @@ import gdaltest import pytest +import webserver from osgeo import gdal @@ -1925,3 +1926,82 @@ def test_wmts_24(): data = struct.unpack("h", structval) # Expect a null value for the pixel data assert data[0] == 0 + + +############################################################################### +# Test force opening a URL as WMTS + + +def test_wmts_force_identifying_url(): + + drv = gdal.IdentifyDriverEx("http://example.com", allowed_drivers=["WMTS"]) + assert drv.GetDescription() == "WMTS" + + +# Launch a single webserver in a module-scoped fixture. +@pytest.fixture(scope="module") +def webserver_launch(): + + process, port = webserver.launch(handler=webserver.DispatcherHttpHandler) + + yield process, port + + webserver.server_stop(process, port) + + +@pytest.fixture(scope="function") +def webserver_port(webserver_launch): + + webserver_process, webserver_port = webserver_launch + + if webserver_port == 0: + pytest.skip() + yield webserver_port + + +@pytest.mark.require_curl +@gdaltest.enable_exceptions() +def test_wmts_force_opening_url(tmp_vsimem, webserver_port): + + handler = webserver.SequentialHandler() + handler.add( + "GET", + "/", + 200, + {"Content-type": "application/xml"}, + open("data/wmts/WMTSCapabilities.xml", "rb").read(), + ) + with webserver.install_http_handler(handler): + gdal.OpenEx(f"http://localhost:{webserver_port}", allowed_drivers=["WMTS"]) + + +############################################################################### +# Test force opening + + +@gdaltest.enable_exceptions() +def test_wmts_force_opening(tmp_vsimem): + + filename = str(tmp_vsimem / "test.foo") + + with open("data/wmts/WMTSCapabilities.xml", "rb") as fsrc: + with gdaltest.vsi_open(filename, "wb") as fdest: + fdest.write(fsrc.read(1)) + fdest.write(b" " * (1000 * 1000)) + fdest.write(fsrc.read()) + + with pytest.raises(Exception): + gdal.OpenEx(filename) + + ds = gdal.OpenEx(filename, allowed_drivers=["WMTS"]) + assert ds.GetDriver().GetDescription() == "WMTS" + + +############################################################################### +# Test force opening, but provided file is still not recognized (for good reasons) + + +def test_wmts_force_opening_no_match(): + + drv = gdal.IdentifyDriverEx("data/byte.tif", allowed_drivers=["WMTS"]) + assert drv is None diff --git a/doc/source/drivers/raster/wmts.rst b/doc/source/drivers/raster/wmts.rst index 5159d9d618a8..3e2b1c491283 100644 --- a/doc/source/drivers/raster/wmts.rst +++ b/doc/source/drivers/raster/wmts.rst @@ -77,6 +77,12 @@ layer has more than one style or a tile matrix set, a list of subdatasets will be returned. If there is only one layer, it will be opened on the default style and the first tile matrix set listed. +Starting with GDAL 3.10, specifying the ``-if WMTS`` option to command line utilities +accepting it, or ``WMTS`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +filename/URL, when it is not using subdataset syntax (it can typically be used to +force open a HDF5 file that would be nominally recognized by the HDF5 driver). + Open options ------------ diff --git a/frmts/wmts/wmtsdataset.cpp b/frmts/wmts/wmtsdataset.cpp index da70c514e316..01f8aad9cc5a 100644 --- a/frmts/wmts/wmtsdataset.cpp +++ b/frmts/wmts/wmtsdataset.cpp @@ -133,7 +133,7 @@ class WMTSDataset final : public GDALPamDataset CPLString osURLFeatureInfoTemplate; WMTSTileMatrixSet oTMS; - char **papszHTTPOptions; + CPLStringList m_aosHTTPOptions{}; std::vector apoDatasets; OGRSpatialReference m_oSRS{}; @@ -142,9 +142,9 @@ class WMTSDataset final : public GDALPamDataset CPLString osLastGetFeatureInfoURL; CPLString osMetadataItemGetFeatureInfo; - static char **BuildHTTPRequestOpts(CPLString osOtherXML); + static CPLStringList BuildHTTPRequestOpts(CPLString osOtherXML); static CPLXMLNode *GetCapabilitiesResponse(const CPLString &osFilename, - char **papszHTTPOptions); + CSLConstList papszHTTPOptions); static CPLString FixCRSName(const char *pszCRS); static CPLString Replace(const CPLString &osStr, const char *pszOld, const char *pszNew); @@ -371,7 +371,7 @@ const char *WMTSBand::GetMetadataItem(const char *pszName, poGDS->osMetadataItemGetFeatureInfo = ""; char *pszRes = nullptr; CPLHTTPResult *psResult = - CPLHTTPFetch(osURL, poGDS->papszHTTPOptions); + CPLHTTPFetch(osURL, poGDS->m_aosHTTPOptions.List()); if (psResult && psResult->nStatus == 0 && psResult->pabyData) pszRes = CPLStrdup((const char *)psResult->pabyData); CPLHTTPDestroyResult(psResult); @@ -422,7 +422,7 @@ const char *WMTSBand::GetMetadataItem(const char *pszName, /* WMTSDataset() */ /************************************************************************/ -WMTSDataset::WMTSDataset() : papszHTTPOptions(nullptr) +WMTSDataset::WMTSDataset() { m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); adfGT[0] = 0; @@ -440,7 +440,6 @@ WMTSDataset::WMTSDataset() : papszHTTPOptions(nullptr) WMTSDataset::~WMTSDataset() { WMTSDataset::CloseDependentDatasets(); - CSLDestroy(papszHTTPOptions); } /************************************************************************/ @@ -883,7 +882,7 @@ CPLString WMTSDataset::Replace(const CPLString &osStr, const char *pszOld, /************************************************************************/ CPLXMLNode *WMTSDataset::GetCapabilitiesResponse(const CPLString &osFilename, - char **papszHTTPOptions) + CSLConstList papszHTTPOptions) { CPLXMLNode *psXML; VSIStatBufL sStat; @@ -965,42 +964,34 @@ CPLString WMTSDataset::GetOperationKVPURL(CPLXMLNode *psXML, /* BuildHTTPRequestOpts() */ /************************************************************************/ -char **WMTSDataset::BuildHTTPRequestOpts(CPLString osOtherXML) +CPLStringList WMTSDataset::BuildHTTPRequestOpts(CPLString osOtherXML) { osOtherXML = "" + osOtherXML + ""; CPLXMLNode *psXML = CPLParseXMLString(osOtherXML); - char **http_request_opts = nullptr; + CPLStringList opts; if (CPLGetXMLValue(psXML, "Timeout", nullptr)) { - CPLString optstr; - optstr.Printf("TIMEOUT=%s", CPLGetXMLValue(psXML, "Timeout", nullptr)); - http_request_opts = CSLAddString(http_request_opts, optstr.c_str()); + opts.SetNameValue("TIMEOUT", CPLGetXMLValue(psXML, "Timeout", nullptr)); } if (CPLGetXMLValue(psXML, "UserAgent", nullptr)) { - CPLString optstr; - optstr.Printf("USERAGENT=%s", - CPLGetXMLValue(psXML, "UserAgent", nullptr)); - http_request_opts = CSLAddString(http_request_opts, optstr.c_str()); + opts.SetNameValue("USERAGENT", + CPLGetXMLValue(psXML, "UserAgent", nullptr)); } if (CPLGetXMLValue(psXML, "Referer", nullptr)) { - CPLString optstr; - optstr.Printf("REFERER=%s", CPLGetXMLValue(psXML, "Referer", nullptr)); - http_request_opts = CSLAddString(http_request_opts, optstr.c_str()); + opts.SetNameValue("REFERER", CPLGetXMLValue(psXML, "Referer", nullptr)); } if (CPLTestBool(CPLGetXMLValue(psXML, "UnsafeSSL", "false"))) { - http_request_opts = CSLAddString(http_request_opts, "UNSAFESSL=1"); + opts.SetNameValue("UNSAFESSL", "1"); } if (CPLGetXMLValue(psXML, "UserPwd", nullptr)) { - CPLString optstr; - optstr.Printf("USERPWD=%s", CPLGetXMLValue(psXML, "UserPwd", nullptr)); - http_request_opts = CSLAddString(http_request_opts, optstr.c_str()); + opts.SetNameValue("USERPWD", CPLGetXMLValue(psXML, "UserPwd", nullptr)); } CPLDestroyXMLNode(psXML); - return http_request_opts; + return opts; } /************************************************************************/ @@ -1074,9 +1065,17 @@ GDALDataset *WMTSDataset::Open(GDALOpenInfo *poOpenInfo) } CSLDestroy(papszTokens); - char **papszHTTPOptions = BuildHTTPRequestOpts(osOtherXML); - psXML = GetCapabilitiesResponse(osGetCapabilitiesURL, papszHTTPOptions); - CSLDestroy(papszHTTPOptions); + const CPLStringList aosHTTPOptions(BuildHTTPRequestOpts(osOtherXML)); + psXML = GetCapabilitiesResponse(osGetCapabilitiesURL, + aosHTTPOptions.List()); + } + else if (poOpenInfo->IsSingleAllowedDriver("WMTS") && + (STARTS_WITH(poOpenInfo->pszFilename, "http://") || + STARTS_WITH(poOpenInfo->pszFilename, "https://"))) + { + const CPLStringList aosHTTPOptions(BuildHTTPRequestOpts(osOtherXML)); + psXML = GetCapabilitiesResponse(poOpenInfo->pszFilename, + aosHTTPOptions.List()); } int bHasAOI = FALSE; @@ -1179,11 +1178,13 @@ GDALDataset *WMTSDataset::Open(GDALOpenInfo *poOpenInfo) CPLDestroyXMLNode(psGDALWMTS); CPLDestroyXMLNode(psXML); - char **papszHTTPOptions = BuildHTTPRequestOpts(osOtherXML); - psXML = GetCapabilitiesResponse(osGetCapabilitiesURL, papszHTTPOptions); - CSLDestroy(papszHTTPOptions); + const CPLStringList aosHTTPOptions(BuildHTTPRequestOpts(osOtherXML)); + psXML = GetCapabilitiesResponse(osGetCapabilitiesURL, + aosHTTPOptions.List()); } - else if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "WMTS:")) + else if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "WMTS:") && + !STARTS_WITH(poOpenInfo->pszFilename, "http://") && + !STARTS_WITH(poOpenInfo->pszFilename, "https://")) { osGetCapabilitiesURL = poOpenInfo->pszFilename; psXML = CPLParseXMLFile(poOpenInfo->pszFilename); @@ -1554,7 +1555,7 @@ GDALDataset *WMTSDataset::Open(GDALOpenInfo *poOpenInfo) if (!osSelectLayerAbstract.empty()) poDS->SetMetadataItem("ABSTRACT", osSelectLayerAbstract); - poDS->papszHTTPOptions = BuildHTTPRequestOpts(osOtherXML); + poDS->m_aosHTTPOptions = BuildHTTPRequestOpts(osOtherXML); poDS->osLayer = osSelectLayer; poDS->osTMS = osSelectTMS; diff --git a/frmts/wmts/wmtsdrivercore.cpp b/frmts/wmts/wmtsdrivercore.cpp index cc4275cf5b4a..19a21f77934e 100644 --- a/frmts/wmts/wmtsdrivercore.cpp +++ b/frmts/wmts/wmtsdrivercore.cpp @@ -42,18 +42,35 @@ int WMTSDriverIdentify(GDALOpenInfo *poOpenInfo) if (STARTS_WITH_CI(poOpenInfo->pszFilename, "IsSingleAllowedDriver("WMTS"); + if (bIsSingleDriver && (STARTS_WITH(poOpenInfo->pszFilename, "http://") || + STARTS_WITH(poOpenInfo->pszFilename, "https://"))) + { + return true; + } + if (poOpenInfo->nHeaderBytes == 0) return FALSE; - if (strstr((const char *)poOpenInfo->pabyHeader, "(poOpenInfo->pabyHeader); + if (strstr(pszHeader, "(*pszHeader))) + ++pszHeader; + return *pszHeader == '<'; + } - return (strstr((const char *)poOpenInfo->pabyHeader, "pabyHeader, - "pabyHeader, - "http://www.opengis.net/wmts/1.0") != nullptr; + return FALSE; } /************************************************************************/ From 3fa4d74b0c039ed583ce95e15234392bb209ae47 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 00:56:49 +0200 Subject: [PATCH 0121/1119] OAPIF: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/ogr/ogr_oapif.py | 4 +++- doc/source/drivers/vector/oapif.rst | 17 +++++++++++------ ogr/ogrsf_frmts/wfs/ogroapifdriver.cpp | 5 ++++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/autotest/ogr/ogr_oapif.py b/autotest/ogr/ogr_oapif.py index 55ddd1104a30..d83a5bd8b316 100755 --- a/autotest/ogr/ogr_oapif.py +++ b/autotest/ogr/ogr_oapif.py @@ -1968,7 +1968,9 @@ def test_ogr_oapif_initial_request_page_size(): ) with webserver.install_http_handler(handler): ds = gdal.OpenEx( - "OAPIF:http://localhost:%d/oapif" % gdaltest.webserver_port, gdal.OF_VECTOR + "http://localhost:%d/oapif" % gdaltest.webserver_port, + gdal.OF_VECTOR, + allowed_drivers=["OAPIF"], ) lyr = ds.GetLayer(0) diff --git a/doc/source/drivers/vector/oapif.rst b/doc/source/drivers/vector/oapif.rst index 370f90559f22..52430bbf9fe4 100644 --- a/doc/source/drivers/vector/oapif.rst +++ b/doc/source/drivers/vector/oapif.rst @@ -31,6 +31,11 @@ The syntax to open a OGC API - Features datasource is : where endpoint is the landing page or a the path to collections/{id}. +Starting with GDAL 3.10, specifying the ``-if OAPIF`` option to command line utilities +accepting it, or ``OAPIF`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +URL without the ``OAPIF:`` prefix. + Layer schema ------------ @@ -144,7 +149,7 @@ Examples :: $ ogrinfo OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr - + INFO: Open of `OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr' using driver `OAPIF' successful. 1: governmentalservice (title: Feuerwehrleitstellen) (Point) @@ -153,11 +158,11 @@ Examples :: - $ ogrinfo OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr governmentalservice -al -so - + $ ogrinfo OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr governmentalservice -al -so + INFO: Open of `OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr' using driver `OAPIF' successful. - + Layer name: governmentalservice Metadata: DESCRIPTION=Staatliche Verwaltungs- und Sozialdienste wie öffentliche Verwaltung, Katastrophenschutz, Schulen und Krankenhäuser, die von öffentlichen oder privaten Einrichtungen erbracht werden, soweit sie in den Anwendungsbereich der Richtlinie 2007/2/EG fallen. Dieser Datensatz enthält Informationen zu Feuerwehrleitstellen. @@ -208,7 +213,7 @@ Examples :: $ ogrinfo OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr governmentalservice -al -q -where "name = 'Schwelm'" - + Layer name: governmentalservice Metadata: DESCRIPTION=Staatliche Verwaltungs- und Sozialdienste wie öffentliche Verwaltung, Katastrophenschutz, Schulen und Krankenhäuser, die von öffentlichen oder privaten Einrichtungen erbracht werden, soweit sie in den Anwendungsbereich der Richtlinie 2007/2/EG fallen. Dieser Datensatz enthält Informationen zu Feuerwehrleitstellen. @@ -241,7 +246,7 @@ Examples :: $ ogrinfo OAPIF:https://ogc-api.nrw.de/inspire-us-feuerwehr governmentalservice -al -q -spat 7.1 51.2 7.2 51.5 - + Layer name: governmentalservice Metadata: DESCRIPTION=Staatliche Verwaltungs- und Sozialdienste wie öffentliche Verwaltung, Katastrophenschutz, Schulen und Krankenhäuser, die von öffentlichen oder privaten Einrichtungen erbracht werden, soweit sie in den Anwendungsbereich der Richtlinie 2007/2/EG fallen. Dieser Datensatz enthält Informationen zu Feuerwehrleitstellen. diff --git a/ogr/ogrsf_frmts/wfs/ogroapifdriver.cpp b/ogr/ogrsf_frmts/wfs/ogroapifdriver.cpp index b46e324c1234..21c2ce1f6181 100644 --- a/ogr/ogrsf_frmts/wfs/ogroapifdriver.cpp +++ b/ogr/ogrsf_frmts/wfs/ogroapifdriver.cpp @@ -1201,7 +1201,10 @@ static int OGROAPIFDriverIdentify(GDALOpenInfo *poOpenInfo) { return STARTS_WITH_CI(poOpenInfo->pszFilename, "WFS3:") || STARTS_WITH_CI(poOpenInfo->pszFilename, "OAPIF:") || - STARTS_WITH_CI(poOpenInfo->pszFilename, "OAPIF_COLLECTION:"); + STARTS_WITH_CI(poOpenInfo->pszFilename, "OAPIF_COLLECTION:") || + (poOpenInfo->IsSingleAllowedDriver("OAPIF") && + (STARTS_WITH(poOpenInfo->pszFilename, "http://") || + STARTS_WITH(poOpenInfo->pszFilename, "https://"))); } /************************************************************************/ From 26e4a92324f161e39ac828cccb9170b6ef428d9d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 01:12:19 +0200 Subject: [PATCH 0122/1119] WFS: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/ogr/ogr_wfs.py | 38 ++++++++++---------- doc/source/drivers/vector/wfs.rst | 5 +++ ogr/ogrsf_frmts/wfs/ogr_wfs.h | 3 +- ogr/ogrsf_frmts/wfs/ogrwfsdatasource.cpp | 46 ++++++++++++------------ ogr/ogrsf_frmts/wfs/ogrwfsdriver.cpp | 28 +++++++++++---- 5 files changed, 71 insertions(+), 49 deletions(-) diff --git a/autotest/ogr/ogr_wfs.py b/autotest/ogr/ogr_wfs.py index 3b27bd7e71a2..7873234f9d2a 100755 --- a/autotest/ogr/ogr_wfs.py +++ b/autotest/ogr/ogr_wfs.py @@ -499,34 +499,32 @@ def do_GET(self): # Test reading a local fake WFS server -def test_ogr_wfs_fake_wfs_server(): +@gdaltest.enable_exceptions() +@pytest.mark.parametrize("using_wfs_prefix", [True, False]) +def test_ogr_wfs_fake_wfs_server(using_wfs_prefix): (process, port) = webserver.launch(handler=WFSHTTPHandler) if port == 0: pytest.skip() - with gdal.config_option("OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN", "NO"): - ds = ogr.Open("WFS:http://127.0.0.1:%d/fakewfs" % port) - if ds is None: - webserver.server_stop(process, port) - pytest.fail("did not managed to open WFS datastore") + try: + with gdal.config_option("OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN", "NO"): + if using_wfs_prefix: + ds = gdal.OpenEx("WFS:http://127.0.0.1:%d/fakewfs" % port) + else: + ds = gdal.OpenEx( + "http://127.0.0.1:%d/fakewfs" % port, allowed_drivers=["WFS"] + ) - lyr = ds.GetLayerByName("rijkswegen") - if lyr.GetName() != "rijkswegen": - print(lyr.GetName()) - webserver.server_stop(process, port) - pytest.fail("did not get expected layer name") + lyr = ds.GetLayerByName("rijkswegen") + assert lyr.GetName() == "rijkswegen" - sr = lyr.GetSpatialRef() - sr2 = osr.SpatialReference() - sr2.ImportFromEPSG(28992) - if not sr.IsSame(sr2): - print(sr) - webserver.server_stop(process, port) - pytest.fail("did not get expected SRS") + sr = lyr.GetSpatialRef() + sr2 = osr.SpatialReference() + sr2.ImportFromEPSG(28992) + assert sr.IsSame(sr2), sr - feat = lyr.GetNextFeature() - try: + feat = lyr.GetNextFeature() assert feat.GetField("MPLength") == "33513." ogrtest.check_feature_geometry( feat, diff --git a/doc/source/drivers/vector/wfs.rst b/doc/source/drivers/vector/wfs.rst index 7ceb53e67b10..bc45eddfbdcd 100644 --- a/doc/source/drivers/vector/wfs.rst +++ b/doc/source/drivers/vector/wfs.rst @@ -30,6 +30,11 @@ The minimal syntax to open a WFS datasource is : *WFS:http://path/to/WFS/service* or *http://path/to/WFS/service?SERVICE=WFS* +Starting with GDAL 3.10, specifying the ``-if WFS`` option to command line utilities +accepting it, or ``WFS`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +URL without the ``WFS:`` prefix. + Additional optional parameters can be specified such as *TYPENAME*, *VERSION*, *MAXFEATURES* (WFS < 2) or *COUNT* (WFS > 2) as specified in WFS specification. diff --git a/ogr/ogrsf_frmts/wfs/ogr_wfs.h b/ogr/ogrsf_frmts/wfs/ogr_wfs.h index 566b81c7f5bb..91ca09f34f3d 100644 --- a/ogr/ogrsf_frmts/wfs/ogr_wfs.h +++ b/ogr/ogrsf_frmts/wfs/ogr_wfs.h @@ -399,7 +399,8 @@ class OGRWFSDataSource final : public OGRDataSource OGRWFSDataSource(); virtual ~OGRWFSDataSource(); - int Open(const char *pszFilename, int bUpdate, char **papszOpenOptions); + int Open(const char *pszFilename, int bUpdate, + CSLConstList papszOpenOptions); virtual const char *GetName() override { diff --git a/ogr/ogrsf_frmts/wfs/ogrwfsdatasource.cpp b/ogr/ogrsf_frmts/wfs/ogrwfsdatasource.cpp index 8e523309ae7f..3bb8ef68f0b2 100644 --- a/ogr/ogrsf_frmts/wfs/ogrwfsdatasource.cpp +++ b/ogr/ogrsf_frmts/wfs/ogrwfsdatasource.cpp @@ -801,30 +801,18 @@ CPLXMLNode *OGRWFSDataSource::LoadFromFile(const char *pszFilename) if (fp == nullptr) return nullptr; - char achHeader[1024] = {}; - const int nRead = - static_cast(VSIFReadL(achHeader, 1, sizeof(achHeader) - 1, fp)); - if (nRead == 0) - { - VSIFCloseL(fp); - return nullptr; - } - achHeader[nRead] = 0; - - if (!STARTS_WITH_CI(achHeader, "") && - strstr(achHeader, " 100 * 1024 * 1024) + { + VSIFCloseL(fp); + return nullptr; + } + const int nLen = static_cast(nLenLarge); char *pszXML = (char *)VSI_MALLOC_VERBOSE(nLen + 1); if (pszXML == nullptr) @@ -842,6 +830,13 @@ CPLXMLNode *OGRWFSDataSource::LoadFromFile(const char *pszFilename) } VSIFCloseL(fp); + if (!STARTS_WITH_CI(pszXML, "") && + strstr(pszXML, "pszFilename, "WFS:")) { + const bool bIsSingleDriver = poOpenInfo->IsSingleAllowedDriver("WFS"); + if (bIsSingleDriver && + (STARTS_WITH(poOpenInfo->pszFilename, "http://") || + STARTS_WITH(poOpenInfo->pszFilename, "https://"))) + { + return true; + } + if (poOpenInfo->fpL == nullptr) return FALSE; - if (!STARTS_WITH_CI((const char *)poOpenInfo->pabyHeader, - "") && - strstr((const char *)poOpenInfo->pabyHeader, "pabyHeader, - "(poOpenInfo->pabyHeader); + if (!STARTS_WITH_CI(pszHeader, "") && + strstr(pszHeader, "(*pszHeader))) + ++pszHeader; + return *pszHeader == '<'; + } + return FALSE; } } From 90b9567ced870bc318983f25d0615daa40ef8890 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 5 Jun 2024 01:28:36 +0200 Subject: [PATCH 0123/1119] WMS: relax identification checks when papszAllowedDrivers[] contains only the driver name --- .../gdrivers/data/wms/demo_mapserver_org.xml | 215 ++++++++++++++++++ autotest/gdrivers/wms.py | 38 ++++ doc/source/drivers/raster/wms.rst | 5 + frmts/wms/wmsdriver.cpp | 8 +- frmts/wms/wmsdrivercore.cpp | 7 + 5 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 autotest/gdrivers/data/wms/demo_mapserver_org.xml diff --git a/autotest/gdrivers/data/wms/demo_mapserver_org.xml b/autotest/gdrivers/data/wms/demo_mapserver_org.xml new file mode 100644 index 000000000000..f76a7c536988 --- /dev/null +++ b/autotest/gdrivers/data/wms/demo_mapserver_org.xml @@ -0,0 +1,215 @@ + + + + WMS + WMS Demo Server for MapServer + This demonstration server showcases MapServer (www.mapserver.org) and its OGC support + + + + Jeff McKenna + GatewayGeo + + Director + info@gatewaygeomatics.com + + 4096 + 4096 + + + + + + text/xml + + + + + + + + + image/png + image/jpeg + image/png; mode=8bit + image/vnd.jpeg-png + image/vnd.jpeg-png8 + application/x-pdf + image/svg+xml + image/tiff + application/vnd.google-earth.kml+xml + application/vnd.google-earth.kmz + application/vnd.mapbox-vector-tile + application/x-protobuf + application/json + + + + + + + + + text/html + application/vnd.ogc.gml + text/plain + + + + + + + + + text/xml + + + + + + + + + image/png + image/jpeg + image/png; mode=8bit + image/vnd.jpeg-png + image/vnd.jpeg-png8 + + + + + + + + + text/xml + + + + + + + + + + XML + INIMAGE + BLANK + + + + WMS_server + WMS Demo Server for MapServer + This demonstration server showcases MapServer (www.mapserver.org) and its OGC support + EPSG:4326 + EPSG:4269 + EPSG:3978 + EPSG:3857 + + -180.000000 + 180.000000 + -90.000000 + 90.000000 + + + + bluemarble + Blue Marble World Elevation and Bathymetry Raster + EPSG:4326 + + -180.000000 + 180.000000 + -90.000000 + 90.000000 + + + + NASA Blue Marble + + + + text/xml + + + + + continents + World continents + EPSG:4326 + + -180.000000 + 180.000000 + -90.000000 + 83.627419 + + + + text/xml + + + + + + country_bounds + World country boundaries + EPSG:4326 + + -180.000000 + 180.000000 + -90.000000 + 83.627419 + + + + text/xml + + + + + + cities + World cities + EPSG:4326 + + -178.166667 + 179.383333 + -54.800000 + 78.933333 + + + + text/xml + + + + + + + diff --git a/autotest/gdrivers/wms.py b/autotest/gdrivers/wms.py index 1eab4bc2405e..9e7ca4da5508 100755 --- a/autotest/gdrivers/wms.py +++ b/autotest/gdrivers/wms.py @@ -37,6 +37,7 @@ import gdaltest import pytest +import webserver from osgeo import gdal @@ -1162,3 +1163,40 @@ def test_wms_cache_path(): with pytest.raises(Exception): gdal.Open("") + + +# Launch a single webserver in a module-scoped fixture. +@pytest.fixture(scope="module") +def webserver_launch(): + + process, port = webserver.launch(handler=webserver.DispatcherHttpHandler) + + yield process, port + + webserver.server_stop(process, port) + + +@pytest.fixture(scope="function") +def webserver_port(webserver_launch): + + webserver_process, webserver_port = webserver_launch + + if webserver_port == 0: + pytest.skip() + yield webserver_port + + +@pytest.mark.require_curl +@gdaltest.enable_exceptions() +def test_wms_force_opening_url(tmp_vsimem, webserver_port): + + handler = webserver.SequentialHandler() + handler.add( + "GET", + "/?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetCapabilities", + 200, + {"Content-type": "application/xml"}, + open("data/wms/demo_mapserver_org.xml", "rb").read(), + ) + with webserver.install_http_handler(handler): + gdal.OpenEx(f"http://localhost:{webserver_port}", allowed_drivers=["WMS"]) diff --git a/doc/source/drivers/raster/wms.rst b/doc/source/drivers/raster/wms.rst index a16f08fa588b..c34a4110d9db 100644 --- a/doc/source/drivers/raster/wms.rst +++ b/doc/source/drivers/raster/wms.rst @@ -475,6 +475,11 @@ The WMS driver can open : A list of subdatasets will be returned, resulting from the parsing of the GetCapabilities request on that server. + Starting with GDAL 3.10, specifying the ``-if WMS`` option to command line utilities + accepting it, or ``WMS`` as the only value of the ``papszAllowedDrivers`` of + :cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed + URL, without the ``WMS:`` prefix. + - a pseudo GetMap request, such as the subdataset name returned by the previous syntax : diff --git a/frmts/wms/wmsdriver.cpp b/frmts/wms/wmsdriver.cpp index 6dd0f93965dc..a372989d9b6c 100644 --- a/frmts/wms/wmsdriver.cpp +++ b/frmts/wms/wmsdriver.cpp @@ -66,7 +66,7 @@ static CPLXMLNode *GDALWMSDatasetGetConfigFromURL(GDALOpenInfo *poOpenInfo) { const char *pszBaseURL = poOpenInfo->pszFilename; if (STARTS_WITH_CI(pszBaseURL, "WMS:")) - pszBaseURL += 4; + pszBaseURL += strlen("WMS:"); const CPLString osLayer = CPLURLGetValue(pszBaseURL, "LAYERS"); CPLString osVersion = CPLURLGetValue(pszBaseURL, "VERSION"); @@ -771,7 +771,11 @@ GDALDataset *GDALWMSDataset::Open(GDALOpenInfo *poOpenInfo) else if (poOpenInfo->nHeaderBytes == 0 && (STARTS_WITH_CI(pszFilename, "WMS:") || - CPLString(pszFilename).ifind("SERVICE=WMS") != std::string::npos)) + CPLString(pszFilename).ifind("SERVICE=WMS") != + std::string::npos || + (poOpenInfo->IsSingleAllowedDriver("WMS") && + (STARTS_WITH(poOpenInfo->pszFilename, "http://") || + STARTS_WITH(poOpenInfo->pszFilename, "https://"))))) { CPLString osLayers = CPLURLGetValue(pszFilename, "LAYERS"); CPLString osRequest = CPLURLGetValue(pszFilename, "REQUEST"); diff --git a/frmts/wms/wmsdrivercore.cpp b/frmts/wms/wmsdrivercore.cpp index 72a4c5090c5a..122594bf5db2 100644 --- a/frmts/wms/wmsdrivercore.cpp +++ b/frmts/wms/wmsdrivercore.cpp @@ -55,6 +55,13 @@ int WMSDriverIdentify(GDALOpenInfo *poOpenInfo) { return TRUE; } + else if (poOpenInfo->nHeaderBytes == 0 && + poOpenInfo->IsSingleAllowedDriver("WMS") && + (STARTS_WITH(poOpenInfo->pszFilename, "http://") || + STARTS_WITH(poOpenInfo->pszFilename, "https://"))) + { + return true; + } else if (poOpenInfo->nHeaderBytes != 0 && (strstr(pabyHeader, " Date: Thu, 6 Jun 2024 18:49:46 +0200 Subject: [PATCH 0124/1119] GML: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/ogr/ogr_gml.py | 28 +++++++ autotest/ogr/ogr_wfs.py | 21 +++--- ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp | 96 ++++++++++++------------ ogr/ogrsf_frmts/gml/ogrgmldriver.cpp | 46 +++++++++--- ogr/ogrsf_frmts/wfs/ogrwfsjoinlayer.cpp | 40 ++++------ 5 files changed, 135 insertions(+), 96 deletions(-) diff --git a/autotest/ogr/ogr_gml.py b/autotest/ogr/ogr_gml.py index eadda1826340..1a167b4fdffe 100755 --- a/autotest/ogr/ogr_gml.py +++ b/autotest/ogr/ogr_gml.py @@ -4356,3 +4356,31 @@ def test_ogr_gml_geom_link_to_immediate_child(): "data/gml/link_to_immediate_child.gml", open_options=["WRITE_GFS=NO"] ) assert ds + + +############################################################################### +# Test force opening a GML file + + +@gdaltest.enable_exceptions() +def test_ogr_gml_force_opening(tmp_vsimem): + + filename = str(tmp_vsimem / "test.xml") + + with gdaltest.vsi_open(filename, "wb") as f: + f.write( + b""" + missing +""" + ) + + with pytest.raises(Exception): + gdal.OpenEx(filename) + + ds = gdal.OpenEx(filename, allowed_drivers=["GML"]) + assert ds.GetDriver().GetDescription() == "GML" diff --git a/autotest/ogr/ogr_wfs.py b/autotest/ogr/ogr_wfs.py index 7873234f9d2a..1408fa7d5511 100755 --- a/autotest/ogr/ogr_wfs.py +++ b/autotest/ogr/ogr_wfs.py @@ -4427,10 +4427,8 @@ def test_ogr_wfs_vsimem_wfs200_join(with_and_without_streaming): ): with gdal.quiet_errors(): f = sql_lyr.GetNextFeature() - assert ( - f is None - and gdal.GetLastErrorMsg().find("Empty content returned by server") >= 0 - ) + assert f is None + assert "Empty content returned by server" in gdal.GetLastErrorMsg() ds = ogr.Open("WFS:/vsimem/wfs200_endpoint_join") with ds.ExecuteSQL( @@ -4441,10 +4439,8 @@ def test_ogr_wfs_vsimem_wfs200_join(with_and_without_streaming): ): with gdal.quiet_errors(): f = sql_lyr.GetNextFeature() - assert ( - f is None - and gdal.GetLastErrorMsg().find("Error returned by server") >= 0 - ) + assert f is None + assert "Error returned by server" in gdal.GetLastErrorMsg() ds = ogr.Open("WFS:/vsimem/wfs200_endpoint_join") with ds.ExecuteSQL( @@ -4455,7 +4451,11 @@ def test_ogr_wfs_vsimem_wfs200_join(with_and_without_streaming): ): with gdal.quiet_errors(): f = sql_lyr.GetNextFeature() - assert f is None and gdal.GetLastErrorMsg().find("Error: cannot parse") >= 0 + assert f is None + assert ( + "Error: cannot parse" in gdal.GetLastErrorMsg() + or "XML parsing of GML file failed" in gdal.GetLastErrorMsg() + ) ds = ogr.Open("WFS:/vsimem/wfs200_endpoint_join") with ds.ExecuteSQL( @@ -4466,7 +4466,8 @@ def test_ogr_wfs_vsimem_wfs200_join(with_and_without_streaming): ): with gdal.quiet_errors(): f = sql_lyr.GetNextFeature() - assert f is None and gdal.GetLastErrorMsg().find("Error: cannot parse") >= 0 + assert f is None + assert "Error: cannot parse" in gdal.GetLastErrorMsg() ds = ogr.Open("WFS:/vsimem/wfs200_endpoint_join") with gdaltest.tempfile( diff --git a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp index 4ef9d4f6b47a..38eb73b14301 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp @@ -283,52 +283,45 @@ OGRGMLDataSource::~OGRGMLDataSource() bool OGRGMLDataSource::CheckHeader(const char *pszStr) { - if (strstr(pszStr, "") != nullptr || - strstr(pszStr, "") || + strstr(pszStr, "(abyHeader); - if ((static_cast(szHeader[0]) == 0xEF) && - (static_cast(szHeader[1]) == 0xBB) && - (static_cast(szHeader[2]) == 0xBF)) + if (memcmp(pszPtr, "\xEF\xBB\xBF", 3) == 0) { - szPtr += 3; + pszPtr += 3; } + // Skip spaces + while (*pszPtr && std::isspace(static_cast(*pszPtr))) + ++pszPtr; + bool bExpatCompatibleEncoding = false; - const char *pszEncoding = strstr(szPtr, "encoding="); + const char *pszEncoding = strstr(pszPtr, "encoding="); if (pszEncoding) bExpatCompatibleEncoding = (pszEncoding[9] == '\'' || pszEncoding[9] == '"') && @@ -470,11 +465,12 @@ bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo) else bExpatCompatibleEncoding = true; // utf-8 is the default. - const bool bHas3D = strstr(szPtr, "srsDimension=\"3\"") != nullptr || - strstr(szPtr, "") != nullptr; + const bool bHas3D = strstr(pszPtr, "srsDimension=\"3\"") != nullptr || + strstr(pszPtr, "") != nullptr; // Here, we expect the opening chevrons of GML tree root element. - if (szPtr[0] != '<' || !CheckHeader(szPtr)) + if (pszPtr[0] != '<' || + (!poOpenInfo->IsSingleAllowedDriver("GML") && !CheckHeader(pszPtr))) { if (fpToClose) VSIFCloseL(fpToClose); @@ -488,16 +484,16 @@ bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo) // Small optimization: if we parse a and // that numberOfFeatures is set, we can use it to set the FeatureCount // but *ONLY* if there's just one class. - const char *pszFeatureCollection = strstr(szPtr, "wfs:FeatureCollection"); + const char *pszFeatureCollection = strstr(pszPtr, "wfs:FeatureCollection"); if (pszFeatureCollection == nullptr) // GML 3.2.1 output. - pszFeatureCollection = strstr(szPtr, "gml:FeatureCollection"); + pszFeatureCollection = strstr(pszPtr, "gml:FeatureCollection"); if (pszFeatureCollection == nullptr) { // Deegree WFS 1.0.0 output. - pszFeatureCollection = strstr(szPtr, ""); + bool bIsWFSJointLayer = bIsWFS && strstr(pszPtr, ""); if (bIsWFSJointLayer) bExposeGMLId = false; @@ -923,7 +919,7 @@ bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo) CPLGetConfigOption("GML_REGISTRY", ""))); if (oRegistry.Parse()) { - CPLString osHeader(szHeader); + const CPLString osHeader(pszPtr); for (size_t iNS = 0; iNS < oRegistry.aoNamespaces.size(); iNS++) { GMLRegistryNamespace &oNamespace = @@ -946,7 +942,7 @@ bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo) const char *pszURIToFind = CPLSPrintf("\"%s\"", oNamespace.osURI.c_str()); - if (strstr(szHeader, pszURIToFind) != nullptr) + if (strstr(pszPtr, pszURIToFind) != nullptr) { if (oNamespace.bUseGlobalSRSName) bUseGlobalSRSName = true; diff --git a/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp b/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp index 8a016dbe20cc..783cb1af29f7 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp @@ -55,27 +55,49 @@ static int OGRGMLDriverIdentify(GDALOpenInfo *poOpenInfo) } else { - const char *szPtr = (const char *)poOpenInfo->pabyHeader; + const char *pszPtr = + reinterpret_cast(poOpenInfo->pabyHeader); - if (((unsigned char)szPtr[0] == 0xEF) && - ((unsigned char)szPtr[1] == 0xBB) && - ((unsigned char)szPtr[2] == 0xBF)) + // Skip UTF-8 BOM + if (poOpenInfo->nHeaderBytes > 3 && + memcmp(poOpenInfo->pabyHeader, "\xEF\xBB\xBF", 3) == 0) { - szPtr += 3; + pszPtr += 3; } - /* -------------------------------------------------------------------- - */ - /* Here, we expect the opening chevrons of GML tree root element */ - /* -------------------------------------------------------------------- - */ - if (szPtr[0] != '<') + + // Skip spaces + while (*pszPtr && std::isspace(static_cast(*pszPtr))) + ++pszPtr; + + // Here, we expect the opening chevrons of GML tree root element */ + if (pszPtr[0] != '<') + return FALSE; + + if (strstr(pszPtr, "IsSingleAllowedDriver("GML")) + return TRUE; + // TryToIngest() invalidates above pszPtr + pszPtr = nullptr; + CPL_IGNORE_RET_VAL(pszPtr); if (!poOpenInfo->TryToIngest(4096)) return FALSE; return OGRGMLDataSource::CheckHeader( - (const char *)poOpenInfo->pabyHeader); + reinterpret_cast(poOpenInfo->pabyHeader)); } } diff --git a/ogr/ogrsf_frmts/wfs/ogrwfsjoinlayer.cpp b/ogr/ogrsf_frmts/wfs/ogrwfsjoinlayer.cpp index f024b1d02b76..6c0c55e7cc29 100644 --- a/ogr/ogrsf_frmts/wfs/ogrwfsjoinlayer.cpp +++ b/ogr/ogrsf_frmts/wfs/ogrwfsjoinlayer.cpp @@ -447,13 +447,13 @@ GDALDataset *OGRWFSJoinLayer::FetchGetFeature() const char *const apszAllowedDrivers[] = {"GML", nullptr}; const char *apszOpenOptions[2] = {nullptr, nullptr}; apszOpenOptions[0] = CPLSPrintf("XSD=%s", osXSDFileName.c_str()); - GDALDataset *poGML_DS = (GDALDataset *)GDALOpenEx( - pszStreamingName, GDAL_OF_VECTOR, apszAllowedDrivers, - apszOpenOptions, nullptr); - if (poGML_DS) + auto poGML_DS = std::unique_ptr( + GDALDataset::Open(pszStreamingName, GDAL_OF_VECTOR, + apszAllowedDrivers, apszOpenOptions, nullptr)); + if (poGML_DS && poGML_DS->GetLayerCount() != 0) { // bStreamingDS = true; - return poGML_DS; + return poGML_DS.release(); } /* In case of failure, read directly the content to examine */ @@ -516,30 +516,22 @@ GDALDataset *OGRWFSJoinLayer::FetchGetFeature() CPLHTTPDestroyResult(psResult); - OGRDataSource *l_poDS = - (OGRDataSource *)OGROpen(osTmpFileName, FALSE, nullptr); - if (l_poDS == nullptr) + auto l_poDS = std::unique_ptr(GDALDataset::Open( + osTmpFileName.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); + if (l_poDS && l_poDS->GetLayerCount() != 0) { - if (strstr((const char *)pabyData, " 1000) - pabyData[1000] = 0; - CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s", - pabyData); - } - return nullptr; + return l_poDS.release(); } - OGRLayer *poLayer = l_poDS->GetLayer(0); - if (poLayer == nullptr) + if (strstr((const char *)pabyData, " 1000) + pabyData[1000] = 0; + CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s", + pabyData); } - - return l_poDS; + return nullptr; } /************************************************************************/ From 4f395854df5207342694303315bc66f7e1eec12f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 11 Jun 2024 00:57:17 +0200 Subject: [PATCH 0125/1119] Revert "GML: relax identification checks when papszAllowedDrivers[] contains only the driver name" This reverts commit 36b7a5cc64f9853c46200d906c37f7c91a25e3f7. To avoid test failure on osx 45: =================================== FAILURES =================================== 45: _____________________ test_gml_read_compound_crs_lat_long ______________________ 45: 45: tmp_path = PosixPath('/private/var/folders/lr/439_fwvd3m76p9vy50d57kcc0000gn/T/pytest-of-runner/pytest-2/test_gml_read_compound_crs_lat0') 45: 45: def test_gml_read_compound_crs_lat_long(tmp_path): 45: 45: shutil.copy("data/gml/citygml_compound_crs.gml", tmp_path) 45: 45: # open CityGML file 45: gml = ogr.Open(tmp_path / "citygml_compound_crs.gml") 45: 45: # check number of layers 45: > assert gml.GetLayerCount() == 1, "Wrong layer count" 45: E AttributeError: 'NoneType' object has no attribute 'GetLayerCount' 45: /Users/runner/work/gdal/gdal/build/autotest/ogr/ogr_gml.py:639: AttributeError --- autotest/ogr/ogr_gml.py | 28 ------- autotest/ogr/ogr_wfs.py | 21 +++--- ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp | 96 ++++++++++++------------ ogr/ogrsf_frmts/gml/ogrgmldriver.cpp | 46 +++--------- ogr/ogrsf_frmts/wfs/ogrwfsjoinlayer.cpp | 40 ++++++---- 5 files changed, 96 insertions(+), 135 deletions(-) diff --git a/autotest/ogr/ogr_gml.py b/autotest/ogr/ogr_gml.py index 1a167b4fdffe..eadda1826340 100755 --- a/autotest/ogr/ogr_gml.py +++ b/autotest/ogr/ogr_gml.py @@ -4356,31 +4356,3 @@ def test_ogr_gml_geom_link_to_immediate_child(): "data/gml/link_to_immediate_child.gml", open_options=["WRITE_GFS=NO"] ) assert ds - - -############################################################################### -# Test force opening a GML file - - -@gdaltest.enable_exceptions() -def test_ogr_gml_force_opening(tmp_vsimem): - - filename = str(tmp_vsimem / "test.xml") - - with gdaltest.vsi_open(filename, "wb") as f: - f.write( - b""" - missing -""" - ) - - with pytest.raises(Exception): - gdal.OpenEx(filename) - - ds = gdal.OpenEx(filename, allowed_drivers=["GML"]) - assert ds.GetDriver().GetDescription() == "GML" diff --git a/autotest/ogr/ogr_wfs.py b/autotest/ogr/ogr_wfs.py index 1408fa7d5511..7873234f9d2a 100755 --- a/autotest/ogr/ogr_wfs.py +++ b/autotest/ogr/ogr_wfs.py @@ -4427,8 +4427,10 @@ def test_ogr_wfs_vsimem_wfs200_join(with_and_without_streaming): ): with gdal.quiet_errors(): f = sql_lyr.GetNextFeature() - assert f is None - assert "Empty content returned by server" in gdal.GetLastErrorMsg() + assert ( + f is None + and gdal.GetLastErrorMsg().find("Empty content returned by server") >= 0 + ) ds = ogr.Open("WFS:/vsimem/wfs200_endpoint_join") with ds.ExecuteSQL( @@ -4439,8 +4441,10 @@ def test_ogr_wfs_vsimem_wfs200_join(with_and_without_streaming): ): with gdal.quiet_errors(): f = sql_lyr.GetNextFeature() - assert f is None - assert "Error returned by server" in gdal.GetLastErrorMsg() + assert ( + f is None + and gdal.GetLastErrorMsg().find("Error returned by server") >= 0 + ) ds = ogr.Open("WFS:/vsimem/wfs200_endpoint_join") with ds.ExecuteSQL( @@ -4451,11 +4455,7 @@ def test_ogr_wfs_vsimem_wfs200_join(with_and_without_streaming): ): with gdal.quiet_errors(): f = sql_lyr.GetNextFeature() - assert f is None - assert ( - "Error: cannot parse" in gdal.GetLastErrorMsg() - or "XML parsing of GML file failed" in gdal.GetLastErrorMsg() - ) + assert f is None and gdal.GetLastErrorMsg().find("Error: cannot parse") >= 0 ds = ogr.Open("WFS:/vsimem/wfs200_endpoint_join") with ds.ExecuteSQL( @@ -4466,8 +4466,7 @@ def test_ogr_wfs_vsimem_wfs200_join(with_and_without_streaming): ): with gdal.quiet_errors(): f = sql_lyr.GetNextFeature() - assert f is None - assert "Error: cannot parse" in gdal.GetLastErrorMsg() + assert f is None and gdal.GetLastErrorMsg().find("Error: cannot parse") >= 0 ds = ogr.Open("WFS:/vsimem/wfs200_endpoint_join") with gdaltest.tempfile( diff --git a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp index 38eb73b14301..4ef9d4f6b47a 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp @@ -283,45 +283,52 @@ OGRGMLDataSource::~OGRGMLDataSource() bool OGRGMLDataSource::CheckHeader(const char *pszStr) { - // Definitely a GML compatible file - if (strstr(pszStr, "") || - strstr(pszStr, "") != nullptr || + strstr(pszStr, "(abyHeader); + char *szPtr = szHeader; - if (memcmp(pszPtr, "\xEF\xBB\xBF", 3) == 0) + if ((static_cast(szHeader[0]) == 0xEF) && + (static_cast(szHeader[1]) == 0xBB) && + (static_cast(szHeader[2]) == 0xBF)) { - pszPtr += 3; + szPtr += 3; } - // Skip spaces - while (*pszPtr && std::isspace(static_cast(*pszPtr))) - ++pszPtr; - bool bExpatCompatibleEncoding = false; - const char *pszEncoding = strstr(pszPtr, "encoding="); + const char *pszEncoding = strstr(szPtr, "encoding="); if (pszEncoding) bExpatCompatibleEncoding = (pszEncoding[9] == '\'' || pszEncoding[9] == '"') && @@ -465,12 +470,11 @@ bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo) else bExpatCompatibleEncoding = true; // utf-8 is the default. - const bool bHas3D = strstr(pszPtr, "srsDimension=\"3\"") != nullptr || - strstr(pszPtr, "") != nullptr; + const bool bHas3D = strstr(szPtr, "srsDimension=\"3\"") != nullptr || + strstr(szPtr, "") != nullptr; // Here, we expect the opening chevrons of GML tree root element. - if (pszPtr[0] != '<' || - (!poOpenInfo->IsSingleAllowedDriver("GML") && !CheckHeader(pszPtr))) + if (szPtr[0] != '<' || !CheckHeader(szPtr)) { if (fpToClose) VSIFCloseL(fpToClose); @@ -484,16 +488,16 @@ bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo) // Small optimization: if we parse a and // that numberOfFeatures is set, we can use it to set the FeatureCount // but *ONLY* if there's just one class. - const char *pszFeatureCollection = strstr(pszPtr, "wfs:FeatureCollection"); + const char *pszFeatureCollection = strstr(szPtr, "wfs:FeatureCollection"); if (pszFeatureCollection == nullptr) // GML 3.2.1 output. - pszFeatureCollection = strstr(pszPtr, "gml:FeatureCollection"); + pszFeatureCollection = strstr(szPtr, "gml:FeatureCollection"); if (pszFeatureCollection == nullptr) { // Deegree WFS 1.0.0 output. - pszFeatureCollection = strstr(pszPtr, ""); + bool bIsWFSJointLayer = bIsWFS && strstr(szPtr, ""); if (bIsWFSJointLayer) bExposeGMLId = false; @@ -919,7 +923,7 @@ bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo) CPLGetConfigOption("GML_REGISTRY", ""))); if (oRegistry.Parse()) { - const CPLString osHeader(pszPtr); + CPLString osHeader(szHeader); for (size_t iNS = 0; iNS < oRegistry.aoNamespaces.size(); iNS++) { GMLRegistryNamespace &oNamespace = @@ -942,7 +946,7 @@ bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo) const char *pszURIToFind = CPLSPrintf("\"%s\"", oNamespace.osURI.c_str()); - if (strstr(pszPtr, pszURIToFind) != nullptr) + if (strstr(szHeader, pszURIToFind) != nullptr) { if (oNamespace.bUseGlobalSRSName) bUseGlobalSRSName = true; diff --git a/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp b/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp index 783cb1af29f7..8a016dbe20cc 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmldriver.cpp @@ -55,49 +55,27 @@ static int OGRGMLDriverIdentify(GDALOpenInfo *poOpenInfo) } else { - const char *pszPtr = - reinterpret_cast(poOpenInfo->pabyHeader); + const char *szPtr = (const char *)poOpenInfo->pabyHeader; - // Skip UTF-8 BOM - if (poOpenInfo->nHeaderBytes > 3 && - memcmp(poOpenInfo->pabyHeader, "\xEF\xBB\xBF", 3) == 0) + if (((unsigned char)szPtr[0] == 0xEF) && + ((unsigned char)szPtr[1] == 0xBB) && + ((unsigned char)szPtr[2] == 0xBF)) { - pszPtr += 3; + szPtr += 3; } - - // Skip spaces - while (*pszPtr && std::isspace(static_cast(*pszPtr))) - ++pszPtr; - - // Here, we expect the opening chevrons of GML tree root element */ - if (pszPtr[0] != '<') - return FALSE; - - if (strstr(pszPtr, "IsSingleAllowedDriver("GML")) - return TRUE; - // TryToIngest() invalidates above pszPtr - pszPtr = nullptr; - CPL_IGNORE_RET_VAL(pszPtr); if (!poOpenInfo->TryToIngest(4096)) return FALSE; return OGRGMLDataSource::CheckHeader( - reinterpret_cast(poOpenInfo->pabyHeader)); + (const char *)poOpenInfo->pabyHeader); } } diff --git a/ogr/ogrsf_frmts/wfs/ogrwfsjoinlayer.cpp b/ogr/ogrsf_frmts/wfs/ogrwfsjoinlayer.cpp index 6c0c55e7cc29..f024b1d02b76 100644 --- a/ogr/ogrsf_frmts/wfs/ogrwfsjoinlayer.cpp +++ b/ogr/ogrsf_frmts/wfs/ogrwfsjoinlayer.cpp @@ -447,13 +447,13 @@ GDALDataset *OGRWFSJoinLayer::FetchGetFeature() const char *const apszAllowedDrivers[] = {"GML", nullptr}; const char *apszOpenOptions[2] = {nullptr, nullptr}; apszOpenOptions[0] = CPLSPrintf("XSD=%s", osXSDFileName.c_str()); - auto poGML_DS = std::unique_ptr( - GDALDataset::Open(pszStreamingName, GDAL_OF_VECTOR, - apszAllowedDrivers, apszOpenOptions, nullptr)); - if (poGML_DS && poGML_DS->GetLayerCount() != 0) + GDALDataset *poGML_DS = (GDALDataset *)GDALOpenEx( + pszStreamingName, GDAL_OF_VECTOR, apszAllowedDrivers, + apszOpenOptions, nullptr); + if (poGML_DS) { // bStreamingDS = true; - return poGML_DS.release(); + return poGML_DS; } /* In case of failure, read directly the content to examine */ @@ -516,22 +516,30 @@ GDALDataset *OGRWFSJoinLayer::FetchGetFeature() CPLHTTPDestroyResult(psResult); - auto l_poDS = std::unique_ptr(GDALDataset::Open( - osTmpFileName.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr)); - if (l_poDS && l_poDS->GetLayerCount() != 0) + OGRDataSource *l_poDS = + (OGRDataSource *)OGROpen(osTmpFileName, FALSE, nullptr); + if (l_poDS == nullptr) { - return l_poDS.release(); + if (strstr((const char *)pabyData, " 1000) + pabyData[1000] = 0; + CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s", + pabyData); + } + return nullptr; } - if (strstr((const char *)pabyData, "GetLayer(0); + if (poLayer == nullptr) { - if (nDataLen > 1000) - pabyData[1000] = 0; - CPLError(CE_Failure, CPLE_AppDefined, "Error: cannot parse %s", - pabyData); + OGRDataSource::DestroyDataSource(l_poDS); + return nullptr; } - return nullptr; + + return l_poDS; } /************************************************************************/ From 01c76e05cffe4181bd814f507364629b5f3742fb Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 19:00:44 +0200 Subject: [PATCH 0126/1119] NAS: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/ogr/ogr_nas.py | 21 +++++++++++++++++++++ doc/source/drivers/vector/nas.rst | 3 +++ ogr/ogrsf_frmts/nas/ogrnasdriver.cpp | 28 +++++++++++++++++++--------- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/autotest/ogr/ogr_nas.py b/autotest/ogr/ogr_nas.py index 35a4dc2e9778..c548635f9706 100755 --- a/autotest/ogr/ogr_nas.py +++ b/autotest/ogr/ogr_nas.py @@ -283,3 +283,24 @@ def test_ogr_nas_5(): os.remove("data/nas/replace_nas.gfs") except OSError: pass + + +############################################################################### +# Test force opening a NAS file + + +def test_ogr_nas_force_opening(tmp_vsimem): + + filename = str(tmp_vsimem / "test.xml") + + prolog = '' + with gdaltest.vsi_open(filename, "wb") as f: + with open("data/nas/replace_nas.xml", "rb") as fsrc: + f.write(fsrc.read(len(prolog)) + b" " * (1000 * 1000) + fsrc.read()) # '<' + + with pytest.raises(Exception): + gdal.OpenEx(filename) + + with gdal.quiet_errors(): + ds = gdal.OpenEx(filename, allowed_drivers=["NAS"]) + assert ds.GetDriver().GetDescription() == "NAS" diff --git a/doc/source/drivers/vector/nas.rst b/doc/source/drivers/vector/nas.rst index e64f09b10a5e..ccb8ac81536c 100644 --- a/doc/source/drivers/vector/nas.rst +++ b/doc/source/drivers/vector/nas.rst @@ -27,6 +27,9 @@ Starting with GDAL 3.7, defining the NAS_GFS_TEMPLATE configuration option is required for the NAS driver to open a file. It may be set to the empty string to mean that the driver should try to establish the schema of the file from its content, but using one of templates mentioned below is recommended. +Alternatively, starting with GDAL 3.10, specifying the ``-if NAS`` option to command line utilities +accepting it, or ``NAS`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed filename. The GFS templates and PostgreSQL schemas are part of `norGIS ALKIS-Import `__ (also featuring a shell script and diff --git a/ogr/ogrsf_frmts/nas/ogrnasdriver.cpp b/ogr/ogrsf_frmts/nas/ogrnasdriver.cpp index b21546afe9ea..6659d57f8f50 100644 --- a/ogr/ogrsf_frmts/nas/ogrnasdriver.cpp +++ b/ogr/ogrsf_frmts/nas/ogrnasdriver.cpp @@ -49,26 +49,36 @@ static int OGRNASDriverIdentify(GDALOpenInfo *poOpenInfo) /* -------------------------------------------------------------------- */ // Used to skip to actual beginning of XML data - // const char* szPtr = (const char*)poOpenInfo->pabyHeader; - const char *szPtr = reinterpret_cast(poOpenInfo->pabyHeader); + const char *pszPtr = reinterpret_cast(poOpenInfo->pabyHeader); - if (((unsigned char)szPtr[0] == 0xEF) && - ((unsigned char)szPtr[1] == 0xBB) && ((unsigned char)szPtr[2] == 0xBF)) + // Skip UTF-8 BOM + if (poOpenInfo->nHeaderBytes > 3 && + memcmp(poOpenInfo->pabyHeader, "\xEF\xBB\xBF", 3) == 0) { - szPtr += 3; + pszPtr += 3; } + // Skip spaces + while (*pszPtr && std::isspace(static_cast(*pszPtr))) + ++pszPtr; + /* -------------------------------------------------------------------- */ /* Here, we expect the opening chevrons of NAS tree root element */ /* -------------------------------------------------------------------- */ - if (szPtr[0] != '<') + if (pszPtr[0] != '<') return FALSE; + if (poOpenInfo->IsSingleAllowedDriver("NAS")) + return TRUE; + + // TryToIngest() invalidates above pszPtr + pszPtr = nullptr; + CPL_IGNORE_RET_VAL(pszPtr); if (!poOpenInfo->TryToIngest(8192)) return FALSE; - szPtr = (const char *)poOpenInfo->pabyHeader; + pszPtr = reinterpret_cast(poOpenInfo->pabyHeader); - if (strstr(szPtr, "opengis.net/gml") == nullptr) + if (strstr(pszPtr, "opengis.net/gml") == nullptr) return FALSE; char **papszIndicators = CSLTokenizeStringComplex( @@ -79,7 +89,7 @@ static int OGRNASDriverIdentify(GDALOpenInfo *poOpenInfo) bool bFound = false; for (int i = 0; papszIndicators[i] && !bFound; i++) { - bFound = strstr(szPtr, papszIndicators[i]) != nullptr; + bFound = strstr(pszPtr, papszIndicators[i]) != nullptr; } CSLDestroy(papszIndicators); From 36054888a096d5e74fc30d3a29e565c98cc1b8d0 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 19:09:30 +0200 Subject: [PATCH 0127/1119] GMLAS: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/ogr/ogr_gmlas.py | 38 ++++++++++---------- doc/source/drivers/vector/gmlas.rst | 10 ++++-- ogr/ogrsf_frmts/gmlas/ogrgmlasdrivercore.cpp | 25 ++++++++++++- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/autotest/ogr/ogr_gmlas.py b/autotest/ogr/ogr_gmlas.py index 13492e7ea21f..353a849d83e6 100755 --- a/autotest/ogr/ogr_gmlas.py +++ b/autotest/ogr/ogr_gmlas.py @@ -57,25 +57,18 @@ def module_disable_exceptions(): @pytest.fixture(autouse=True, scope="module") def startup_and_cleanup(): - gdal.SetConfigOption("GMLAS_WARN_UNEXPECTED", "YES") - # FileGDB embedded libxml2 cause random crashes with CPLValidateXML() use of external libxml2 - old_val_GDAL_XML_VALIDATION = gdal.GetConfigOption("GDAL_XML_VALIDATION") - if ( - ogr.GetDriverByName("FileGDB") is not None - and old_val_GDAL_XML_VALIDATION is None - ): - gdal.SetConfigOption("GDAL_XML_VALIDATION", "NO") + # hence GDAL_XML_VALIDATION=NO - yield + with gdaltest.config_options( + {"GMLAS_WARN_UNEXPECTED": "YES", "GDAL_XML_VALIDATION": "NO"} + ): + yield files = gdal.ReadDir("/vsimem/") if files is not None: print("Remaining files: " + str(files)) - gdal.SetConfigOption("GMLAS_WARN_UNEXPECTED", None) - gdal.SetConfigOption("GDAL_XML_VALIDATION", old_val_GDAL_XML_VALIDATION) - ############################################################################### @@ -620,12 +613,10 @@ def test_ogr_gmlas_validate(): ds = gdal.OpenEx("GMLAS:data/gmlas/gmlas_validate.xml") assert ds is not None myhandler = MyHandler() - gdal.PushErrorHandler(myhandler.error_handler) - gdal.SetConfigOption("GMLAS_WARN_UNEXPECTED", None) - lyr = ds.GetLayer(0) - lyr.GetFeatureCount() - gdal.SetConfigOption("GMLAS_WARN_UNEXPECTED", "YES") - gdal.PopErrorHandler() + with gdaltest.error_handler(myhandler.error_handler): + with gdal.config_option("GMLAS_WARN_UNEXPECTED", "NO"): + lyr = ds.GetLayer(0) + lyr.GetFeatureCount() assert not myhandler.error_list ds = gdal.OpenEx("GMLAS:data/gmlas/gmlas_validate.xml") @@ -3470,3 +3461,14 @@ def test_ogr_gmlas_bugfix_sf_2371(): ds = gdal.OpenEx("GMLAS:data/gmlas/citygml_empty_lod1.gml") lyr = ds.GetLayerByName("address1") assert lyr.GetFeatureCount() == 0 + + +############################################################################### +# Test force opening a GMLAS file + + +@gdaltest.enable_exceptions() +def test_ogr_gmlas_force_opening(tmp_vsimem): + + ds = gdal.OpenEx("data/gmlas/gmlas_test1.xml", allowed_drivers=["GMLAS"]) + assert ds.GetDriver().GetDescription() == "GMLAS" diff --git a/doc/source/drivers/vector/gmlas.rst b/doc/source/drivers/vector/gmlas.rst index c728ea540842..e4188f58c410 100644 --- a/doc/source/drivers/vector/gmlas.rst +++ b/doc/source/drivers/vector/gmlas.rst @@ -33,11 +33,15 @@ Driver capabilities Opening syntax -------------- -The connection string is GMLAS:/path/to/the.gml. Note the GMLAS: prefix. +The connection string is GMLAS:/path/to/the.gml. Note the ``GMLAS:`` prefix. If this prefix it is omitted, then the GML driver is likely to be used. -It is also possible to only used "GMLAS:" as the connection string, but -in that case the schemas must be explicitly provided with the XSD open +Alternatively, starting with GDAL 3.10, specifying the ``-if GMLAS`` option to command line utilities +accepting it, or ``GMLAS`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed filename. + +It is also possible to only used ``GMLAS:`` as the connection string, but +in that case the schemas must be explicitly provided with the ``XSD`` open option. Mapping of XML structure to OGR layers and fields diff --git a/ogr/ogrsf_frmts/gmlas/ogrgmlasdrivercore.cpp b/ogr/ogrsf_frmts/gmlas/ogrgmlasdrivercore.cpp index 3352ac090b8a..fa61dbeb8c6d 100644 --- a/ogr/ogrsf_frmts/gmlas/ogrgmlasdrivercore.cpp +++ b/ogr/ogrsf_frmts/gmlas/ogrgmlasdrivercore.cpp @@ -40,7 +40,30 @@ int OGRGMLASDriverIdentify(GDALOpenInfo *poOpenInfo) { - return STARTS_WITH_CI(poOpenInfo->pszFilename, "GMLAS:"); + if (STARTS_WITH_CI(poOpenInfo->pszFilename, "GMLAS:")) + return true; + + if (poOpenInfo->IsSingleAllowedDriver("GMLAS")) + { + const char *pszPtr = + reinterpret_cast(poOpenInfo->pabyHeader); + + // Skip UTF-8 BOM + if (poOpenInfo->nHeaderBytes > 3 && + memcmp(poOpenInfo->pabyHeader, "\xEF\xBB\xBF", 3) == 0) + { + pszPtr += 3; + } + + // Skip spaces + while (*pszPtr && std::isspace(static_cast(*pszPtr))) + ++pszPtr; + + // Here, we expect the opening chevrons of GML tree root element */ + return pszPtr[0] == '<'; + } + + return false; } /************************************************************************/ From b0f334fd628f5fb4ced0213dab5bda4c855e7a0a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 19:19:43 +0200 Subject: [PATCH 0128/1119] GTI: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/gdrivers/gti.py | 8 +++++-- doc/source/drivers/raster/gti.rst | 5 ++++ frmts/vrt/gdaltileindexdataset.cpp | 37 ++++++++++++++++++++++-------- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/autotest/gdrivers/gti.py b/autotest/gdrivers/gti.py index c8b674b65c83..13066179ef91 100755 --- a/autotest/gdrivers/gti.py +++ b/autotest/gdrivers/gti.py @@ -115,13 +115,17 @@ def check_basic( def test_gti_no_metadata(tmp_vsimem): - index_filename = str(tmp_vsimem / "index.gti.gpkg") + index_filename = str(tmp_vsimem / "index.gpkg") src_ds = gdal.Open(os.path.join(os.getcwd(), "data", "byte.tif")) index_ds, _ = create_basic_tileindex(index_filename, src_ds) del index_ds - vrt_ds = gdal.Open(index_filename) + with pytest.raises(Exception): + gdal.Open(index_filename) + + vrt_ds = gdal.OpenEx(index_filename, allowed_drivers=["GTI"]) + assert vrt_ds.GetDriver().GetDescription() == "GTI" check_basic(vrt_ds, src_ds) assert ( vrt_ds.GetMetadataItem("SCANNED_ONE_FEATURE_AT_OPENING", "__DEBUG__") == "YES" diff --git a/doc/source/drivers/raster/gti.rst b/doc/source/drivers/raster/gti.rst index 1304fb74b3b1..a41a41b9c02b 100644 --- a/doc/source/drivers/raster/gti.rst +++ b/doc/source/drivers/raster/gti.rst @@ -55,6 +55,11 @@ The GTI driver accepts different types of connection strings: For example: ``tileindex.gti.gpkg`` + Starting with GDAL 3.10, specifying the ``-if GTI`` option to command line utilities + accepting it, or ``GTI`` as the only value of the ``papszAllowedDrivers`` of + :cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed filename + if its extension is just ``.gpkg`` or ``.fgb``. + * any vector file in a GDAL supported format, with its filename (or connection string prefixed with ``GTI:`` diff --git a/frmts/vrt/gdaltileindexdataset.cpp b/frmts/vrt/gdaltileindexdataset.cpp index d4099ef4ef0d..122e7f782b98 100644 --- a/frmts/vrt/gdaltileindexdataset.cpp +++ b/frmts/vrt/gdaltileindexdataset.cpp @@ -1965,20 +1965,39 @@ static int GDALTileIndexDatasetIdentify(GDALOpenInfo *poOpenInfo) if (poOpenInfo->nHeaderBytes >= 100 && STARTS_WITH(reinterpret_cast(poOpenInfo->pabyHeader), - "SQLite format 3") && - ENDS_WITH_CI(poOpenInfo->pszFilename, ".gti.gpkg") && - !STARTS_WITH(poOpenInfo->pszFilename, "GPKG:")) + "SQLite format 3")) { - // Most likely handled by GTI driver, but we can't be sure - return GDAL_IDENTIFY_UNKNOWN; + if (ENDS_WITH_CI(poOpenInfo->pszFilename, ".gti.gpkg")) + { + // Most likely handled by GTI driver, but we can't be sure + return GDAL_IDENTIFY_UNKNOWN; + } + else if (poOpenInfo->IsSingleAllowedDriver("GTI") && + EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "gpkg")) + { + return true; + } } - return poOpenInfo->nHeaderBytes > 0 && - (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 && - (strstr(reinterpret_cast(poOpenInfo->pabyHeader), + if (poOpenInfo->nHeaderBytes > 0 && + (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0) + { + if (strstr(reinterpret_cast(poOpenInfo->pabyHeader), "pszFilename, ".gti.fgb") || - ENDS_WITH_CI(poOpenInfo->pszFilename, ".gti.parquet")); + ENDS_WITH_CI(poOpenInfo->pszFilename, ".gti.parquet")) + { + return true; + } + else if (poOpenInfo->IsSingleAllowedDriver("GTI") && + (EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "fgb") || + EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "parquet"))) + { + return true; + } + } + + return false; } /************************************************************************/ From bfd8570beeeb04a252633660c72ddbea5adff01f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 11 Jun 2024 00:15:09 +0200 Subject: [PATCH 0129/1119] GDALDriver::QuietDeleteForCreateCopy(): do not set error state when attempting to open datasets --- gcore/gdaldriver.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gcore/gdaldriver.cpp b/gcore/gdaldriver.cpp index e44cdeae2904..1b1c85ba8d67 100644 --- a/gcore/gdaldriver.cpp +++ b/gcore/gdaldriver.cpp @@ -1018,7 +1018,7 @@ CPLErr GDALDriver::QuietDeleteForCreateCopy(const char *pszFilename, */ std::set oSetExistingDestFiles; { - CPLPushErrorHandler(CPLQuietErrorHandler); + CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); const char *const apszAllowedDrivers[] = {GetDescription(), nullptr}; auto poExistingOutputDS = @@ -1033,7 +1033,6 @@ CPLErr GDALDriver::QuietDeleteForCreateCopy(const char *pszFilename, CPLString(pszFileInList).replaceAll('\\', '/')); } } - CPLPopErrorHandler(); } /* -------------------------------------------------------------------- @@ -1045,7 +1044,7 @@ CPLErr GDALDriver::QuietDeleteForCreateCopy(const char *pszFilename, std::set oSetExistingDestFilesFoundInSource; if (!oSetExistingDestFiles.empty()) { - CPLPushErrorHandler(CPLQuietErrorHandler); + CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); // We need to reopen in a temporary dataset for the particular // case of overwritten a .tif.ovr file from a .tif // If we probe the file list of the .tif, it will then open the @@ -1071,7 +1070,6 @@ CPLErr GDALDriver::QuietDeleteForCreateCopy(const char *pszFilename, } } } - CPLPopErrorHandler(); } // If the source file(s) and the dest one share some files in From 4cf95de59ceedc01c9fe3df4079f9c8e6465732b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 20:43:36 +0200 Subject: [PATCH 0130/1119] TileDB: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/gdrivers/tiledb_read.py | 14 +++++++++++++- frmts/tiledb/tiledbcommon.cpp | 5 +++++ frmts/tiledb/tiledbdrivercore.cpp | 5 +++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/autotest/gdrivers/tiledb_read.py b/autotest/gdrivers/tiledb_read.py index 64724a87d8d9..2f4607830304 100755 --- a/autotest/gdrivers/tiledb_read.py +++ b/autotest/gdrivers/tiledb_read.py @@ -32,8 +32,20 @@ import gdaltest import pytest +from osgeo import gdal + +pytestmark = pytest.mark.require_driver("TileDB") + -@pytest.mark.require_driver("TileDB") def test_tiledb_open(): ut = gdaltest.GDALTest("TileDB", "tiledb_array", 1, 4857) ut.testOpen() + + +############################################################################### + + +def test_tiledb_force_identify(): + + drv = gdal.IdentifyDriverEx("data/tiledb_array", allowed_drivers=["TileDB"]) + assert drv is not None diff --git a/frmts/tiledb/tiledbcommon.cpp b/frmts/tiledb/tiledbcommon.cpp index 5cee01eaab64..120276dcc08d 100644 --- a/frmts/tiledb/tiledbcommon.cpp +++ b/frmts/tiledb/tiledbcommon.cpp @@ -123,6 +123,11 @@ int TileDBDataset::Identify(GDALOpenInfo *poOpenInfo) return TRUE; } + if (poOpenInfo->IsSingleAllowedDriver("TileDB")) + { + return TRUE; + } + try { const char *pszConfig = diff --git a/frmts/tiledb/tiledbdrivercore.cpp b/frmts/tiledb/tiledbdrivercore.cpp index 8e5d234d6e34..4737945ff783 100644 --- a/frmts/tiledb/tiledbdrivercore.cpp +++ b/frmts/tiledb/tiledbdrivercore.cpp @@ -43,6 +43,11 @@ static int TileDBDriverIdentifySimplified(GDALOpenInfo *poOpenInfo) return TRUE; } + if (poOpenInfo->IsSingleAllowedDriver("TileDB")) + { + return TRUE; + } + const char *pszConfig = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "TILEDB_CONFIG"); From 64b29295e56efdd89666419a1b66855564372274 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 21:30:17 +0200 Subject: [PATCH 0131/1119] OGCAPI: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/gdrivers/ogcapi.py | 3 ++- doc/source/drivers/raster/ogcapi.rst | 5 +++++ frmts/ogcapi/gdalogcapidataset.cpp | 15 ++++++++++++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/autotest/gdrivers/ogcapi.py b/autotest/gdrivers/ogcapi.py index f12a239ec7ed..d1023238edc0 100644 --- a/autotest/gdrivers/ogcapi.py +++ b/autotest/gdrivers/ogcapi.py @@ -455,7 +455,7 @@ def test_ogc_api_raster_tiles(): def test_ogc_api_raster_tiles_format(image_format, raster_count, statistics): ds = gdal.OpenEx( - f"OGCAPI:http://127.0.0.1:{gdaltest.webserver_port}/fakeogcapi/collections/blueMarble", + f"http://127.0.0.1:{gdaltest.webserver_port}/fakeogcapi/collections/blueMarble", gdal.OF_RASTER, open_options=[ "API=TILES", @@ -463,6 +463,7 @@ def test_ogc_api_raster_tiles_format(image_format, raster_count, statistics): "TILEMATRIXSET=WorldMercatorWGS84Quad", f"IMAGE_FORMAT={image_format}", ], + allowed_drivers=["OGCAPI"], ) assert ds is not None diff --git a/doc/source/drivers/raster/ogcapi.rst b/doc/source/drivers/raster/ogcapi.rst index e1feb51f4e39..bee04ec1c5bd 100644 --- a/doc/source/drivers/raster/ogcapi.rst +++ b/doc/source/drivers/raster/ogcapi.rst @@ -51,6 +51,11 @@ The driver supports opening by: - passing a string "OGCAPI:{url}" where {url} is the URL to a OGC API landing page In that case the driver will return subdatasets with the different collections. + Starting with GDAL 3.10, specifying the ``-if OGCAPI`` option to command line utilities + accepting it, or ``OGCAPI`` as the only value of the ``papszAllowedDrivers`` of + :cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed + URL, without the ``OGCAPI:`` prefix. + - passing a string "OGCAPI:{url}" where {url} is the URL to a OGC API collection description diff --git a/frmts/ogcapi/gdalogcapidataset.cpp b/frmts/ogcapi/gdalogcapidataset.cpp index 6ae821e623b1..fdf43973861f 100644 --- a/frmts/ogcapi/gdalogcapidataset.cpp +++ b/frmts/ogcapi/gdalogcapidataset.cpp @@ -664,6 +664,10 @@ int OGCAPIDataset::Identify(GDALOpenInfo *poOpenInfo) return TRUE; if (EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "moaw")) return TRUE; + if (poOpenInfo->IsSingleAllowedDriver("OGCAPI")) + { + return TRUE; + } return FALSE; } @@ -999,9 +1003,12 @@ bool OGCAPIDataset::InitFromCollection(GDALOpenInfo *poOpenInfo, bool OGCAPIDataset::InitFromURL(GDALOpenInfo *poOpenInfo) { - CPLAssert(STARTS_WITH_CI(poOpenInfo->pszFilename, "OGCAPI:")); + const char *pszInitialURL = + STARTS_WITH_CI(poOpenInfo->pszFilename, "OGCAPI:") + ? poOpenInfo->pszFilename + strlen("OGCAPI:") + : poOpenInfo->pszFilename; CPLJSONDocument oDoc; - CPLString osURL(poOpenInfo->pszFilename + strlen("OGCAPI:")); + CPLString osURL(pszInitialURL); if (!DownloadJSon(osURL, oDoc)) return false; @@ -2881,7 +2888,9 @@ GDALDataset *OGCAPIDataset::Open(GDALOpenInfo *poOpenInfo) if (!Identify(poOpenInfo)) return nullptr; auto poDS = std::make_unique(); - if (STARTS_WITH_CI(poOpenInfo->pszFilename, "OGCAPI:")) + if (STARTS_WITH_CI(poOpenInfo->pszFilename, "OGCAPI:") || + STARTS_WITH(poOpenInfo->pszFilename, "http://") || + STARTS_WITH(poOpenInfo->pszFilename, "https://")) { if (!poDS->InitFromURL(poOpenInfo)) return nullptr; From 06e3fb67bb6eecff78ac2878e4ab318ecd7d849d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 6 Jun 2024 22:16:48 +0200 Subject: [PATCH 0132/1119] CSV: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/ogr/ogr_csv.py | 19 +++++++++++++++++ doc/source/drivers/vector/csv.rst | 5 +++++ ogr/ogrsf_frmts/csv/ogr_csv.h | 10 ++++----- ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp | 20 ++++++++++-------- ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp | 27 +++++++++++++++--------- ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp | 4 ++-- 6 files changed, 59 insertions(+), 26 deletions(-) diff --git a/autotest/ogr/ogr_csv.py b/autotest/ogr/ogr_csv.py index 80c536399840..22d9111f4cc7 100755 --- a/autotest/ogr/ogr_csv.py +++ b/autotest/ogr/ogr_csv.py @@ -3114,6 +3114,25 @@ def test_ogr_csv_invalid_geometry_option(tmp_vsimem): ds.CreateLayer("test", geom_type=ogr.wkbLineString25D, options=["GEOMETRY=foo"]) +############################################################################### +# Test force opening a CSV file + + +@gdaltest.enable_exceptions() +def test_ogr_csv_force_opening(tmp_vsimem): + + filename = str(tmp_vsimem / "test.bin") + + with gdaltest.vsi_open(filename, "wb") as fdest: + fdest.write(b"foo\nbar\n") + + with pytest.raises(Exception): + gdal.OpenEx(filename) + + ds = gdal.OpenEx(filename, allowed_drivers=["CSV"]) + assert ds.GetDriver().GetDescription() == "CSV" + + ############################################################################### diff --git a/doc/source/drivers/vector/csv.rst b/doc/source/drivers/vector/csv.rst index 709f0dd479d8..d0f78a53990a 100644 --- a/doc/source/drivers/vector/csv.rst +++ b/doc/source/drivers/vector/csv.rst @@ -25,6 +25,11 @@ For files structured as CSV, but not ending with the ".csv" extension, the 'CSV:' prefix can be added before the filename to force loading by the CSV driver. +Starting with GDAL 3.10, specifying the ``-if CSV`` option to command line utilities +accepting it, or ``CSV`` as the only value of the ``papszAllowedDrivers`` of +:cpp:func:`GDALOpenEx`, also forces the driver to recognize the passed +filename, without the ``CSV:`` prefix. + The OGR CSV driver supports reading and writing. Because the CSV format has variable length text lines, reading is done sequentially. Reading features in random order will generally be very slow. OGR CSV layer diff --git a/ogr/ogrsf_frmts/csv/ogr_csv.h b/ogr/ogrsf_frmts/csv/ogr_csv.h index 7bac0102d471..a741b08cab67 100644 --- a/ogr/ogrsf_frmts/csv/ogr_csv.h +++ b/ogr/ogrsf_frmts/csv/ogr_csv.h @@ -136,7 +136,7 @@ class OGRCSVLayer final : public IOGRCSVLayer, public OGRLayer GIntBig nTotalFeatures; - char **AutodetectFieldTypes(char **papszOpenOptions, int nFieldCount); + char **AutodetectFieldTypes(CSLConstList papszOpenOptions, int nFieldCount); bool bWarningBadTypeOrWidth; bool bKeepSourceColumns; @@ -222,7 +222,7 @@ class OGRCSVLayer final : public IOGRCSVLayer, public OGRLayer void BuildFeatureDefn(const char *pszNfdcGeomField = nullptr, const char *pszGeonamesGeomFieldPrefix = nullptr, - char **papszOpenOptions = nullptr); + CSLConstList papszOpenOptions = nullptr); void ResetReading() override; OGRFeature *GetNextFeature() override; @@ -295,9 +295,9 @@ class OGRCSVDataSource final : public OGRDataSource OGRCSVDataSource(); virtual ~OGRCSVDataSource() override; - int Open(const char *pszFilename, int bUpdate, int bForceAccept, - char **papszOpenOptions = nullptr); - bool OpenTable(const char *pszFilename, char **papszOpenOptions, + bool Open(const char *pszFilename, bool bUpdate, bool bForceOpen, + CSLConstList papszOpenOptions, bool bSingleDriver); + bool OpenTable(const char *pszFilename, CSLConstList papszOpenOptions, const char *pszNfdcRunwaysGeomField = nullptr, const char *pszGeonamesGeomFieldPrefix = nullptr); diff --git a/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp b/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp index eeb267197098..b012e48786a8 100644 --- a/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp +++ b/ogr/ogrsf_frmts/csv/ogrcsvdatasource.cpp @@ -59,7 +59,7 @@ class OGRCSVEditableLayerSynchronizer final public: OGRCSVEditableLayerSynchronizer(OGRCSVLayer *poCSVLayer, - char **papszOpenOptions) + CSLConstList papszOpenOptions) : m_poCSVLayer(poCSVLayer), m_papszOpenOptions(CSLDuplicate(papszOpenOptions)) { @@ -317,7 +317,7 @@ class OGRCSVEditableLayer final : public IOGRCSVLayer, public OGREditableLayer std::set m_oSetFields; public: - OGRCSVEditableLayer(OGRCSVLayer *poCSVLayer, char **papszOpenOptions); + OGRCSVEditableLayer(OGRCSVLayer *poCSVLayer, CSLConstList papszOpenOptions); OGRLayer *GetLayer() override { @@ -344,7 +344,7 @@ class OGRCSVEditableLayer final : public IOGRCSVLayer, public OGREditableLayer /************************************************************************/ OGRCSVEditableLayer::OGRCSVEditableLayer(OGRCSVLayer *poCSVLayer, - char **papszOpenOptions) + CSLConstList papszOpenOptions) : OGREditableLayer( poCSVLayer, true, new OGRCSVEditableLayerSynchronizer(poCSVLayer, papszOpenOptions), @@ -510,8 +510,9 @@ CPLString OGRCSVDataSource::GetRealExtension(CPLString osFilename) /* Open() */ /************************************************************************/ -int OGRCSVDataSource::Open(const char *pszFilename, int bUpdateIn, - int bForceOpen, char **papszOpenOptionsIn) +bool OGRCSVDataSource::Open(const char *pszFilename, bool bUpdateIn, + bool bForceOpen, CSLConstList papszOpenOptionsIn, + bool bSingleDriver) { pszName = CPLStrdup(pszFilename); @@ -528,11 +529,12 @@ int OGRCSVDataSource::Open(const char *pszFilename, int bUpdateIn, const CPLString osBaseFilename = CPLGetFilename(pszFilename); const CPLString osExt = GetRealExtension(osFilename); - bool bIgnoreExtension = STARTS_WITH_CI(osFilename, "CSV:"); + bool bIgnoreExtension = bSingleDriver; bool bUSGeonamesFile = false; - if (bIgnoreExtension) + if (STARTS_WITH_CI(osFilename, "CSV:")) { - osFilename = osFilename + 4; + bIgnoreExtension = true; + osFilename = osFilename.substr(strlen("CSV:")); } // Those are *not* real .XLS files, but text file with tab as column @@ -736,7 +738,7 @@ int OGRCSVDataSource::Open(const char *pszFilename, int bUpdateIn, /************************************************************************/ bool OGRCSVDataSource::OpenTable(const char *pszFilename, - char **papszOpenOptionsIn, + CSLConstList papszOpenOptionsIn, const char *pszNfdcRunwaysGeomField, const char *pszGeonamesGeomFieldPrefix) diff --git a/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp b/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp index 74fb83eca498..d50068323c7f 100644 --- a/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp +++ b/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp @@ -56,6 +56,9 @@ static int OGRCSVDriverIdentify(GDALOpenInfo *poOpenInfo) { if (poOpenInfo->fpL != nullptr) { + if (poOpenInfo->IsSingleAllowedDriver("CSV")) + return TRUE; + const CPLString osBaseFilename = CPLGetFilename(poOpenInfo->pszFilename); const CPLString osExt = @@ -114,6 +117,9 @@ static int OGRCSVDriverIdentify(GDALOpenInfo *poOpenInfo) } else if (poOpenInfo->bIsDirectory) { + if (poOpenInfo->IsSingleAllowedDriver("CSV")) + return TRUE; + return -1; // Unsure. } @@ -160,13 +166,13 @@ static GDALDataset *OGRCSVDriverOpen(GDALOpenInfo *poOpenInfo) } } - OGRCSVDataSource *poDS = new OGRCSVDataSource(); + auto poDS = std::make_unique(); if (!poDS->Open(poOpenInfo->pszFilename, poOpenInfo->eAccess == GA_Update, - FALSE, poOpenInfo->papszOpenOptions)) + false, poOpenInfo->papszOpenOptions, + poOpenInfo->IsSingleAllowedDriver("CSV"))) { - delete poDS; - poDS = nullptr; + poDS.reset(); } if (poOpenInfo->eAccess == GA_Update && poDS != nullptr) @@ -176,11 +182,11 @@ static GDALDataset *OGRCSVDriverOpen(GDALOpenInfo *poOpenInfo) poMap = new std::map(); if (poMap->find(poOpenInfo->pszFilename) == poMap->end()) { - (*poMap)[poOpenInfo->pszFilename] = poDS; + (*poMap)[poOpenInfo->pszFilename] = poDS.get(); } } - return poDS; + return poDS.release(); } /************************************************************************/ @@ -238,15 +244,16 @@ OGRCSVDriverCreate(const char *pszName, CPL_UNUSED int nBands, } // Force it to open as a datasource. - OGRCSVDataSource *poDS = new OGRCSVDataSource(); + auto poDS = std::make_unique(); if (EQUAL(CPLGetExtension(pszName), "csv")) { poDS->CreateForSingleFile(osDirName, pszName); } - else if (!poDS->Open(osDirName, TRUE, TRUE)) + else if (!poDS->Open(osDirName, /* bUpdate = */ true, + /* bForceAccept = */ true, nullptr, + /* bSingleDriver = */ true)) { - delete poDS; return nullptr; } @@ -254,7 +261,7 @@ OGRCSVDriverCreate(const char *pszName, CPL_UNUSED int nBands, if (pszGeometry != nullptr && EQUAL(pszGeometry, "AS_WKT")) poDS->EnableGeometryFields(); - return poDS; + return poDS.release(); } /************************************************************************/ diff --git a/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp b/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp index 8dac065f30d3..cee5cbcd4916 100644 --- a/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp +++ b/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp @@ -153,7 +153,7 @@ bool OGRCSVLayer::Matches(const char *pszFieldName, char **papszPossibleNames) void OGRCSVLayer::BuildFeatureDefn(const char *pszNfdcGeomField, const char *pszGeonamesGeomFieldPrefix, - char **papszOpenOptions) + CSLConstList papszOpenOptions) { bMergeDelimiter = CPLFetchBool(papszOpenOptions, "MERGE_SEPARATOR", false); bEmptyStringNull = @@ -903,7 +903,7 @@ static bool OGRCSVIsFalse(const char *pszStr) /* AutodetectFieldTypes() */ /************************************************************************/ -char **OGRCSVLayer::AutodetectFieldTypes(char **papszOpenOptions, +char **OGRCSVLayer::AutodetectFieldTypes(CSLConstList papszOpenOptions, int nFieldCount) { const bool bStreaming = From 9f7bea57dbd84093934d197d7601dba77a72d1de Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 10 Jun 2024 15:39:51 +0200 Subject: [PATCH 0133/1119] XYZ: relax identification checks when papszAllowedDrivers[] contains only the driver name --- frmts/xyz/xyzdataset.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/frmts/xyz/xyzdataset.cpp b/frmts/xyz/xyzdataset.cpp index 645f4e832f02..2112430f457f 100644 --- a/frmts/xyz/xyzdataset.cpp +++ b/frmts/xyz/xyzdataset.cpp @@ -723,13 +723,14 @@ int XYZDataset::IdentifyEx(GDALOpenInfo *poOpenInfo, int &bHasHeaderLine, nCommentLineCount = 0; CPLString osFilename(poOpenInfo->pszFilename); - if (EQUAL(CPLGetExtension(osFilename), "GRA")) + if (EQUAL(CPLGetExtension(osFilename), "GRA") && + !poOpenInfo->IsSingleAllowedDriver("XYZ")) { // IGNFHeightASCIIGRID .GRA return FALSE; } - GDALOpenInfo *poOpenInfoToDelete = nullptr; + std::unique_ptr poOpenInfoToDelete; // keep in this scope /* GZipped .xyz files are common, so automagically open them */ /* if the /vsigzip/ has not been explicitly passed */ if (strlen(poOpenInfo->pszFilename) > 6 && @@ -739,13 +740,13 @@ int XYZDataset::IdentifyEx(GDALOpenInfo *poOpenInfo, int &bHasHeaderLine, { osFilename = "/vsigzip/"; osFilename += poOpenInfo->pszFilename; - poOpenInfo = poOpenInfoToDelete = new GDALOpenInfo( + poOpenInfoToDelete = std::make_unique( osFilename.c_str(), GA_ReadOnly, poOpenInfo->GetSiblingFiles()); + poOpenInfo = poOpenInfoToDelete.get(); } if (poOpenInfo->nHeaderBytes == 0) { - delete poOpenInfoToDelete; return FALSE; } @@ -755,10 +756,10 @@ int XYZDataset::IdentifyEx(GDALOpenInfo *poOpenInfo, int &bHasHeaderLine, const char *pszData = reinterpret_cast(poOpenInfo->pabyHeader); - if (poOpenInfo->nHeaderBytes >= 4 && STARTS_WITH(pszData, "DSAA")) + if (poOpenInfo->nHeaderBytes >= 4 && STARTS_WITH(pszData, "DSAA") && + !poOpenInfo->IsSingleAllowedDriver("XYZ")) { // Do not match GSAG datasets - delete poOpenInfoToDelete; return FALSE; } @@ -806,7 +807,6 @@ int XYZDataset::IdentifyEx(GDALOpenInfo *poOpenInfo, int &bHasHeaderLine, bHasHeaderLine = TRUE; else { - delete poOpenInfoToDelete; return FALSE; } } @@ -837,7 +837,6 @@ int XYZDataset::IdentifyEx(GDALOpenInfo *poOpenInfo, int &bHasHeaderLine, CSLDestroy(papszTokens); if (nXIndex >= 0 && nYIndex >= 0 && nZIndex >= 0) { - delete poOpenInfoToDelete; return TRUE; } } @@ -878,12 +877,10 @@ int XYZDataset::IdentifyEx(GDALOpenInfo *poOpenInfo, int &bHasHeaderLine, } else { - delete poOpenInfoToDelete; return FALSE; } } - delete poOpenInfoToDelete; return bHasFoundNewLine && nMaxCols >= 3; } From e5d743bd87cb48d58386cd96da89b1654a7bdf4c Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 10 Jun 2024 16:32:53 +0200 Subject: [PATCH 0134/1119] GTFS: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/ogr/ogr_gtfs.py | 6 ++- doc/source/drivers/vector/gtfs.rst | 5 +++ ogr/ogrsf_frmts/gtfs/ogrgtfsdriver.cpp | 62 +++++++++++++------------- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/autotest/ogr/ogr_gtfs.py b/autotest/ogr/ogr_gtfs.py index aaf29eee35aa..923d989dc7d0 100755 --- a/autotest/ogr/ogr_gtfs.py +++ b/autotest/ogr/ogr_gtfs.py @@ -33,7 +33,7 @@ import ogrtest import pytest -from osgeo import ogr +from osgeo import gdal, ogr pytestmark = pytest.mark.require_driver("GTFS") @@ -59,6 +59,10 @@ def test_ogr_gtfs_open(): assert ds assert ds.GetLayerCount() == 9 + ds = gdal.OpenEx("/vsizip/data/gtfs/gtfs_extract.zip", allowed_drivers=["GTFS"]) + assert ds + assert ds.GetLayerCount() == 9 + ############################################################################### diff --git a/doc/source/drivers/vector/gtfs.rst b/doc/source/drivers/vector/gtfs.rst index 166f701e39f2..c03b9ac2fa65 100644 --- a/doc/source/drivers/vector/gtfs.rst +++ b/doc/source/drivers/vector/gtfs.rst @@ -29,6 +29,11 @@ The connection name can be: - a directory name prefixed with ``GTFS:`` (potentially a ``/vsizip/path/to/the.zip`` filename prefixed with ``GTFS:``) +Alternatively, starting with GDAL 3.10, specifying the ``-if GTFS`` option to +command line utilities accepting it, or ``GTFS`` as the only value of the +``papszAllowedDrivers`` of :cpp:func:`GDALOpenEx`, also forces the driver to +recognize the passed filename. + Driver capabilities ------------------- diff --git a/ogr/ogrsf_frmts/gtfs/ogrgtfsdriver.cpp b/ogr/ogrsf_frmts/gtfs/ogrgtfsdriver.cpp index 899d35cc112a..bb86c5af78ef 100644 --- a/ogr/ogrsf_frmts/gtfs/ogrgtfsdriver.cpp +++ b/ogr/ogrsf_frmts/gtfs/ogrgtfsdriver.cpp @@ -34,6 +34,8 @@ #include #include +constexpr const char *const apszCSVDriver[] = {"CSV", nullptr}; + /***********************************************************************/ /* OGRGTFSDataset */ /***********************************************************************/ @@ -73,7 +75,7 @@ OGRLayer *OGRGTFSDataset::GetLayer(int nIdx) class OGRGTFSLayer final : public OGRLayer { - std::string m_osDirname{}; + const std::string m_osDirname; std::unique_ptr m_poUnderlyingDS{}; OGRLayer *m_poUnderlyingLayer = nullptr; // owned by m_poUnderlyingDS OGRFeatureDefn *m_poFeatureDefn = nullptr; @@ -212,7 +214,8 @@ void OGRGTFSLayer::PrepareTripsData() { { auto poStopsDS = std::unique_ptr(GDALDataset::Open( - (m_osDirname + "/stops.txt").c_str(), GDAL_OF_VECTOR)); + std::string(m_osDirname).append("/stops.txt").c_str(), + GDAL_OF_VECTOR, apszCSVDriver)); if (!poStopsDS) return; auto poStopsLyr = poStopsDS->GetLayer(0); @@ -237,7 +240,8 @@ void OGRGTFSLayer::PrepareTripsData() } auto poStopTimesDS = std::unique_ptr(GDALDataset::Open( - (m_osDirname + "/stop_times.txt").c_str(), GDAL_OF_VECTOR)); + std::string(m_osDirname).append("/stop_times.txt").c_str(), + GDAL_OF_VECTOR, apszCSVDriver)); if (!poStopTimesDS) return; auto poStopTimesLyr = poStopTimesDS->GetLayer(0); @@ -564,6 +568,11 @@ int OGRGTFSDataset::Identify(GDALOpenInfo *poOpenInfo) if (STARTS_WITH(poOpenInfo->pszFilename, "GTFS:")) return TRUE; + if (poOpenInfo->IsSingleAllowedDriver("GTFS") && poOpenInfo->bIsDirectory) + { + return TRUE; + } + if (!EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "zip")) return FALSE; @@ -618,45 +627,39 @@ GDALDataset *OGRGTFSDataset::Open(GDALOpenInfo *poOpenInfo) if (!Identify(poOpenInfo)) return nullptr; - const char *pszFilename = poOpenInfo->pszFilename; - if (STARTS_WITH(pszFilename, "GTFS:")) - pszFilename += strlen("GTFS:"); + const char *pszGTFSFilename = poOpenInfo->pszFilename; + if (STARTS_WITH(pszGTFSFilename, "GTFS:")) + pszGTFSFilename += strlen("GTFS:"); - std::string osBaseDir(pszFilename); - if (!STARTS_WITH(pszFilename, "/vsizip/") && - EQUAL(CPLGetExtension(pszFilename), "zip")) - { - osBaseDir = "/vsizip/{"; - osBaseDir += pszFilename; - osBaseDir += '}'; - } - - const std::string osCSVBaseDirPrefix(std::string("CSV:") + osBaseDir); + const std::string osBaseDir( + (!STARTS_WITH(pszGTFSFilename, "/vsizip/") && + EQUAL(CPLGetExtension(pszGTFSFilename), "zip")) + ? std::string("/vsizip/{").append(pszGTFSFilename).append("}") + : std::string(pszGTFSFilename)); auto poDS = std::make_unique(); - char **papszFilenames = VSIReadDir(osBaseDir.c_str()); + const CPLStringList aosFilenames(VSIReadDir(osBaseDir.c_str())); size_t nCountFound = 0; std::string osShapesFilename; - for (CSLConstList papszIter = papszFilenames; papszIter && *papszIter; - ++papszIter) + for (const char *pszFilename : cpl::Iterate(aosFilenames)) { - if (!EQUAL(CPLGetExtension(*papszIter), "txt")) + if (!EQUAL(CPLGetExtension(pszFilename), "txt")) continue; for (const char *pszFilenameInDir : apszRequiredFiles) { - if (EQUAL(*papszIter, pszFilenameInDir)) + if (EQUAL(pszFilename, pszFilenameInDir)) { nCountFound++; break; } } - if (EQUAL(*papszIter, "shapes.txt")) - osShapesFilename = *papszIter; + if (EQUAL(pszFilename, "shapes.txt")) + osShapesFilename = pszFilename; - auto poCSVDataset = std::unique_ptr( - GDALDataset::Open((osCSVBaseDirPrefix + '/' + *papszIter).c_str(), - GDAL_OF_VERBOSE_ERROR | GDAL_OF_VECTOR)); + auto poCSVDataset = std::unique_ptr(GDALDataset::Open( + std::string(osBaseDir).append("/").append(pszFilename).c_str(), + GDAL_OF_VERBOSE_ERROR | GDAL_OF_VECTOR, apszCSVDriver)); if (poCSVDataset) { auto poUnderlyingLayer = poCSVDataset->GetLayer(0); @@ -667,13 +670,12 @@ GDALDataset *OGRGTFSDataset::Open(GDALOpenInfo *poOpenInfo) { poDS->m_apoLayers.emplace_back( std::make_unique( - osCSVBaseDirPrefix, CPLGetBasename(*papszIter), + osBaseDir, CPLGetBasename(pszFilename), std::move(poCSVDataset))); } } } } - CSLDestroy(papszFilenames); if (nCountFound != sizeof(apszRequiredFiles) / sizeof(apszRequiredFiles[0])) { @@ -685,8 +687,8 @@ GDALDataset *OGRGTFSDataset::Open(GDALOpenInfo *poOpenInfo) if (!osShapesFilename.empty()) { auto poCSVDataset = std::unique_ptr(GDALDataset::Open( - (osCSVBaseDirPrefix + '/' + osShapesFilename).c_str(), - GDAL_OF_VERBOSE_ERROR | GDAL_OF_VECTOR)); + std::string(osBaseDir).append("/").append(osShapesFilename).c_str(), + GDAL_OF_VERBOSE_ERROR | GDAL_OF_VECTOR, apszCSVDriver)); if (poCSVDataset) { auto poUnderlyingLayer = poCSVDataset->GetLayer(0); From 4c14f3d76c88602e8d54257ee37aaaebe0ec3a3b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 10 Jun 2024 17:34:27 +0200 Subject: [PATCH 0135/1119] Arrow: relax identification checks when papszAllowedDrivers[] contains only the driver name (for IPC format) --- autotest/ogr/ogr_arrow.py | 64 ++++++++++--------- doc/source/drivers/vector/arrow.rst | 4 ++ ogr/ogrsf_frmts/arrow/ogrfeatherdriver.cpp | 3 + .../arrow/ogrfeatherdrivercore.cpp | 8 ++- 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/autotest/ogr/ogr_arrow.py b/autotest/ogr/ogr_arrow.py index 5c2088f71165..c5c1165d97fa 100755 --- a/autotest/ogr/ogr_arrow.py +++ b/autotest/ogr/ogr_arrow.py @@ -738,38 +738,44 @@ def test_ogr_arrow_write_arrow_fid_in_input_but_not_in_output(tmp_vsimem): @gdaltest.enable_exceptions() -def test_ogr_arrow_write_arrow_fid_in_output_but_not_in_input(tmp_vsimem): +def test_ogr_arrow_ipc_read_stdin(tmp_path): - src_ds = ogr.Open("data/poly.shp") - src_lyr = src_ds.GetLayer(0) - - outfilename = str(tmp_vsimem / "poly.feather") - with ogr.GetDriverByName("Arrow").CreateDataSource(outfilename) as dst_ds: - dst_lyr = dst_ds.CreateLayer( + outfilename = str(tmp_path / "poly.bin") + with ogr.GetDriverByName("Arrow").CreateDataSource(outfilename) as ds: + lyr = ds.CreateLayer( "test", - srs=src_lyr.GetSpatialRef(), - geom_type=ogr.wkbPoint, - options=["GEOMETRY_ENCODING=WKB", "FID=my_fid"], + geom_type=ogr.wkbNone, + options=["FORMAT=STREAM"], ) + fld_defn = ogr.FieldDefn("foo") + fld_defn.SetComment("x" * (1024 * 1024)) + lyr.CreateField(fld_defn) + f = ogr.Feature(lyr.GetLayerDefn()) + f["foo"] = "bar" + lyr.CreateFeature(f) - stream = src_lyr.GetArrowStream(["INCLUDE_FID=NO"]) - schema = stream.GetSchema() - - success, error_msg = dst_lyr.IsArrowSchemaSupported(schema) - assert success - - for i in range(schema.GetChildrenCount()): - if schema.GetChild(i).GetName() not in ("wkb_geometry", "OGC_FID"): - dst_lyr.CreateFieldFromArrowSchema(schema.GetChild(i)) + assert gdal.VSIStatL(outfilename).size > 1024 * 1024 - while True: - array = stream.GetNextRecordBatch() - if array is None: - break - assert dst_lyr.WriteArrowBatch(schema, array) == ogr.OGRERR_NONE + # By default, as the header section is larger than 1 MB, we can't + # identify /vsistdin/ + with gdaltest.config_options( + { + "CPL_VSISTDIN_FILE": outfilename, + "CPL_VSISTDIN_RESET_POSITION": "YES", + "CPL_VSISTDIN_FILE_CLOSE": "YES", + } + ): + with pytest.raises(Exception): + gdal.Open("/vsistdin/") - ds = ogr.Open(outfilename) - lyr = ds.GetLayer(0) - src_lyr.ResetReading() - for i in range(src_lyr.GetFeatureCount()): - assert str(src_lyr.GetNextFeature()) == str(lyr.GetNextFeature()) + with gdaltest.config_options( + { + "CPL_VSISTDIN_FILE": outfilename, + "CPL_VSISTDIN_RESET_POSITION": "YES", + "CPL_VSISTDIN_FILE_CLOSE": "YES", + } + ): + ds = gdal.OpenEx("/vsistdin/", allowed_drivers=["ARROW"]) + lyr = ds.GetLayer(0) + f = lyr.GetNextFeature() + assert f["foo"] == "bar" diff --git a/doc/source/drivers/vector/arrow.rst b/doc/source/drivers/vector/arrow.rst index 76448ac060db..a523131a71c6 100644 --- a/doc/source/drivers/vector/arrow.rst +++ b/doc/source/drivers/vector/arrow.rst @@ -30,6 +30,10 @@ The driver supports the 2 variants of the format: and the metadata section is large. Prefixing the filename with ``ARROW_IPC_STREAM:`` (e.g "ARROW_IPC_STREAM:/vsistdin/") will cause the driver to unconditionally open the file as a streaming IPC format. + Alternatively, starting with GDAL 3.10, specifying the ``-if ARROW`` option to + command line utilities accepting it, or ``ARROW`` as the only value of the + ``papszAllowedDrivers`` of :cpp:func:`GDALOpenEx`, also forces the driver to + recognize the passed filename. This driver also supports geometry columns using the GeoArrow specification. diff --git a/ogr/ogrsf_frmts/arrow/ogrfeatherdriver.cpp b/ogr/ogrsf_frmts/arrow/ogrfeatherdriver.cpp index d37d31c340d7..a2e7906be06f 100644 --- a/ogr/ogrsf_frmts/arrow/ogrfeatherdriver.cpp +++ b/ogr/ogrsf_frmts/arrow/ogrfeatherdriver.cpp @@ -68,6 +68,9 @@ static bool IsArrowIPCStream(GDALOpenInfo *poOpenInfo) CPL_LSBUINT32PTR(poOpenInfo->pabyHeader + CONTINUATION_SIZE); if (strcmp(poOpenInfo->pszFilename, "/vsistdin/") == 0) { + if (poOpenInfo->IsSingleAllowedDriver("ARROW")) + return true; + // Padding after metadata and before body is not necessarily present // but the body must be at least 4 bytes constexpr int PADDING_MAX_SIZE = 4; diff --git a/ogr/ogrsf_frmts/arrow/ogrfeatherdrivercore.cpp b/ogr/ogrsf_frmts/arrow/ogrfeatherdrivercore.cpp index 99ca4ac1ebc6..84639ecf0ab9 100644 --- a/ogr/ogrsf_frmts/arrow/ogrfeatherdrivercore.cpp +++ b/ogr/ogrsf_frmts/arrow/ogrfeatherdrivercore.cpp @@ -70,7 +70,9 @@ static int OGRFeatherDriverIsArrowIPCStreamBasic(GDALOpenInfo *poOpenInfo) 1024 * 1024 - (CONTINUATION_SIZE + METADATA_SIZE_SIZE + PADDING_MAX_SIZE)) { - return false; + if (poOpenInfo->IsSingleAllowedDriver("ARROW")) + return true; + return GDAL_IDENTIFY_UNKNOWN; } const int nSizeToRead = CONTINUATION_SIZE + METADATA_SIZE_SIZE + nMetadataSize + PADDING_MAX_SIZE; @@ -79,6 +81,8 @@ static int OGRFeatherDriverIsArrowIPCStreamBasic(GDALOpenInfo *poOpenInfo) return false; } + if (poOpenInfo->IsSingleAllowedDriver("ARROW")) + return true; return GDAL_IDENTIFY_UNKNOWN; } @@ -89,6 +93,8 @@ static int OGRFeatherDriverIsArrowIPCStreamBasic(GDALOpenInfo *poOpenInfo) nFileSize - (CONTINUATION_SIZE + METADATA_SIZE_SIZE)) return false; + if (poOpenInfo->IsSingleAllowedDriver("ARROW")) + return true; return GDAL_IDENTIFY_UNKNOWN; } return false; From 0399de7b199d6f863dbea3b47f7efd17875476f4 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 10 Jun 2024 18:45:47 +0200 Subject: [PATCH 0136/1119] VICAR: handle VICAR header being located beyond 2 GB within PDS3 file --- frmts/pds/pdsdrivercore.cpp | 16 ++++++++-------- frmts/pds/pdsdrivercore.h | 2 +- frmts/pds/vicardataset.cpp | 7 ++++--- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/frmts/pds/pdsdrivercore.cpp b/frmts/pds/pdsdrivercore.cpp index fa1bea070932..82cc8c19cd99 100644 --- a/frmts/pds/pdsdrivercore.cpp +++ b/frmts/pds/pdsdrivercore.cpp @@ -493,11 +493,11 @@ void ISIS3DriverSetCommonMetadata(GDALDriver *poDriver) /* VICARGetLabelOffset() */ /************************************************************************/ -int VICARGetLabelOffset(GDALOpenInfo *poOpenInfo) +vsi_l_offset VICARGetLabelOffset(GDALOpenInfo *poOpenInfo) { if (poOpenInfo->pabyHeader == nullptr || poOpenInfo->fpL == nullptr) - return -1; + return static_cast(-1); std::string osHeader; const char *pszHeader = @@ -520,12 +520,12 @@ int VICARGetLabelOffset(GDALOpenInfo *poOpenInfo) // If opening in vector-only mode, then check when have NBB != 0 const char *pszNBB = strstr(pszHeader, "NBB"); if (pszNBB == nullptr) - return -1; + return static_cast(-1); const char *pszEqualSign = strchr(pszNBB, '='); if (pszEqualSign == nullptr) - return -1; + return static_cast(-1); if (atoi(pszEqualSign + 1) == 0) - return -1; + return static_cast(-1); } if (strstr(pszHeader, "LBLSIZE") != nullptr && strstr(pszHeader, "FORMAT") != nullptr && @@ -533,9 +533,9 @@ int VICARGetLabelOffset(GDALOpenInfo *poOpenInfo) strstr(pszHeader, "NS") != nullptr && strstr(pszHeader, "NB") != nullptr) { - return static_cast(nOffset); + return nOffset; } - return -1; + return static_cast(-1); } /************************************************************************/ @@ -544,7 +544,7 @@ int VICARGetLabelOffset(GDALOpenInfo *poOpenInfo) static int VICARDriverIdentify(GDALOpenInfo *poOpenInfo) { - return VICARGetLabelOffset(poOpenInfo) >= 0; + return VICARGetLabelOffset(poOpenInfo) != static_cast(-1); } /************************************************************************/ diff --git a/frmts/pds/pdsdrivercore.h b/frmts/pds/pdsdrivercore.h index 44c2031d91c6..3f347c613d05 100644 --- a/frmts/pds/pdsdrivercore.h +++ b/frmts/pds/pdsdrivercore.h @@ -82,7 +82,7 @@ constexpr const char *VICAR_DRIVER_NAME = "VICAR"; #define VICARDriverSetCommonMetadata \ PLUGIN_SYMBOL_NAME(VICARDriverSetCommonMetadata) -int VICARGetLabelOffset(GDALOpenInfo *poOpenInfo); +vsi_l_offset VICARGetLabelOffset(GDALOpenInfo *poOpenInfo); void VICARDriverSetCommonMetadata(GDALDriver *poDriver); diff --git a/frmts/pds/vicardataset.cpp b/frmts/pds/vicardataset.cpp index 77d11af8b636..c217753e79d0 100644 --- a/frmts/pds/vicardataset.cpp +++ b/frmts/pds/vicardataset.cpp @@ -2481,13 +2481,14 @@ GDALDataset *VICARDataset::Open(GDALOpenInfo *poOpenInfo) /* -------------------------------------------------------------------- */ /* Does this look like a VICAR dataset? */ /* -------------------------------------------------------------------- */ - const int nLabelOffset = VICARGetLabelOffset(poOpenInfo); - if (nLabelOffset < 0) + const vsi_l_offset nLabelOffset = VICARGetLabelOffset(poOpenInfo); + if (nLabelOffset == static_cast(-1)) return nullptr; if (nLabelOffset > 0) { CPLString osSubFilename; - osSubFilename.Printf("/vsisubfile/%d,%s", nLabelOffset, + osSubFilename.Printf("/vsisubfile/" CPL_FRMT_GUIB ",%s", + static_cast(nLabelOffset), poOpenInfo->pszFilename); GDALOpenInfo oOpenInfo(osSubFilename.c_str(), poOpenInfo->eAccess); return Open(&oOpenInfo); From a3eeabfcf842f3d55e90707f7cf654c7bbbbab86 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 10 Jun 2024 18:56:33 +0200 Subject: [PATCH 0137/1119] VICAR: relax identification checks when papszAllowedDrivers[] contains only the driver name --- autotest/gdrivers/vicar.py | 5 ++++ doc/source/drivers/raster/vicar.rst | 5 ++++ frmts/pds/pdsdrivercore.cpp | 42 ++++++++++++++++++++++------- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/autotest/gdrivers/vicar.py b/autotest/gdrivers/vicar.py index 1df52fd84912..4b37ad125d75 100755 --- a/autotest/gdrivers/vicar.py +++ b/autotest/gdrivers/vicar.py @@ -560,3 +560,8 @@ def test_vicar_open_from_pds3(): assert ds assert ds.GetDriver().ShortName == "VICAR" assert struct.unpack("B", ds.GetRasterBand(1).ReadRaster())[0] == ord("x") + + ds = gdal.OpenEx("/vsimem/test", allowed_drivers=["VICAR"]) + assert ds + assert ds.GetDriver().ShortName == "VICAR" + assert struct.unpack("B", ds.GetRasterBand(1).ReadRaster())[0] == ord("x") diff --git a/doc/source/drivers/raster/vicar.rst b/doc/source/drivers/raster/vicar.rst index 7fb023a7ed17..30997485a4bc 100644 --- a/doc/source/drivers/raster/vicar.rst +++ b/doc/source/drivers/raster/vicar.rst @@ -13,6 +13,11 @@ VICAR -- VICAR :ref:`PDS ` driver in that situation. Starting with GDAL 3.1, if the :config:`GDAL_TRY_PDS3_WITH_VICAR` configuration option is set to YES, the dataset will be opened by the VICAR driver. + Alternatively, starting with GDAL 3.10, specifying the ``-if VICAR`` option + to command line utilities accepting it, or ``VICAR`` as the only value of + the ``papszAllowedDrivers`` of :cpp:func:`GDALOpenEx`, also forces the + driver to recognize the passed filename. + Driver capabilities ------------------- diff --git a/frmts/pds/pdsdrivercore.cpp b/frmts/pds/pdsdrivercore.cpp index 82cc8c19cd99..2dc779741acb 100644 --- a/frmts/pds/pdsdrivercore.cpp +++ b/frmts/pds/pdsdrivercore.cpp @@ -499,6 +499,15 @@ vsi_l_offset VICARGetLabelOffset(GDALOpenInfo *poOpenInfo) if (poOpenInfo->pabyHeader == nullptr || poOpenInfo->fpL == nullptr) return static_cast(-1); + const auto HasFoundVICARKeywords = [](const char *pszHeader) + { + return strstr(pszHeader, "LBLSIZE") != nullptr && + strstr(pszHeader, "FORMAT") != nullptr && + strstr(pszHeader, "NL") != nullptr && + strstr(pszHeader, "NS") != nullptr && + strstr(pszHeader, "NB") != nullptr; + }; + std::string osHeader; const char *pszHeader = reinterpret_cast(poOpenInfo->pabyHeader); @@ -506,10 +515,11 @@ vsi_l_offset VICARGetLabelOffset(GDALOpenInfo *poOpenInfo) // If the user sets GDAL_TRY_PDS3_WITH_VICAR=YES, then we will gracefully // hand over the file to the VICAR dataset. vsi_l_offset nOffset = 0; - if (CPLTestBool(CPLGetConfigOption("GDAL_TRY_PDS3_WITH_VICAR", "NO")) && - !STARTS_WITH(poOpenInfo->pszFilename, "/vsisubfile/") && - (nOffset = GetVICARLabelOffsetFromPDS3(pszHeader, poOpenInfo->fpL, - osHeader)) > 0) + const bool bTryPDS3WithVicar = + CPLTestBool(CPLGetConfigOption("GDAL_TRY_PDS3_WITH_VICAR", "NO")) && + !STARTS_WITH(poOpenInfo->pszFilename, "/vsisubfile/"); + if (bTryPDS3WithVicar && (nOffset = GetVICARLabelOffsetFromPDS3( + pszHeader, poOpenInfo->fpL, osHeader)) > 0) { pszHeader = osHeader.c_str(); } @@ -527,12 +537,26 @@ vsi_l_offset VICARGetLabelOffset(GDALOpenInfo *poOpenInfo) if (atoi(pszEqualSign + 1) == 0) return static_cast(-1); } - if (strstr(pszHeader, "LBLSIZE") != nullptr && - strstr(pszHeader, "FORMAT") != nullptr && - strstr(pszHeader, "NL") != nullptr && - strstr(pszHeader, "NS") != nullptr && - strstr(pszHeader, "NB") != nullptr) + + if (HasFoundVICARKeywords(pszHeader)) { + // If we find VICAR keywords, but the file starts with PDS_VERSION_ID, + // it might be a PDS3 image that includes a VICAR header. Check if + // this is the case. + if (nOffset == 0 && STARTS_WITH(pszHeader, "PDS_VERSION_ID")) + { + if (!bTryPDS3WithVicar && + (!GDALGetDriverByName("PDS") || + poOpenInfo->IsSingleAllowedDriver("VICAR"))) + { + const auto nOffset2 = GetVICARLabelOffsetFromPDS3( + pszHeader, poOpenInfo->fpL, osHeader); + if (nOffset2 > 0 && HasFoundVICARKeywords(osHeader.c_str())) + { + return nOffset2; + } + } + } return nOffset; } return static_cast(-1); From 7f33f87cf079a671a8c02f5bf2d790d65ebb9d12 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 11 Jun 2024 15:01:38 +0200 Subject: [PATCH 0138/1119] download.rst: fix link to GDAL Spack package --- doc/source/download.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/download.rst b/doc/source/download.rst index 162a35dfad82..ce15eeaeae8b 100644 --- a/doc/source/download.rst +++ b/doc/source/download.rst @@ -407,7 +407,7 @@ It was designed for large supercomputing centers. Spack builds packages from sources, and allows tweaking their configurations. You can find information about GDAL in Spack at -https://spack.readthedocs.io/en/latest/package_list.html#gdal +https://packages.spack.io/package.html?name=gdal For the default GDAL build with a reduced number of drivers: From b2a4eaf60363d9762199754028d5db70c46ce3fc Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 11 Jun 2024 00:54:13 +0200 Subject: [PATCH 0139/1119] Remove obsolete traces of checking/defining cxx_std_17 --- frmts/tiledb/CMakeLists.txt | 1 - port/CMakeLists.txt | 14 +------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/frmts/tiledb/CMakeLists.txt b/frmts/tiledb/CMakeLists.txt index 883baa401c9b..620c85a7f185 100644 --- a/frmts/tiledb/CMakeLists.txt +++ b/frmts/tiledb/CMakeLists.txt @@ -16,7 +16,6 @@ if(TARGET gdal_TileDB_core) target_include_directories(gdal_TileDB_core PRIVATE $) target_compile_definitions(gdal_TileDB_core PRIVATE $) target_compile_definitions(gdal_TileDB_core PRIVATE -DTILEDB_DEPRECATED=) - target_compile_features(gdal_TileDB_core PRIVATE cxx_std_17) endif() if(NOT TARGET gdal_TileDB) diff --git a/port/CMakeLists.txt b/port/CMakeLists.txt index 3fce7cb5aa77..bb87bc8c3e37 100644 --- a/port/CMakeLists.txt +++ b/port/CMakeLists.txt @@ -52,6 +52,7 @@ set(CPL_SOURCES cplstring.cpp cpl_vsisimple.cpp cpl_vsil.cpp + cpl_vsi_mem.cpp cpl_http.cpp cpl_hash_set.cpp cplkeywordparser.cpp @@ -130,19 +131,6 @@ if(CMAKE_INSTALL_FULL_SYSCONFDIR) target_compile_definitions(cpl PRIVATE SYSCONFDIR="${CMAKE_INSTALL_FULL_SYSCONFDIR}") endif() -# To enable conditional use of std::shared_mutex in cpl_vsi_mem.cpp -add_library(cpl_vsi_mem OBJECT cpl_vsi_mem.cpp) -target_sources(${GDAL_LIB_TARGET_NAME} PRIVATE $) -target_compile_options(cpl_vsi_mem PRIVATE ${GDAL_CXX_WARNING_FLAGS} ${WFLAG_OLD_STYLE_CAST} ${WFLAG_EFFCXX}) -set_property(TARGET cpl_vsi_mem PROPERTY POSITION_INDEPENDENT_CODE ${GDAL_OBJECT_LIBRARIES_POSITION_INDEPENDENT_CODE}) -target_include_directories(cpl_vsi_mem PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) - -include(CheckCXXCompilerFlag) -check_cxx_compiler_flag("-std=c++17" COMPILER_SUPPORTS_CXX17) -if(COMPILER_SUPPORTS_CXX17) - target_compile_features(cpl_vsi_mem PRIVATE cxx_std_17) -endif() - if (WIN32) target_sources(cpl PRIVATE cpl_vsil_win32.cpp) else () From 99870b9e9352f31a71d29ff4f9e5a8ae4c866891 Mon Sep 17 00:00:00 2001 From: Lucian Plesea Date: Tue, 11 Jun 2024 06:14:39 -0700 Subject: [PATCH 0140/1119] MRF: Faster LERC V1 encoding (#10188) Make LERC V1 encoding in MRF about twice as fast, by avoiding temporary vector use --- frmts/mrf/LERCV1/Lerc1Image.cpp | 230 ++++++++++++++------------------ frmts/mrf/LERCV1/Lerc1Image.h | 7 +- 2 files changed, 100 insertions(+), 137 deletions(-) diff --git a/frmts/mrf/LERCV1/Lerc1Image.cpp b/frmts/mrf/LERCV1/Lerc1Image.cpp index 89a274819de6..80c7fe525867 100644 --- a/frmts/mrf/LERCV1/Lerc1Image.cpp +++ b/frmts/mrf/LERCV1/Lerc1Image.cpp @@ -1,5 +1,5 @@ /* -Copyright 2015 - 2021 Esri +Copyright 2015 - 2024 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -58,6 +58,7 @@ bool BitMaskV1::RLEdecompress(const Byte *src, size_t n) count = *src++; \ count += (*src++ << 8); \ } + while (sz > 0) { // One sequence per loop READ_COUNT; @@ -203,68 +204,17 @@ static int numBytesUInt(unsigned int k) return (k <= 0xff) ? 1 : (k <= 0xffff) ? 2 : 4; } -// Bits required to store v +// Index of top set bit, counting from 1 static int nBits(unsigned int v) { - int n = 0; - while (v >> n) - n++; - return n; -} - -// see the old stream IO functions below on how to call. -// if you change write(...) / read(...), don't forget to update -// computeNumBytesNeeded(...). -static bool blockwrite(Byte **ppByte, const std::vector &d) -{ - if (!ppByte || d.empty()) - return false; - - unsigned int maxElem = *std::max_element(d.begin(), d.end()); - unsigned int numElements = (unsigned int)d.size(); - int n = numBytesUInt(numElements); - int numBits = nBits(maxElem); // 0 to 28 - - // use bits67 to encode the type used for numElements: Byte, ushort, or uint - // n is in {1, 2, 4} - // 0xc0 is invalid, will trigger an error - **ppByte = static_cast(numBits | bits67[n - 1]); - (*ppByte)++; - memcpy(*ppByte, &numElements, n); - *ppByte += n; - if (numBits == 0) - return true; - - int bits = 32; // Available - unsigned int acc = 0; // Accumulator - for (unsigned int val : d) - { - if (bits >= numBits) - { // no accumulator overflow - acc |= val << (bits - numBits); - bits -= numBits; - } - else - { // accum overflowing - acc |= val >> (numBits - bits); - memcpy(*ppByte, &acc, sizeof(acc)); - *ppByte += sizeof(acc); - bits += 32 - numBits; // under 32 - acc = val << bits; - } - } - - // There are between 1 and 4 bytes left to write - int nbytes = 4; - while (bits >= 8) - { - acc >>= 8; - bits -= 8; - nbytes--; - } - memcpy(*ppByte, &acc, nbytes); - *ppByte += nbytes; - return true; + int r = int(0 != (v >> 16)) << 4; + v >>= r; + int t = int(0 != (v >> 8)) << 3; + v >>= t; + r += t; + t = int(0 != (v >> 4)) << 2; + v = (v >> t) << 1; + return 1 + r + t + int((0xffffaa50ul >> v) & 0x3); } static bool blockread(Byte **ppByte, size_t &size, std::vector &d) @@ -355,28 +305,27 @@ unsigned int Lerc1Image::computeNumBytesNeededToWrite(double maxZError, bool onlyZPart, InfoFromComputeNumBytes *info) const { - int numBytesOpt; unsigned int sz = - (unsigned int)sCntZImage.size() + 4 * sizeof(int) + sizeof(double); + (unsigned int)(sCntZImage.size() + 4 * sizeof(int) + sizeof(double)); if (!onlyZPart) { - float cntMin, cntMax; - computeCntStats(cntMin, cntMax); - - numBytesOpt = 0; - if (cntMin != cntMax) - numBytesOpt = mask.RLEsize(); - + auto m = mask.IsValid(0); info->numTilesVertCnt = 0; info->numTilesHoriCnt = 0; - info->numBytesCnt = numBytesOpt; - info->maxCntInImg = cntMax; - - sz += 3 * sizeof(int) + sizeof(float) + numBytesOpt; + info->maxCntInImg = m; + info->numBytesCnt = 0; + for (int i = 0; i < getSize(); i++) + if (m != mask.IsValid(i)) + { + info->numBytesCnt = mask.RLEsize(); + info->maxCntInImg = 1; + break; + } + sz += 3 * sizeof(int) + sizeof(float) + info->numBytesCnt; } // z part - int numTilesVert, numTilesHori; + int numTilesVert, numTilesHori, numBytesOpt; float maxValInImg; if (!findTiling(maxZError, numTilesVert, numTilesHori, numBytesOpt, maxValInImg)) @@ -478,6 +427,7 @@ bool Lerc1Image::read(Byte **ppByte, size_t &nRemainingBytes, double maxZError, #define RDVAR(PTR, VAR) \ memcpy(&(VAR), (PTR), sizeof(VAR)); \ (PTR) += sizeof(VAR) + size_t len = sCntZImage.length(); if (nRemainingBytes < len) return false; @@ -685,12 +635,10 @@ bool Lerc1Image::writeTiles(double maxZError, int numTilesV, int numTilesH, maxValInImg = -FLT_MAX; int tileHeight = static_cast(getHeight() / numTilesV); int tileWidth = static_cast(getWidth() / numTilesH); - int v0 = 0; - while (v0 < getHeight()) + for (int v0 = 0; v0 < getHeight(); v0 += tileHeight) { int v1 = std::min(getHeight(), v0 + tileHeight); - int h0 = 0; - while (h0 < getWidth()) + for (int h0 = 0; h0 < getWidth(); h0 += tileWidth) { int h1 = std::min(getWidth(), h0 + tileWidth); float zMin = 0, zMax = 0; @@ -712,9 +660,8 @@ bool Lerc1Image::writeTiles(double maxZError, int numTilesV, int numTilesH, { numBytesNeeded = numBytesZTile(numValidPixel, zMin, zMax, maxZError); - // Try moving zMin up by maxZError, it may require fewer - // bytes A bit less than maxZError, to avoid quantization - // underflow + // Try moving zMin up by almost maxZError, + // it may require fewer bytes float zm = static_cast(zMin + 0.999999 * maxZError); if (numFinite == numValidPixel && zm <= zMax) { @@ -761,9 +708,7 @@ bool Lerc1Image::writeTiles(double maxZError, int numTilesV, int numTilesH, if (numBytesWritten != numBytesNeeded) return false; } - h0 = h1; } - v0 = v1; } return true; } @@ -778,34 +723,20 @@ bool Lerc1Image::readTiles(double maxZErrorInFile, int numTilesV, int numTilesH, int tileWidth = static_cast(getWidth() / numTilesH); if (tileWidth <= 0 || tileHeight <= 0) // Prevent infinite loop return false; - int r0 = 0; - while (r0 < getHeight()) + for (int r0 = 0; r0 < getHeight(); r0 += tileHeight) { int r1 = std::min(getHeight(), r0 + tileHeight); - int c0 = 0; - while (c0 < getWidth()) + for (int c0 = 0; c0 < getWidth(); c0 += tileWidth) { int c1 = std::min(getWidth(), c0 + tileWidth); if (!readZTile(&bArr, nRemainingBytes, r0, r1, c0, c1, maxZErrorInFile, maxValInImg)) return false; - c0 = c1; } - r0 = r1; } return true; } -void Lerc1Image::computeCntStats(float &cntMin, float &cntMax) const -{ - cntMin = cntMax = static_cast(mask.IsValid(0) ? 1.0f : 0.0f); - for (int k = 0; k < getSize() && cntMin == cntMax; k++) - if (mask.IsValid(k)) - cntMax = 1.0f; - else - cntMin = 0.0f; -} - bool Lerc1Image::computeZStats(int r0, int r1, int c0, int c1, float &zMin, float &zMax, int &numValidPixel, int &numFinite) const @@ -822,10 +753,10 @@ bool Lerc1Image::computeZStats(int r0, int r1, int c0, int c1, float &zMin, { numValidPixel++; float val = (*this)(row, col); - if (!std::isfinite(val)) - zMin = NAN; // Serves as a flag, this block will be stored - else + if (std::isfinite(val)) numFinite++; + else + zMin = NAN; // Serves as a flag, this block will be stored if (val < zMin) zMin = val; if (val > zMax) @@ -859,17 +790,15 @@ bool Lerc1Image::writeZTile(Byte **ppByte, int &numBytes, int r0, int r1, Byte *ptr = *ppByte; int cntPixel = 0; if (numValidPixel == 0 || (zMin == 0 && zMax == 0)) - { // special cases - *(*ppByte)++ = - 2; // set compression flag to 2 to mark tile as constant 0 + { + *(*ppByte)++ = 2; // mark tile as constant 0 numBytes = 1; return true; } if (maxZError == 0 || !std::isfinite(zMin) || !std::isfinite(zMax) || ((double)zMax - zMin) / (2 * maxZError) > MAXQ) - { // we'd need > 28 bit - // write z's as flt arr uncompressed - *ptr++ = 0; // flag + { // store valid pixels as floating point + *ptr++ = 0; for (int row = r0; row < r1; row++) for (int col = c0; col < c1; col++) if (IsValid(row, col)) @@ -882,34 +811,69 @@ bool Lerc1Image::writeZTile(Byte **ppByte, int &numBytes, int r0, int r1, return false; } else - { // write z's as int arr bit stuffed - Byte flag = 1; - unsigned int maxElem = - (unsigned int)(((double)zMax - zMin) / (2 * maxZError) + 0.5); + { + Byte flag = 1; // bitstuffed int array + double f = 0.5 / maxZError; // conversion to int multiplier + unsigned int maxElem = (unsigned int)(((double)zMax - zMin) * f + 0.5); if (maxElem == 0) - flag = - 3; // set compression flag to 3 to mark tile as constant zMin + flag = 3; // mark tile as constant zMin int n = numBytesFlt(zMin); // n in { 1, 2, 4 } *ptr++ = (flag | bits67[n - 1]); ptr = writeFlt(ptr, zMin, n); if (maxElem > 0) { - std::vector odataVec; + int numBits = nBits(maxElem); + n = numBytesUInt(numValidPixel); + // use bits67 to encode the type used for numElements: Byte, ushort, or uint + // n is in {1, 2, 4} + // 0xc0 is invalid, will trigger an error + *ptr++ = static_cast(numBits | bits67[n - 1]); + memcpy(ptr, &numValidPixel, n); + ptr += n; + + unsigned int acc = 0; // Accumulator + int bits = 32; // Available + for (int row = r0; row < r1; row++) for (int col = c0; col < c1; col++) if (IsValid(row, col)) - odataVec.push_back( - (unsigned int)(((double)(*this)(row, col) - zMin) / - (2 * maxZError) + - 0.5)); - if (odataVec.size() != static_cast(numValidPixel)) - return false; - if (!blockwrite(&ptr, odataVec)) + { + cntPixel++; + auto val = static_cast( + ((double)(*this)(row, col) - zMin) * f + 0.5); + + if (bits >= numBits) + { // no accumulator overflow + acc |= val << (bits - numBits); + bits -= numBits; + } + else + { // accum overflowing + acc |= val >> (numBits - bits); + memcpy(ptr, &acc, sizeof(acc)); + ptr += sizeof(acc); + bits += 32 - numBits; // under 32 + acc = val << bits; + } + } + + if (cntPixel != numValidPixel) return false; + + // There are between 1 and 4 bytes left in the accumulator + int nbytes = 4; + while (bits >= 8) + { + acc >>= 8; + bits -= 8; + nbytes--; + } + memcpy(ptr, &acc, nbytes); + ptr += nbytes; } } - numBytes = (int)(ptr - *ppByte); + numBytes = static_cast(ptr - *ppByte); *ppByte = ptr; return true; } @@ -977,7 +941,7 @@ bool Lerc1Image::readZTile(Byte **ppByte, size_t &nRemainingBytes, int r0, if (nRemainingBytes < n) return false; - float bminval = readFlt(ptr, n); + float minval = readFlt(ptr, n); ptr += n; nRemainingBytes -= n; @@ -985,27 +949,29 @@ bool Lerc1Image::readZTile(Byte **ppByte, size_t &nRemainingBytes, int r0, { // all min val, regardless of mask for (int row = r0; row < r1; row++) for (int col = c0; col < c1; col++) - (*this)(row, col) = bminval; + (*this)(row, col) = minval; *ppByte = ptr; return true; } - idataVec.resize((r1 - r0) * (c1 - c0)); // max size + idataVec.resize((r1 - r0) * (c1 - c0)); // max size, gets adjusted if (!blockread(&ptr, nRemainingBytes, idataVec)) return false; - size_t nDataVecIdx = 0; + size_t numValid = idataVec.size(); + size_t i = 0; + double q = maxZErrorInFile * 2; // quanta for (int row = r0; row < r1; row++) for (int col = c0; col < c1; col++) if (IsValid(row, col)) { - if (nDataVecIdx >= idataVec.size()) + if (i >= numValid) return false; (*this)(row, col) = std::min( - maxZInImg, - static_cast(bminval + maxZErrorInFile * 2 * - idataVec[nDataVecIdx++])); + maxZInImg, static_cast(minval + q * idataVec[i++])); } + if (i != numValid) + return false; *ppByte = ptr; return true; diff --git a/frmts/mrf/LERCV1/Lerc1Image.h b/frmts/mrf/LERCV1/Lerc1Image.h index 1d4f6e9aeed4..6396119cf957 100644 --- a/frmts/mrf/LERCV1/Lerc1Image.h +++ b/frmts/mrf/LERCV1/Lerc1Image.h @@ -1,5 +1,5 @@ /* -Copyright 2015 - 2021 Esri +Copyright 2015 - 2024 Esri Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -185,8 +185,6 @@ class Lerc1Image : public TImage bool readTiles(double maxZErrorInFile, int numTilesVert, int numTilesHori, float maxValInImg, Byte *bArr, size_t nRemainingBytes); - void computeCntStats(float &cntMin, float &cntMax) - const; // Across the whole image, always works bool computeZStats(int r0, int r1, int c0, int c1, float &zMin, float &zMax, int &numValidPixel, int &numFinite) const; @@ -205,8 +203,7 @@ class Lerc1Image : public TImage computeNumBytesNeededToWrite(double maxZError, bool onlyZPart, InfoFromComputeNumBytes *info) const; - std::vector - idataVec; // temporary buffer, reused in readZTile + std::vector idataVec; // temporary buffer BitMaskV1 mask; public: From c70112a63345a9bc380a6f7da7540d1c4aac2247 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 00:51:36 +0200 Subject: [PATCH 0141/1119] Python: generate launcher shell/bat scripts for Python scripts in /swig/python/bin --- swig/python/CMakeLists.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/swig/python/CMakeLists.txt b/swig/python/CMakeLists.txt index c626b790931e..bccd75ac94a6 100644 --- a/swig/python/CMakeLists.txt +++ b/swig/python/CMakeLists.txt @@ -339,6 +339,37 @@ if (Python_Interpreter_FOUND) endif() endif() + # Install launcher scripts in bin/ (for development) + file(GLOB SCRIPTS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/gdal-utils/scripts" "${CMAKE_CURRENT_SOURCE_DIR}/gdal-utils/scripts/*.py") + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/tmp_bin") + foreach(filename IN LISTS SCRIPTS) + get_filename_component(filename_without_ext "${filename}" NAME_WLE) + if (UNIX) + set(lancher_filename "${filename_without_ext}") + else() + set(lancher_filename "${filename_without_ext}.bat") + endif() + set(tmp_launcher_filename "${CMAKE_CURRENT_BINARY_DIR}/tmp_bin/${lancher_filename}") + if (UNIX) + file(WRITE "${tmp_launcher_filename}" + "#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import re +import sys +from osgeo_utils.${filename_without_ext} import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\\.pyw|\\.exe)?\$', '', sys.argv[0]) + sys.exit(main()) +") + else() + file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/gdal-utils/scripts/${filename}" NATIVE_FILENAME) + file(WRITE "${tmp_launcher_filename}" "@python \"${NATIVE_FILENAME}\" %*") + endif() + file(COPY "${tmp_launcher_filename}" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/bin" + FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + endforeach() + add_custom_target(python_binding ALL DEPENDS ${PY_SO_LIST} ${PY_SO_LIST_WITH_RPATH} ${GDAL_PYTHON_PYSOURCES} ${GDAL_LIB_TARGET_NAME}) # Generate wheel From ac43cc91d908625b7cbdf1fd66f1e511a260e4eb Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 00:51:53 +0200 Subject: [PATCH 0142/1119] setdevenv.sh: add /swig/python/bin to PATH --- scripts/setdevenv.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/setdevenv.sh b/scripts/setdevenv.sh index ab28488cafca..eeecd1fdc757 100755 --- a/scripts/setdevenv.sh +++ b/scripts/setdevenv.sh @@ -30,9 +30,12 @@ CUR_DIR=$PWD echo "Setting environment for a CMake build from ${CUR_DIR}..." +GDAL_PYTHONPATH="$CUR_DIR/swig/python" + if [[ ! ${PATH} =~ $CUR_DIR/apps ]]; then export PATH="$CUR_DIR/apps:$PATH" export PATH="$CUR_DIR/perftests:$PATH" + export PATH="$GDAL_PYTHONPATH/bin:$PATH" export PATH="$GDAL_ROOT/swig/python/gdal-utils/scripts:$PATH" echo "Setting PATH=$PATH" fi @@ -59,7 +62,6 @@ if [[ ! "${GDAL_DATA:-}" =~ $CUR_DIR/data ]]; then echo "Setting GDAL_DATA=$GDAL_DATA" fi -GDAL_PYTHONPATH="$CUR_DIR/swig/python" if [[ ! "${PYTHONPATH:-}" =~ $GDAL_PYTHONPATH ]]; then export PYTHONPATH="$GDAL_PYTHONPATH:${PYTHONPATH:-}" echo "Setting PYTHONPATH=$PYTHONPATH" From 60bd384411bb783194b24582c1bbd424cb5f01df Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 01:09:07 +0200 Subject: [PATCH 0143/1119] Add scripts/setdevenv.bat --- scripts/setdevenv.bat | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 scripts/setdevenv.bat diff --git a/scripts/setdevenv.bat b/scripts/setdevenv.bat new file mode 100644 index 000000000000..748ff6b91dc7 --- /dev/null +++ b/scripts/setdevenv.bat @@ -0,0 +1,10 @@ +@echo off + +REM This is a very primitive script which must be called from a build +REM directory and set the environment for a Release build + +set PATH=%CD%\swig\python\bin;%CD%\apps\Release;%CD%\Release;%PATH% +set GDAL_DATA=%CD%\data +set PYTHONPATH=%CD%\swig\python +set GDAL_DRIVER_PATH=%CD%\gdalplugins\Release +set USE_PATH_FOR_GDAL_PYTHON=yes From 36df71584ef54d68c3e6ab3e36c4cec87ed7e855 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 01:09:33 +0200 Subject: [PATCH 0144/1119] Doc: add a 'Setting development environment variables' paragraph --- doc/source/development/dev_environment.rst | 33 ++++++++++++++++++++++ doc/source/development/testing.rst | 22 +-------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/doc/source/development/dev_environment.rst b/doc/source/development/dev_environment.rst index e074684d53e3..810d05e7bc19 100644 --- a/doc/source/development/dev_environment.rst +++ b/doc/source/development/dev_environment.rst @@ -136,3 +136,36 @@ From a Conda enabled console cd c:\dev\GDAL cd _build.vs2019 ctest -V --build-config Release + + +.. _setting_dev_environment_variables: + +Setting development environment variables +----------------------------------------- + +Once GDAL has been built, a number of environment variables must be set to be +able to execute C++ or Python utilities of the build directory, or run tests. + +This can be done by sourcing the following from the build directory: + +.. code-block:: bash + + . ../scripts/setdevenv.sh + +(with adjustments to the above path if the build directory is not a subdirectory of the GDAL source root). + +For Windows, a similar ``scripts/setdevenv.bat`` script exists (it currently assumes a Release build). + +To verify that environment variables have been set correctly, you can check the version of a GDAL binary: + +.. code-block:: bash + + gdalinfo --version + # GDAL 3.7.0dev-5327c149f5-dirty, released 2018/99/99 (debug build) + +and the Python bindings: + +.. code-block:: bash + + python3 -c 'from osgeo import gdal; print(gdal.__version__)' + # 3.7.0dev-5327c149f5-dirty diff --git a/doc/source/development/testing.rst b/doc/source/development/testing.rst index 7921d0ebe1a1..df55a4dfe43b 100644 --- a/doc/source/development/testing.rst +++ b/doc/source/development/testing.rst @@ -24,27 +24,7 @@ Running a subset of tests using ``pytest`` The test subsets exposed by ``ctest`` are still rather large and some may take several minutes to run. If a higher level of specificity is needed, ``pytest`` can be called directly to run groups of tests or individual tests. -Before running ``pytest``, it is important to set environment variables so that the development build of GDAL is tested, -rather than a system version. This can be done by sourcing the following from the build directory: - -.. code-block:: bash - - . ../scripts/setdevenv.sh - -(with adjustments to the above path if the build directory is not a subdirectory of the GDAL source root). -To verify that environment variables were set correctly, you can check the version of a GDAL binary: - -.. code-block:: bash - - gdalinfo --version - # GDAL 3.7.0dev-5327c149f5-dirty, released 2018/99/99 (debug build) - -and the Python bindings: - -.. code-block:: bash - - python3 -c 'from osgeo import gdal; print(gdal.__version__)' - # 3.7.0dev-5327c149f5-dirty +Before running ``pytest``, it is important to set :ref:`development environment variables ` so that the development build of GDAL is tested, rather than a system version. Tests can then be run by calling ``pytest``, for example on an individual file. On Linux and MacOS builds, the tests are symlinked into the build directory, so this From 8f291364834fdcc6ceb658d3c78442fd05d76e29 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 01:15:32 +0200 Subject: [PATCH 0145/1119] CI: Test development launcher script --- .github/workflows/ubuntu_24.04/test.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ubuntu_24.04/test.sh b/.github/workflows/ubuntu_24.04/test.sh index 9af1dd53f7b7..5ce09486593d 100755 --- a/.github/workflows/ubuntu_24.04/test.sh +++ b/.github/workflows/ubuntu_24.04/test.sh @@ -7,6 +7,9 @@ set -e LD_LIBRARY_PATH="/opt/instantclient_19_9:/opt/instantclient_19_9/lib:${LD_LIBRARY_PATH}" export LD_LIBRARY_PATH +# Test development launcher script +gdal_edit --version + export PYTEST="python3 -m pytest -vv -p no:sugar --color=no" # Run C++ tests From 2b94d7a274bfeb881a176016de932ba8acd1b79a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 02:09:30 +0200 Subject: [PATCH 0146/1119] ogrinfo.rst: fix wrong indentation --- doc/source/programs/ogrinfo.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/source/programs/ogrinfo.rst b/doc/source/programs/ogrinfo.rst index c47e69ae7b9c..dd54cff6c4fd 100644 --- a/doc/source/programs/ogrinfo.rst +++ b/doc/source/programs/ogrinfo.rst @@ -108,9 +108,10 @@ edit data. Example of ``-where`` and quoting: -.. code-block: bash + .. code-block:: bash + + -where "\"Corner Point Identifier\" LIKE '%__00_00'" - -where "\"Corner Point Identifier\" LIKE '%__00_00'" .. option:: -sql |@ From 75c9020f0bc49a79217bf53372dc4e0674d671c8 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Wed, 12 Jun 2024 14:16:23 +0200 Subject: [PATCH 0147/1119] [gdalwarp] Fix -ovr {absolute_value} (#10193) Fix #10174 -ovr {absolute_value} doesn't influence choice of output resolution --- apps/gdalwarp_lib.cpp | 13 +++++++++++++ autotest/utilities/test_gdalwarp.py | 26 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/apps/gdalwarp_lib.cpp b/apps/gdalwarp_lib.cpp index 9ca7146e5622..6fb54762fc6a 100644 --- a/apps/gdalwarp_lib.cpp +++ b/apps/gdalwarp_lib.cpp @@ -3680,6 +3680,18 @@ static GDALDatasetH GDALWarpCreateOutput( return nullptr; } + // Examine desired overview level and retrieve the corresponding dataset + // if it exists. + std::unique_ptr oDstDSOverview; + if (psOptions->nOvLevel >= 0) + { + oDstDSOverview.reset(GDALCreateOverviewDataset( + GDALDataset::FromHandle(hSrcDS), psOptions->nOvLevel, + /* bThisLevelOnly = */ true)); + if (oDstDSOverview) + hSrcDS = oDstDSOverview.get(); + } + /* -------------------------------------------------------------------- */ /* Check if the source dataset shares some files with the dest @@ -4110,6 +4122,7 @@ static GDALDatasetH GDALWarpCreateOutput( { nOptions |= GDAL_SWO_FORCE_SQUARE_PIXEL; } + if (GDALSuggestedWarpOutput2(hSrcDS, psInfo->pfnTransform, hTransformArg, adfThisGeoTransform, &nThisPixels, &nThisLines, adfExtent, diff --git a/autotest/utilities/test_gdalwarp.py b/autotest/utilities/test_gdalwarp.py index df8cb2d65b70..ee749fed657d 100755 --- a/autotest/utilities/test_gdalwarp.py +++ b/autotest/utilities/test_gdalwarp.py @@ -30,6 +30,7 @@ ############################################################################### import os +import shutil import stat import gdaltest @@ -1049,6 +1050,7 @@ def test_gdalwarp_39(gdalwarp_path, tmp_path): def test_gdalwarp_40(gdalwarp_path, tmp_path): src_tif = str(tmp_path / "test_gdalwarp_40_src.tif") + src_tif_copy = str(tmp_path / "test_gdalwarp_40_src_copy.tif") dst_tif = str(tmp_path / "test_gdalwarp_40.tif") dst_vrt = str(tmp_path / "test_gdalwarp_40.vrt") @@ -1060,8 +1062,11 @@ def test_gdalwarp_40(gdalwarp_path, tmp_path): cs_ov0 = out_ds.GetRasterBand(1).GetOverview(0).Checksum() out_ds.GetRasterBand(1).GetOverview(1).Fill(255) cs_ov1 = out_ds.GetRasterBand(1).GetOverview(1).Checksum() + out_ds = None + shutil.copy(src_tif, src_tif_copy) + # Should select main resolution gdaltest.runexternal(f"{gdalwarp_path} {src_tif} {dst_tif} -overwrite") @@ -1113,6 +1118,27 @@ def test_gdalwarp_40(gdalwarp_path, tmp_path): assert ds.GetRasterBand(1).Checksum() == cs_ov0 ds = None + # Should select overview 0 + gdaltest.runexternal(f"{gdalwarp_path} {src_tif} {dst_tif} -overwrite -ovr 0") + + ds = gdal.Open(dst_tif) + assert ds.GetRasterBand(1).Checksum() == cs_ov0 + ds = None + + # Should select overview 0 (no overwrite) + gdaltest.runexternal(f"{gdalwarp_path} {src_tif} {dst_tif} -ovr 0") + + # Repeat with no output file and no overwrite (takes a different code path) + os.unlink(dst_tif) + gdaltest.runexternal(f"{gdalwarp_path} {src_tif} {dst_tif} -ovr 0") + + # Should not crash (actually it never did) + os.unlink(dst_tif) + gdaltest.runexternal(f"{gdalwarp_path} {src_tif} {src_tif_copy} {dst_tif} -ovr 0") + ds = gdal.Open(dst_tif) + assert ds.GetRasterBand(1).Checksum() == cs_ov0 + ds = None + # Should select overview 0 through VRT gdaltest.runexternal( f"{gdalwarp_path} {src_tif} {dst_vrt} -overwrite -ts 10 10 -of VRT" From 47168b09f77c2a11cff8cb3bd96bd767bd2244ee Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 14:46:02 +0200 Subject: [PATCH 0148/1119] ODS: implement IUpdateFeature() that was broken up to now --- ogr/ogrsf_frmts/ods/ogr_ods.h | 5 +++++ ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp | 27 ++++++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/ogr/ogrsf_frmts/ods/ogr_ods.h b/ogr/ogrsf_frmts/ods/ogr_ods.h index 5cea741a0446..c0f57f710dc0 100644 --- a/ogr/ogrsf_frmts/ods/ogr_ods.h +++ b/ogr/ogrsf_frmts/ods/ogr_ods.h @@ -91,6 +91,11 @@ class OGRODSLayer final : public OGRMemLayer virtual OGRFeature *GetNextFeature() override; virtual OGRFeature *GetFeature(GIntBig nFeatureId) override; virtual OGRErr ISetFeature(OGRFeature *poFeature) override; + OGRErr IUpdateFeature(OGRFeature *poFeature, int nUpdatedFieldsCount, + const int *panUpdatedFieldsIdx, + int nUpdatedGeomFieldsCount, + const int *panUpdatedGeomFieldsIdx, + bool bUpdateStyleString) override; virtual OGRErr DeleteFeature(GIntBig nFID) override; virtual GIntBig GetFeatureCount(int) override; diff --git a/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp b/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp index fe166a6d3975..2340e9122529 100644 --- a/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp +++ b/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp @@ -157,14 +157,11 @@ GIntBig OGRODSLayer::GetFeatureCount(int bForce) } /************************************************************************/ -/* ISetFeature() */ +/* ISetFeature() */ /************************************************************************/ OGRErr OGRODSLayer::ISetFeature(OGRFeature *poFeature) { - if (poFeature == nullptr) - return OGRMemLayer::ISetFeature(poFeature); - GIntBig nFID = poFeature->GetFID(); if (nFID != OGRNullFID) poFeature->SetFID(nFID - (1 + (bHasHeaderLine ? 1 : 0))); @@ -174,6 +171,28 @@ OGRErr OGRODSLayer::ISetFeature(OGRFeature *poFeature) return eErr; } +/************************************************************************/ +/* IUpdateFeature() */ +/************************************************************************/ + +OGRErr OGRODSLayer::IUpdateFeature(OGRFeature *poFeature, + int nUpdatedFieldsCount, + const int *panUpdatedFieldsIdx, + int nUpdatedGeomFieldsCount, + const int *panUpdatedGeomFieldsIdx, + bool bUpdateStyleString) +{ + GIntBig nFID = poFeature->GetFID(); + if (nFID != OGRNullFID) + poFeature->SetFID(nFID - (1 + (bHasHeaderLine ? 1 : 0))); + SetUpdated(); + OGRErr eErr = OGRMemLayer::IUpdateFeature( + poFeature, nUpdatedFieldsCount, panUpdatedFieldsIdx, + nUpdatedGeomFieldsCount, panUpdatedGeomFieldsIdx, bUpdateStyleString); + poFeature->SetFID(nFID); + return eErr; +} + /************************************************************************/ /* DeleteFeature() */ /************************************************************************/ From 0b9af8723ab9977ab906571d47ae96c4e40f54c6 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 14:46:14 +0200 Subject: [PATCH 0149/1119] XLSX: implement IUpdateFeature() that was broken up to now --- ogr/ogrsf_frmts/xlsx/ogr_xlsx.h | 5 ++++ ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp | 28 ++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h b/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h index 6782b69675f6..eb8325740c9a 100644 --- a/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h +++ b/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h @@ -113,6 +113,11 @@ class OGRXLSXLayer final : public OGRMemLayer virtual OGRFeature *GetNextFeature() override; virtual OGRFeature *GetFeature(GIntBig nFeatureId) override; virtual OGRErr ISetFeature(OGRFeature *poFeature) override; + OGRErr IUpdateFeature(OGRFeature *poFeature, int nUpdatedFieldsCount, + const int *panUpdatedFieldsIdx, + int nUpdatedGeomFieldsCount, + const int *panUpdatedGeomFieldsIdx, + bool bUpdateStyleString) override; virtual OGRErr DeleteFeature(GIntBig nFID) override; virtual OGRErr SetNextByIndex(GIntBig nIndex) override diff --git a/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp b/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp index 6f2fa9857f0f..82b834afde51 100644 --- a/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp +++ b/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp @@ -138,15 +138,12 @@ OGRFeature *OGRXLSXLayer::GetFeature(GIntBig nFeatureId) } /************************************************************************/ -/* ISetFeature() */ +/* ISetFeature() */ /************************************************************************/ OGRErr OGRXLSXLayer::ISetFeature(OGRFeature *poFeature) { Init(); - if (poFeature == nullptr) - return OGRMemLayer::ISetFeature(poFeature); - GIntBig nFID = poFeature->GetFID(); if (nFID != OGRNullFID) poFeature->SetFID(nFID - (1 + static_cast(bHasHeaderLine))); @@ -156,6 +153,29 @@ OGRErr OGRXLSXLayer::ISetFeature(OGRFeature *poFeature) return eErr; } +/************************************************************************/ +/* IUpdateFeature() */ +/************************************************************************/ + +OGRErr OGRXLSXLayer::IUpdateFeature(OGRFeature *poFeature, + int nUpdatedFieldsCount, + const int *panUpdatedFieldsIdx, + int nUpdatedGeomFieldsCount, + const int *panUpdatedGeomFieldsIdx, + bool bUpdateStyleString) +{ + Init(); + GIntBig nFID = poFeature->GetFID(); + if (nFID != OGRNullFID) + poFeature->SetFID(nFID - (1 + static_cast(bHasHeaderLine))); + SetUpdated(); + OGRErr eErr = OGRMemLayer::IUpdateFeature( + poFeature, nUpdatedFieldsCount, panUpdatedFieldsIdx, + nUpdatedGeomFieldsCount, panUpdatedGeomFieldsIdx, bUpdateStyleString); + poFeature->SetFID(nFID); + return eErr; +} + /************************************************************************/ /* ICreateFeature() */ /************************************************************************/ From eb362c6adc380983223debce33fd492c49c1f932 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 14:30:46 +0200 Subject: [PATCH 0150/1119] GeoJSON: implement IUpdateFeature() that was broken up to now Fixes https://github.com/qgis/QGIS/pull/57736#discussion_r1636226384 --- autotest/ogr/ogr_geojson.py | 32 ++++++++++++++++-- ogr/ogrsf_frmts/geojson/ogr_geojson.h | 6 ++++ ogr/ogrsf_frmts/geojson/ogrgeojsonlayer.cpp | 37 ++++++++++++++++++--- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index f905273b2939..b5b53bb61cbc 100755 --- a/autotest/ogr/ogr_geojson.py +++ b/autotest/ogr/ogr_geojson.py @@ -1591,7 +1591,7 @@ def test_ogr_geojson_46(tmp_vsimem): ############################################################################### -# Test update support +# Test SetFeature() support @gdaltest.disable_exceptions() @@ -1764,7 +1764,7 @@ def test_ogr_geojson_47(tmp_vsimem): ############################################################################### -# Test update support with file that has a single feature not in a FeatureCollection +# Test SetFeature() support with file that has a single feature not in a FeatureCollection def test_ogr_geojson_48(tmp_vsimem): @@ -1802,6 +1802,34 @@ def test_ogr_geojson_48(tmp_vsimem): ) +############################################################################### +# Test UpdateFeature() support + + +def test_ogr_geojson_update_feature(tmp_vsimem): + + filename = str(tmp_vsimem / "test.json") + + with ogr.GetDriverByName("GeoJSON").CreateDataSource(filename) as ds: + lyr = ds.CreateLayer("test") + lyr.CreateField(ogr.FieldDefn("int64list", ogr.OFTInteger64List)) + f = ogr.Feature(lyr.GetLayerDefn()) + f["int64list"] = [123456790123, -123456790123] + lyr.CreateFeature(f) + + with ogr.Open(filename, update=1) as ds: + lyr = ds.GetLayer(0) + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetFID(0) + f["int64list"] = [123456790123, -123456790123] + lyr.UpdateFeature(f, [0], [], False) + + with ogr.Open(filename) as ds: + lyr = ds.GetLayer(0) + f = lyr.GetNextFeature() + assert f["int64list"] == [123456790123, -123456790123] + + ############################################################################### # Test ARRAY_AS_STRING diff --git a/ogr/ogrsf_frmts/geojson/ogr_geojson.h b/ogr/ogrsf_frmts/geojson/ogr_geojson.h index d531086ddf52..3a4847930f16 100644 --- a/ogr/ogrsf_frmts/geojson/ogr_geojson.h +++ b/ogr/ogrsf_frmts/geojson/ogr_geojson.h @@ -82,6 +82,11 @@ class OGRGeoJSONLayer final : public OGRMemLayer OGRErr ISetFeature(OGRFeature *poFeature) override; OGRErr ICreateFeature(OGRFeature *poFeature) override; + OGRErr IUpdateFeature(OGRFeature *poFeature, int nUpdatedFieldsCount, + const int *panUpdatedFieldsIdx, + int nUpdatedGeomFieldsCount, + const int *panUpdatedGeomFieldsIdx, + bool bUpdateStyleString) override; virtual OGRErr DeleteFeature(GIntBig nFID) override; virtual OGRErr CreateField(const OGRFieldDefn *poField, int bApproxOK = TRUE) override; @@ -140,6 +145,7 @@ class OGRGeoJSONLayer final : public OGRMemLayer bool IngestAll(); void TerminateAppendSession(); + bool SetOrUpdateFeaturePreparation(); CPL_DISALLOW_COPY_ASSIGN(OGRGeoJSONLayer) }; diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonlayer.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonlayer.cpp index 856fbf021d92..32ac9d40caea 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonlayer.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonlayer.cpp @@ -245,23 +245,52 @@ bool OGRGeoJSONLayer::IngestAll() } /************************************************************************/ -/* ISetFeature() */ +/* SetOrUpdateFeaturePreparation() */ /************************************************************************/ -OGRErr OGRGeoJSONLayer::ISetFeature(OGRFeature *poFeature) +bool OGRGeoJSONLayer::SetOrUpdateFeaturePreparation() { if (!IsUpdatable()) - return OGRERR_FAILURE; + return false; if (poReader_) { auto nNextIndex = nFeatureReadSinceReset_; if (!IngestAll()) - return OGRERR_FAILURE; + return false; SetNextByIndex(nNextIndex); } + return true; +} + +/************************************************************************/ +/* ISetFeature() */ +/************************************************************************/ + +OGRErr OGRGeoJSONLayer::ISetFeature(OGRFeature *poFeature) +{ + if (!SetOrUpdateFeaturePreparation()) + return OGRERR_FAILURE; return OGRMemLayer::ISetFeature(poFeature); } +/************************************************************************/ +/* IUpdateFeature() */ +/************************************************************************/ + +OGRErr OGRGeoJSONLayer::IUpdateFeature(OGRFeature *poFeature, + int nUpdatedFieldsCount, + const int *panUpdatedFieldsIdx, + int nUpdatedGeomFieldsCount, + const int *panUpdatedGeomFieldsIdx, + bool bUpdateStyleString) +{ + if (!SetOrUpdateFeaturePreparation()) + return OGRERR_FAILURE; + return OGRMemLayer::IUpdateFeature( + poFeature, nUpdatedFieldsCount, panUpdatedFieldsIdx, + nUpdatedGeomFieldsCount, panUpdatedGeomFieldsIdx, bUpdateStyleString); +} + /************************************************************************/ /* ICreateFeature() */ /************************************************************************/ From 46ef2447c362754d0fb0dc938e011cf7005e9cc0 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 18:20:50 +0200 Subject: [PATCH 0151/1119] test_ogrsf: test OGRLayer::UpdateFeature() --- apps/test_ogrsf.cpp | 188 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 170 insertions(+), 18 deletions(-) diff --git a/apps/test_ogrsf.cpp b/apps/test_ogrsf.cpp index 9b2edcaf3ec2..3f4592b4fd28 100644 --- a/apps/test_ogrsf.cpp +++ b/apps/test_ogrsf.cpp @@ -2001,7 +2001,7 @@ static int TestOGRLayerRandomWrite(OGRLayer *poLayer) { int bRet = TRUE; - OGRFeature *papoFeatures[5], *poFeature; + OGRFeature *papoFeatures[5]; memset(papoFeatures, 0, sizeof(papoFeatures)); @@ -2086,25 +2086,27 @@ static int TestOGRLayerRandomWrite(OGRLayer *poLayer) /* -------------------------------------------------------------------- */ /* Now re-read feature 2 to verify the effect stuck. */ /* -------------------------------------------------------------------- */ - poFeature = LOG_ACTION(poLayer->GetFeature(nFID5)); - if (poFeature == nullptr) - { - bRet = FALSE; - printf("ERROR: Attempt to GetFeature( nFID5 ) failed.\n"); - goto end; - } - if (!poFeature->Equal(papoFeatures[1])) - { - bRet = FALSE; - poFeature->DumpReadable(stderr); - papoFeatures[1]->DumpReadable(stderr); - printf("ERROR: Written feature didn't seem to retain value.\n"); - } - else if (bVerbose) { - printf("INFO: Random write test passed.\n"); + auto poFeature = + std::unique_ptr(LOG_ACTION(poLayer->GetFeature(nFID5))); + if (poFeature == nullptr) + { + bRet = FALSE; + printf("ERROR: Attempt to GetFeature( nFID5 ) failed.\n"); + goto end; + } + if (!poFeature->Equal(papoFeatures[1])) + { + bRet = FALSE; + poFeature->DumpReadable(stderr); + papoFeatures[1]->DumpReadable(stderr); + printf("ERROR: Written feature didn't seem to retain value.\n"); + } + else if (bVerbose) + { + printf("INFO: Random write test passed.\n"); + } } - DestroyFeatureAndNullify(poFeature); /* -------------------------------------------------------------------- */ /* Re-invert the features to restore to original state */ @@ -2130,6 +2132,156 @@ static int TestOGRLayerRandomWrite(OGRLayer *poLayer) printf("ERROR: Attempt to restore SetFeature(4) failed.\n"); } + /* -------------------------------------------------------------------- */ + /* Test UpdateFeature() */ + /* -------------------------------------------------------------------- */ + + if (bRet) + { + int nOldVal = 0; + std::string osOldVal; + int iUpdatedFeature = -1; + int iUpdatedField = -1; + std::unique_ptr poUpdatedFeature; + + for (int iFeature = 0; iUpdatedFeature < 0 && iFeature < 5; iFeature++) + { + for (int iField = 0; + iField < poLayer->GetLayerDefn()->GetFieldCount(); ++iField) + { + if (papoFeatures[iFeature]->IsFieldSetAndNotNull(iField)) + { + if (poLayer->GetLayerDefn() + ->GetFieldDefn(iField) + ->GetType() == OFTInteger) + { + iUpdatedFeature = iFeature; + iUpdatedField = iField; + nOldVal = + papoFeatures[iFeature]->GetFieldAsInteger(iField); + poUpdatedFeature.reset(papoFeatures[iFeature]->Clone()); + poUpdatedFeature->SetField(iField, 0xBEEF); + break; + } + else if (poLayer->GetLayerDefn() + ->GetFieldDefn(iField) + ->GetType() == OFTString) + { + iUpdatedFeature = iFeature; + iUpdatedField = iField; + osOldVal = + papoFeatures[iFeature]->GetFieldAsString(iField); + poUpdatedFeature.reset(papoFeatures[iFeature]->Clone()); + poUpdatedFeature->SetField(iField, "0xBEEF"); + break; + } + } + } + } + + if (poUpdatedFeature) + { + if (LOG_ACTION(poLayer->UpdateFeature(poUpdatedFeature.get(), 1, + &iUpdatedField, 0, nullptr, + false)) != OGRERR_NONE) + { + bRet = FALSE; + printf("ERROR: UpdateFeature() failed.\n"); + } + if (bRet) + { + LOG_ACTION(poLayer->ResetReading()); + for (int iFeature = 0; iFeature < 5; iFeature++) + { + auto poFeature = std::unique_ptr(LOG_ACTION( + poLayer->GetFeature(papoFeatures[iFeature]->GetFID()))); + if (iFeature != iUpdatedFeature) + { + if (!poFeature || + !poFeature->Equal(papoFeatures[iFeature])) + { + bRet = false; + printf("ERROR: UpdateFeature() test: " + "!poFeature->Equals(papoFeatures[iFeature]) " + "unexpected.\n"); + if (poFeature) + poFeature->DumpReadable(stdout); + papoFeatures[iFeature]->DumpReadable(stdout); + } + } + } + + auto poFeature = + std::unique_ptr(LOG_ACTION(poLayer->GetFeature( + papoFeatures[iUpdatedFeature]->GetFID()))); + if (!poFeature) + { + bRet = FALSE; + printf("ERROR: at line %d", __LINE__); + goto end; + } + if (poLayer->GetLayerDefn() + ->GetFieldDefn(iUpdatedField) + ->GetType() == OFTInteger) + { + if (poFeature->GetFieldAsInteger(iUpdatedField) != 0xBEEF) + { + bRet = FALSE; + printf("ERROR: Did not get expected field value after " + "UpdateFeature().\n"); + } + poFeature->SetField(iUpdatedField, nOldVal); + } + else if (poLayer->GetLayerDefn() + ->GetFieldDefn(iUpdatedField) + ->GetType() == OFTString) + { + if (!EQUAL(poFeature->GetFieldAsString(iUpdatedField), + "0xBEEF")) + { + bRet = FALSE; + printf("ERROR: Did not get expected field value after " + "UpdateFeature().\n"); + } + poFeature->SetField(iUpdatedField, osOldVal.c_str()); + } + else + { + CPLAssert(false); + } + + if (LOG_ACTION(poLayer->UpdateFeature( + poFeature.get(), 1, &iUpdatedField, 0, nullptr, + false)) != OGRERR_NONE) + { + bRet = FALSE; + printf("ERROR: UpdateFeature() failed.\n"); + } + + poFeature.reset(LOG_ACTION(poLayer->GetFeature( + papoFeatures[iUpdatedFeature]->GetFID()))); + if (!poFeature) + { + bRet = FALSE; + printf("ERROR: at line %d", __LINE__); + goto end; + } + if (!poFeature->Equal(papoFeatures[iUpdatedFeature])) + { + bRet = false; + printf("ERROR: UpdateFeature() test: " + "!poFeature->Equals(papoFeatures[iUpdatedFeature]) " + "unexpected.\n"); + } + } + } + else + { + if (bVerbose) + printf("INFO: Could not test UpdateFeature().\n"); + } + } + end: /* -------------------------------------------------------------------- */ /* Cleanup. */ From 5453f250f5327ad9c5af4fbd18346ff2682d5a88 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 18:24:36 +0200 Subject: [PATCH 0152/1119] GeoJSON/ODS/XLSX: declare missing capabilities DELETE_FIELD, REORDER_FIELDS, ALTER_FIELD_DEFN_FLAGS --- ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp | 3 +++ ogr/ogrsf_frmts/ods/ogrodsdriver.cpp | 3 +++ ogr/ogrsf_frmts/xlsx/ogrxlsxdriver.cpp | 3 +++ 3 files changed, 9 insertions(+) diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp index 74504d7d6edf..6273ebc05f98 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsondriver.cpp @@ -688,6 +688,9 @@ void RegisterOGRGeoJSON() poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES"); + poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES"); + poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES"); + poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS, "Name Type"); poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES"); poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "GeoJSON"); poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "json geojson"); diff --git a/ogr/ogrsf_frmts/ods/ogrodsdriver.cpp b/ogr/ogrsf_frmts/ods/ogrodsdriver.cpp index 9e04758d4955..247a621f2f99 100644 --- a/ogr/ogrsf_frmts/ods/ogrodsdriver.cpp +++ b/ogr/ogrsf_frmts/ods/ogrodsdriver.cpp @@ -251,6 +251,9 @@ void RegisterOGRODS() poDriver->SetMetadataItem(GDAL_DCAP_CURVE_GEOMETRIES, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES"); + poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES"); + poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES"); + poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS, "Name Type"); poDriver->SetMetadataItem(GDAL_DMD_SUPPORTED_SQL_DIALECTS, "OGRSQL SQLITE"); poDriver->SetMetadataItem( diff --git a/ogr/ogrsf_frmts/xlsx/ogrxlsxdriver.cpp b/ogr/ogrsf_frmts/xlsx/ogrxlsxdriver.cpp index f89c5125fad0..2a6846bb38d6 100644 --- a/ogr/ogrsf_frmts/xlsx/ogrxlsxdriver.cpp +++ b/ogr/ogrsf_frmts/xlsx/ogrxlsxdriver.cpp @@ -243,6 +243,9 @@ void RegisterOGRXLSX() poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_CREATE_FIELD, "YES"); + poDriver->SetMetadataItem(GDAL_DCAP_DELETE_FIELD, "YES"); + poDriver->SetMetadataItem(GDAL_DCAP_REORDER_FIELDS, "YES"); + poDriver->SetMetadataItem(GDAL_DMD_ALTER_FIELD_DEFN_FLAGS, "Name Type"); poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "MS Office Open XML spreadsheet"); From 1d026f5867d8fcf6326ffc58812c1ef89cef1c94 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 18:25:44 +0200 Subject: [PATCH 0153/1119] ODS/XLSX: fix CreateFeature() implementation when a FID is set --- autotest/ogr/ogr_ods.py | 22 ++++++ autotest/ogr/ogr_xlsx.py | 22 ++++++ ogr/ogrsf_frmts/mem/ogr_mem.h | 1 + ogr/ogrsf_frmts/mem/ogrmemlayer.cpp | 3 +- ogr/ogrsf_frmts/ods/ogr_ods.h | 9 +-- ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp | 79 +++++++++++++++++--- ogr/ogrsf_frmts/xlsx/ogr_xlsx.h | 3 + ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp | 85 +++++++++++++++++----- 8 files changed, 187 insertions(+), 37 deletions(-) diff --git a/autotest/ogr/ogr_ods.py b/autotest/ogr/ogr_ods.py index 8b95b0ff278d..10c8c66e6e65 100755 --- a/autotest/ogr/ogr_ods.py +++ b/autotest/ogr/ogr_ods.py @@ -258,6 +258,28 @@ def test_ogr_ods_4(): assert ret.find("INFO") != -1 and ret.find("ERROR") == -1 +############################################################################### +# Run test_ogrsf + + +def test_ogr_ods_test_ogrsf_update(tmp_path): + + import test_cli_utilities + + if test_cli_utilities.get_test_ogrsf_path() is None: + pytest.skip() + + filename = str(tmp_path / "out.ods") + gdal.VectorTranslate(filename, "data/poly.shp", format="ODS") + + ret = gdaltest.runexternal( + test_cli_utilities.get_test_ogrsf_path() + f" {filename}" + ) + + assert "INFO" in ret + assert "ERROR" not in ret + + ############################################################################### # Test write support diff --git a/autotest/ogr/ogr_xlsx.py b/autotest/ogr/ogr_xlsx.py index 120e2f471567..197563a1c964 100755 --- a/autotest/ogr/ogr_xlsx.py +++ b/autotest/ogr/ogr_xlsx.py @@ -189,6 +189,28 @@ def test_ogr_xlsx_4(): assert ret.find("INFO") != -1 and ret.find("ERROR") == -1 +############################################################################### +# Run test_ogrsf + + +def test_ogr_xlsx_test_ogrsf_update(tmp_path): + + import test_cli_utilities + + if test_cli_utilities.get_test_ogrsf_path() is None: + pytest.skip() + + filename = str(tmp_path / "out.xlsx") + gdal.VectorTranslate(filename, "data/poly.shp", format="XLSX") + + ret = gdaltest.runexternal( + test_cli_utilities.get_test_ogrsf_path() + f" {filename}" + ) + + assert "INFO" in ret + assert "ERROR" not in ret + + ############################################################################### # Test write support diff --git a/ogr/ogrsf_frmts/mem/ogr_mem.h b/ogr/ogrsf_frmts/mem/ogr_mem.h index eb14dd08127b..8ee3d34e788a 100644 --- a/ogr/ogrsf_frmts/mem/ogr_mem.h +++ b/ogr/ogrsf_frmts/mem/ogr_mem.h @@ -76,6 +76,7 @@ class CPL_DLL OGRMemLayer CPL_NON_FINAL : public OGRLayer // doesn't change. IOGRMemLayerFeatureIterator *GetIterator(); + protected: OGRFeature *GetFeatureRef(GIntBig nFeatureId); public: diff --git a/ogr/ogrsf_frmts/mem/ogrmemlayer.cpp b/ogr/ogrsf_frmts/mem/ogrmemlayer.cpp index a24edae8a877..0bc25abc0145 100644 --- a/ogr/ogrsf_frmts/mem/ogrmemlayer.cpp +++ b/ogr/ogrsf_frmts/mem/ogrmemlayer.cpp @@ -439,7 +439,8 @@ OGRErr OGRMemLayer::ICreateFeature(OGRFeature *poFeature) } } - return ISetFeature(poFeature); + // Prevent calling ISetFeature() from derived classes + return OGRMemLayer::ISetFeature(poFeature); } /************************************************************************/ diff --git a/ogr/ogrsf_frmts/ods/ogr_ods.h b/ogr/ogrsf_frmts/ods/ogr_ods.h index c0f57f710dc0..de6ce36eec82 100644 --- a/ogr/ogrsf_frmts/ods/ogr_ods.h +++ b/ogr/ogrsf_frmts/ods/ogr_ods.h @@ -55,6 +55,9 @@ class OGRODSLayer final : public OGRMemLayer bool bHasHeaderLine; OGRFeatureQuery *m_poAttrQueryODS; + GIntBig TranslateFIDFromMemLayer(GIntBig nFID) const; + GIntBig TranslateFIDToMemLayer(GIntBig nFID) const; + public: OGRODSLayer(OGRODSDataSource *poDSIn, const char *pszName, bool bUpdateIn = FALSE); @@ -116,11 +119,7 @@ class OGRODSLayer final : public OGRMemLayer return OGRMemLayer::ISetFeature(poFeature); } - OGRErr ICreateFeature(OGRFeature *poFeature) override - { - SetUpdated(); - return OGRMemLayer::ICreateFeature(poFeature); - } + OGRErr ICreateFeature(OGRFeature *poFeature) override; virtual OGRErr CreateField(const OGRFieldDefn *poField, int bApproxOK = TRUE) override diff --git a/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp b/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp index 2340e9122529..e158ebb13c20 100644 --- a/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp +++ b/ogr/ogrsf_frmts/ods/ogrodsdatasource.cpp @@ -111,6 +111,28 @@ OGRErr OGRODSLayer::SyncToDisk() return OGRERR_NONE; } +/************************************************************************/ +/* TranslateFIDFromMemLayer() */ +/************************************************************************/ + +// Translate a FID from MEM convention (0-based) to ODS convention +GIntBig OGRODSLayer::TranslateFIDFromMemLayer(GIntBig nFID) const +{ + return nFID + (1 + (bHasHeaderLine ? 1 : 0)); +} + +/************************************************************************/ +/* TranslateFIDToMemLayer() */ +/************************************************************************/ + +// Translate a FID from ODS convention to MEM convention (0-based) +GIntBig OGRODSLayer::TranslateFIDToMemLayer(GIntBig nFID) const +{ + if (nFID > 0) + return nFID - (1 + (bHasHeaderLine ? 1 : 0)); + return OGRNullFID; +} + /************************************************************************/ /* GetNextFeature() */ /************************************************************************/ @@ -122,7 +144,7 @@ OGRFeature *OGRODSLayer::GetNextFeature() OGRFeature *poFeature = OGRMemLayer::GetNextFeature(); if (poFeature == nullptr) return nullptr; - poFeature->SetFID(poFeature->GetFID() + 1 + (bHasHeaderLine ? 1 : 0)); + poFeature->SetFID(TranslateFIDFromMemLayer(poFeature->GetFID())); if (m_poAttrQueryODS == nullptr || m_poAttrQueryODS->Evaluate(poFeature)) { @@ -139,7 +161,7 @@ OGRFeature *OGRODSLayer::GetNextFeature() OGRFeature *OGRODSLayer::GetFeature(GIntBig nFeatureId) { OGRFeature *poFeature = - OGRMemLayer::GetFeature(nFeatureId - (1 + (bHasHeaderLine ? 1 : 0))); + OGRMemLayer::GetFeature(TranslateFIDToMemLayer(nFeatureId)); if (poFeature) poFeature->SetFID(nFeatureId); return poFeature; @@ -162,12 +184,21 @@ GIntBig OGRODSLayer::GetFeatureCount(int bForce) OGRErr OGRODSLayer::ISetFeature(OGRFeature *poFeature) { - GIntBig nFID = poFeature->GetFID(); - if (nFID != OGRNullFID) - poFeature->SetFID(nFID - (1 + (bHasHeaderLine ? 1 : 0))); + const GIntBig nFIDOrigin = poFeature->GetFID(); + if (nFIDOrigin > 0) + { + const GIntBig nFIDMemLayer = TranslateFIDToMemLayer(nFIDOrigin); + if (!GetFeatureRef(nFIDMemLayer)) + return OGRERR_NON_EXISTING_FEATURE; + poFeature->SetFID(nFIDMemLayer); + } + else + { + return OGRERR_NON_EXISTING_FEATURE; + } SetUpdated(); OGRErr eErr = OGRMemLayer::ISetFeature(poFeature); - poFeature->SetFID(nFID); + poFeature->SetFID(nFIDOrigin); return eErr; } @@ -182,14 +213,40 @@ OGRErr OGRODSLayer::IUpdateFeature(OGRFeature *poFeature, const int *panUpdatedGeomFieldsIdx, bool bUpdateStyleString) { - GIntBig nFID = poFeature->GetFID(); - if (nFID != OGRNullFID) - poFeature->SetFID(nFID - (1 + (bHasHeaderLine ? 1 : 0))); + const GIntBig nFIDOrigin = poFeature->GetFID(); + if (nFIDOrigin != OGRNullFID) + poFeature->SetFID(TranslateFIDToMemLayer(nFIDOrigin)); SetUpdated(); OGRErr eErr = OGRMemLayer::IUpdateFeature( poFeature, nUpdatedFieldsCount, panUpdatedFieldsIdx, nUpdatedGeomFieldsCount, panUpdatedGeomFieldsIdx, bUpdateStyleString); - poFeature->SetFID(nFID); + poFeature->SetFID(nFIDOrigin); + return eErr; +} + +/************************************************************************/ +/* ICreateFeature() */ +/************************************************************************/ + +OGRErr OGRODSLayer::ICreateFeature(OGRFeature *poFeature) +{ + const GIntBig nFIDOrigin = poFeature->GetFID(); + if (nFIDOrigin > 0) + { + const GIntBig nFIDModified = TranslateFIDToMemLayer(nFIDOrigin); + if (GetFeatureRef(nFIDModified)) + { + SetUpdated(); + poFeature->SetFID(nFIDModified); + OGRErr eErr = OGRMemLayer::ISetFeature(poFeature); + poFeature->SetFID(nFIDOrigin); + return eErr; + } + } + SetUpdated(); + poFeature->SetFID(OGRNullFID); + OGRErr eErr = OGRMemLayer::ICreateFeature(poFeature); + poFeature->SetFID(TranslateFIDFromMemLayer(poFeature->GetFID())); return eErr; } @@ -200,7 +257,7 @@ OGRErr OGRODSLayer::IUpdateFeature(OGRFeature *poFeature, OGRErr OGRODSLayer::DeleteFeature(GIntBig nFID) { SetUpdated(); - return OGRMemLayer::DeleteFeature(nFID - (1 + (bHasHeaderLine ? 1 : 0))); + return OGRMemLayer::DeleteFeature(TranslateFIDToMemLayer(nFID)); } /************************************************************************/ diff --git a/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h b/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h index eb8325740c9a..f8b61cadc0d9 100644 --- a/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h +++ b/ogr/ogrsf_frmts/xlsx/ogr_xlsx.h @@ -62,6 +62,9 @@ class OGRXLSXLayer final : public OGRMemLayer std::string m_osCols{}; std::set oSetFieldsOfUnknownType{}; + GIntBig TranslateFIDFromMemLayer(GIntBig nFID) const; + GIntBig TranslateFIDToMemLayer(GIntBig nFID) const; + public: OGRXLSXLayer(OGRXLSXDataSource *poDSIn, const char *pszFilename, const char *pszName, int bUpdateIn = FALSE); diff --git a/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp b/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp index 82b834afde51..7222ca2ef468 100644 --- a/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp +++ b/ogr/ogrsf_frmts/xlsx/ogrxlsxdatasource.cpp @@ -96,6 +96,28 @@ OGRErr OGRXLSXLayer::SyncToDisk() return OGRERR_NONE; } +/************************************************************************/ +/* TranslateFIDFromMemLayer() */ +/************************************************************************/ + +// Translate a FID from MEM convention (0-based) to XLSX convention +GIntBig OGRXLSXLayer::TranslateFIDFromMemLayer(GIntBig nFID) const +{ + return nFID + (1 + (bHasHeaderLine ? 1 : 0)); +} + +/************************************************************************/ +/* TranslateFIDToMemLayer() */ +/************************************************************************/ + +// Translate a FID from XLSX convention to MEM convention (0-based) +GIntBig OGRXLSXLayer::TranslateFIDToMemLayer(GIntBig nFID) const +{ + if (nFID > 0) + return nFID - (1 + (bHasHeaderLine ? 1 : 0)); + return OGRNullFID; +} + /************************************************************************/ /* GetNextFeature() */ /************************************************************************/ @@ -103,16 +125,21 @@ OGRErr OGRXLSXLayer::SyncToDisk() OGRFeature *OGRXLSXLayer::GetNextFeature() { Init(); + OGRFeature *poFeature = OGRMemLayer::GetNextFeature(); if (poFeature) - poFeature->SetFID(poFeature->GetFID() + 1 + - static_cast(bHasHeaderLine)); + poFeature->SetFID(TranslateFIDFromMemLayer(poFeature->GetFID())); return poFeature; } +/************************************************************************/ +/* CreateField() */ +/************************************************************************/ + OGRErr OGRXLSXLayer::CreateField(const OGRFieldDefn *poField, int bApproxOK) { Init(); + if (GetLayerDefn()->GetFieldCount() >= 2000) { CPLError(CE_Failure, CPLE_AppDefined, @@ -130,8 +157,9 @@ OGRErr OGRXLSXLayer::CreateField(const OGRFieldDefn *poField, int bApproxOK) OGRFeature *OGRXLSXLayer::GetFeature(GIntBig nFeatureId) { Init(); - OGRFeature *poFeature = OGRMemLayer::GetFeature( - nFeatureId - (1 + static_cast(bHasHeaderLine))); + + OGRFeature *poFeature = + OGRMemLayer::GetFeature(TranslateFIDToMemLayer(nFeatureId)); if (poFeature) poFeature->SetFID(nFeatureId); return poFeature; @@ -144,12 +172,22 @@ OGRFeature *OGRXLSXLayer::GetFeature(GIntBig nFeatureId) OGRErr OGRXLSXLayer::ISetFeature(OGRFeature *poFeature) { Init(); - GIntBig nFID = poFeature->GetFID(); - if (nFID != OGRNullFID) - poFeature->SetFID(nFID - (1 + static_cast(bHasHeaderLine))); + + const GIntBig nFIDOrigin = poFeature->GetFID(); + if (nFIDOrigin > 0) + { + const GIntBig nFIDMemLayer = TranslateFIDToMemLayer(nFIDOrigin); + if (!GetFeatureRef(nFIDMemLayer)) + return OGRERR_NON_EXISTING_FEATURE; + poFeature->SetFID(nFIDMemLayer); + } + else + { + return OGRERR_NON_EXISTING_FEATURE; + } SetUpdated(); OGRErr eErr = OGRMemLayer::ISetFeature(poFeature); - poFeature->SetFID(nFID); + poFeature->SetFID(nFIDOrigin); return eErr; } @@ -165,14 +203,15 @@ OGRErr OGRXLSXLayer::IUpdateFeature(OGRFeature *poFeature, bool bUpdateStyleString) { Init(); - GIntBig nFID = poFeature->GetFID(); - if (nFID != OGRNullFID) - poFeature->SetFID(nFID - (1 + static_cast(bHasHeaderLine))); + + const GIntBig nFIDOrigin = poFeature->GetFID(); + if (nFIDOrigin != OGRNullFID) + poFeature->SetFID(TranslateFIDToMemLayer(nFIDOrigin)); SetUpdated(); OGRErr eErr = OGRMemLayer::IUpdateFeature( poFeature, nUpdatedFieldsCount, panUpdatedFieldsIdx, nUpdatedGeomFieldsCount, panUpdatedGeomFieldsIdx, bUpdateStyleString); - poFeature->SetFID(nFID); + poFeature->SetFID(nFIDOrigin); return eErr; } @@ -184,16 +223,23 @@ OGRErr OGRXLSXLayer::ICreateFeature(OGRFeature *poFeature) { Init(); - GIntBig nFID = poFeature->GetFID(); - if (nFID != OGRNullFID) + const GIntBig nFIDOrigin = poFeature->GetFID(); + if (nFIDOrigin > 0) { - // Compensate what ISetFeature() will do since - // OGRMemLayer::ICreateFeature() will eventually call it - poFeature->SetFID(nFID + (1 + static_cast(bHasHeaderLine))); + const GIntBig nFIDModified = TranslateFIDToMemLayer(nFIDOrigin); + if (GetFeatureRef(nFIDModified)) + { + SetUpdated(); + poFeature->SetFID(nFIDModified); + OGRErr eErr = OGRMemLayer::ISetFeature(poFeature); + poFeature->SetFID(nFIDOrigin); + return eErr; + } } SetUpdated(); + poFeature->SetFID(OGRNullFID); OGRErr eErr = OGRMemLayer::ICreateFeature(poFeature); - poFeature->SetFID(nFID); + poFeature->SetFID(TranslateFIDFromMemLayer(poFeature->GetFID())); return eErr; } @@ -205,8 +251,7 @@ OGRErr OGRXLSXLayer::DeleteFeature(GIntBig nFID) { Init(); SetUpdated(); - return OGRMemLayer::DeleteFeature(nFID - - (1 + static_cast(bHasHeaderLine))); + return OGRMemLayer::DeleteFeature(TranslateFIDToMemLayer(nFID)); } /************************************************************************/ From 48eca313f5a335a3bf5d282a96c4cb6b1c4e96f7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 18:26:05 +0200 Subject: [PATCH 0154/1119] ogr_geojson.py: run test_ogrsf in update mode --- autotest/ogr/ogr_geojson.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index b5b53bb61cbc..96a5c106a55f 100755 --- a/autotest/ogr/ogr_geojson.py +++ b/autotest/ogr/ogr_geojson.py @@ -4308,6 +4308,28 @@ def test_ogr_geojson_test_ogrsf(): assert "ERROR" not in ret +############################################################################### +# Run test_ogrsf + + +def test_ogr_geojson_test_ogrsf_update(tmp_path): + + import test_cli_utilities + + if test_cli_utilities.get_test_ogrsf_path() is None: + pytest.skip() + + filename = str(tmp_path / "out.json") + gdal.VectorTranslate(filename, "data/poly.shp", format="GeoJSON") + + ret = gdaltest.runexternal( + test_cli_utilities.get_test_ogrsf_path() + f" {filename}" + ) + + assert "INFO" in ret + assert "ERROR" not in ret + + ############################################################################### # Test fix for https://github.com/OSGeo/gdal/issues/7313 From 785223dc20d2593e075c860c3e61a7c7bd8d6df9 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 20:06:49 +0200 Subject: [PATCH 0155/1119] Revert "CI: disable running Windows tests to workaround issue with too old vcruntime140.dll on image windows-latest 20240603.1.0" This reverts commit 839374d3e73af02af8a796509d44254db0c6b8af. --- .github/workflows/cmake_builds.yml | 58 ++++++++++++++---------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index 9c5bbd2481b5..95ea167e078a 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -481,34 +481,29 @@ jobs: run: cmake --build $GITHUB_WORKSPACE/build --config Release -j 2 env: GIT_LFS_SKIP_SMUDGE: 1 # for PublicDecompWT github repository clone - # FIXME !! Disabled because of actions/runner-images#10004 - #- name: test - # shell: bash -l {0} - # run: | - # cmake --build $GITHUB_WORKSPACE/build --config Release --target quicktest - #- name: test (with ctest) - # shell: bash -l {0} - # run: | - # ctest --test-dir $GITHUB_WORKSPACE/build -C Release -V -j 3 - # env: - # SKIP_OGR_GMLAS_HUGE_PROCESSING_TIME: YES - # SKIP_OGR_GMLAS_HTTP_RELATED: YES - # SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES - # BUILD_NAME: "build-windows-conda" + - name: test + shell: bash -l {0} + run: | + cmake --build $GITHUB_WORKSPACE/build --config Release --target quicktest + - name: test (with ctest) + shell: bash -l {0} + run: | + ctest --test-dir $GITHUB_WORKSPACE/build -C Release -V -j 3 + env: + SKIP_OGR_GMLAS_HUGE_PROCESSING_TIME: YES + SKIP_OGR_GMLAS_HTTP_RELATED: YES + SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES + BUILD_NAME: "build-windows-conda" - name: Install shell: bash -l {0} run: | cmake --build $GITHUB_WORKSPACE/build --config Release --target install - # FIXME !! Disabled because of actions/runner-images#10004 - #- name: Test install - # shell: bash -l {0} - # run: | - # export PATH=$GITHUB_WORKSPACE/install-gdal/bin:$PATH - # gdalinfo --version - # python -VV - # PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages python -c "from osgeo import gdal;print(gdal.VersionInfo(None))" - # export PATH=$GITHUB_WORKSPACE/install-gdal/Scripts:$PATH - # PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages gdal_edit --version + export PATH=$GITHUB_WORKSPACE/install-gdal/bin:$PATH + gdalinfo --version + python -VV + PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages python -c "from osgeo import gdal;print(gdal.VersionInfo(None))" + export PATH=$GITHUB_WORKSPACE/install-gdal/Scripts:$PATH + PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages gdal_edit --version - name: Show gdal.pc shell: bash -l {0} run: cat $GITHUB_WORKSPACE/build/gdal.pc @@ -589,14 +584,13 @@ jobs: - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 - # FIXME !! Disable tests because of actions/runner-images#10004 - #- name: test (with ctest) - # shell: bash -l {0} - # run: | - # ctest --test-dir $GITHUB_WORKSPACE/build -C RelWithDebInfo -V -j 3 - # env: - # SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES - # BUILD_NAME: "build-windows-minimum" + - name: test (with ctest) + shell: bash -l {0} + run: | + ctest --test-dir $GITHUB_WORKSPACE/build -C RelWithDebInfo -V -j 3 + env: + SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES + BUILD_NAME: "build-windows-minimum" - name: Show gdal.pc shell: bash -l {0} run: cat $GITHUB_WORKSPACE/build/gdal.pc From c9518088d834d3d765dc3ca12a8b4d8e5ddd07b2 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 20:06:51 +0200 Subject: [PATCH 0156/1119] Revert "CI: another attempt at fixing issues with too old vcruntime140.dll on image windows-latest 20240603.1.0" This reverts commit b1890d703b650ebcedb7c56bc283a49c9833ebc5. --- .github/workflows/cmake_builds.yml | 49 ++++----------------------- cmake/helpers/GdalSetRuntimeEnv.cmake | 3 +- 2 files changed, 7 insertions(+), 45 deletions(-) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index 95ea167e078a..be406c3b5fe5 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -399,17 +399,6 @@ jobs: generator: Ninja cache-name: cmake-msvc steps: - - - name: Copy in the correct VC runtime DLLs (workaround for actions/runner-images#10004) - shell: powershell - run: | - mkdir c:\vcruntime - Copy-Item (Join-Path ` - ((Get-ChildItem -Directory ` - -Path "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Redist\MSVC\14.*" | - Sort -Descending | Select-Object -First 1).FullName - ) 'x64\Microsoft.VC143.CRT\*.dll') "c:\vcruntime" - # To avoid git clone to mess with the line endings of GDAL autotest data # files that look like text, but should be handled as binary content - name: Set git core.autocrlf to false @@ -458,14 +447,6 @@ jobs: rm -rf C:/Strawberry || /bin/true rm -rf "C:/Program Files/OpenSSL/lib" || /bin/true - - name: Set path for the correct VC runtime DLLs (workaround for actions/runner-images#10004) - shell: powershell - run: | - $env:PATH="c:\vcruntime;$env:PATH" - echo "PATH=$env:PATH" >> $env:GITHUB_ENV - echo ((Get-Command vcruntime140.dll).Path) - echo ((Get-Command vcruntime140.dll).Version) - - name: Configure shell: bash -l {0} # Disable MySQL because of "error LNK2038: mismatch detected for '_MSC_VER': value '1800' doesn't match value '1900' in ogrmysqldatasource.obj" and other errors @@ -473,9 +454,10 @@ jobs: # otherwise interpret /bla has a file relative to the Bash root directory and would replace it by a path like c:\Program Files\git\WX # BUILD_JAVA_BINDINGS=OFF because we get "Error occurred during initialization of VM. Corrupted ZIP library: C:\Miniconda\envs\gdalenv\Library\bin\zip.dll" when running java. Not reproducible on a standard VM # Build PDF driver as plugin due to the PDFium build including libopenjp2 symbols which would conflict with external libopenjp2 + # /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR to workaround issue with too old vcruntime140.dll on image 20240603.1.0 (https://github.com/actions/runner-images/issues/10004) run: | mkdir -p $GITHUB_WORKSPACE/build - cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Release -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON + cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Release -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config Release -j 2 @@ -520,17 +502,6 @@ jobs: generator: Visual Studio 17 2022 GDAL_PYTHON_BINDINGS_WITHOUT_NUMPY: YES steps: - - - name: Copy in the correct VC runtime DLLs (workaround for actions/runner-images#10004) - shell: powershell - run: | - mkdir c:\vcruntime - Copy-Item (Join-Path ` - ((Get-ChildItem -Directory ` - -Path "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Redist\MSVC\14.*" | - Sort -Descending | Select-Object -First 1).FullName - ) 'x64\Microsoft.VC143.CRT\*.dll') "c:\vcruntime" - # To avoid git clone to mess with the line endings of GDAL autotest data # files that look like text, but should be handled as binary content - name: Set git core.autocrlf to false @@ -554,20 +525,12 @@ jobs: shell: bash -l {0} run: | cmake --version - - - name: Set path for the correct VC runtime DLLs (workaround for actions/runner-images#10004) - shell: powershell - run: | - $env:PATH="c:\vcruntime;$env:PATH" - echo "PATH=$env:PATH" >> $env:GITHUB_ENV - echo ((Get-Command vcruntime140.dll).Path) - echo ((Get-Command vcruntime140.dll).Version) - - name: Configure shell: bash -l {0} + # /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR to workaround issue with too old vcruntime140.dll on image 20240603.1.0 (https://github.com/actions/runner-images/issues/10004) run: | mkdir -p $GITHUB_WORKSPACE/build - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 @@ -575,12 +538,12 @@ jobs: shell: bash -l {0} run: | rm -f build/CMakeCache.txt - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Configure with even less dependencies, and disabling all optional drivers shell: bash -l {0} run: | rm -f build/CMakeCache.txt - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF -DOGR_BUILD_OPTIONAL_DRIVERS=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF -DOGR_BUILD_OPTIONAL_DRIVERS=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 diff --git a/cmake/helpers/GdalSetRuntimeEnv.cmake b/cmake/helpers/GdalSetRuntimeEnv.cmake index 766f2442dea9..e8152d949d08 100644 --- a/cmake/helpers/GdalSetRuntimeEnv.cmake +++ b/cmake/helpers/GdalSetRuntimeEnv.cmake @@ -33,8 +33,7 @@ function(gdal_set_runtime_env res) if (GTEST_DIR) set(SEP_GTEST_DIR "\\;${GTEST_DIR}") endif() - # c:\vcruntime is a hack for actions/runner-images#10004 (cf .github/workflows/cmake_builds.yml) - list(APPEND RUNTIME_ENV "PATH=c:\\vcruntime\\;${GDAL_OUTPUT_DIR}\\;${GDAL_APPS_OUTPUT_DIR_WITH_SEP}${NATIVE_GDAL_PYTHON_SCRIPTS_DIR_WITH_SEP}${PATH_ESCAPED}${SEP_GTEST_DIR}") + list(APPEND RUNTIME_ENV "PATH=${GDAL_OUTPUT_DIR}\\;${GDAL_APPS_OUTPUT_DIR_WITH_SEP}${NATIVE_GDAL_PYTHON_SCRIPTS_DIR_WITH_SEP}${PATH_ESCAPED}${SEP_GTEST_DIR}") else () if (Python_Interpreter_FOUND) set(GDAL_PYTHON_SCRIPTS_DIR_WITH_SEP "${GDAL_PYTHON_SCRIPTS_DIR}:") From e17cc8e38219a6b94bd6a72363df0c0007164f2d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 20:06:52 +0200 Subject: [PATCH 0157/1119] Revert "CI: workaround issue with too old vcruntime140.dll on image windows-latest 20240603.1.0" This reverts commit 5a9ef441292b5a92ab58fe54a9fcdab5b1e58bdd. --- .github/workflows/cmake_builds.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index be406c3b5fe5..c83846e711fe 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -454,10 +454,9 @@ jobs: # otherwise interpret /bla has a file relative to the Bash root directory and would replace it by a path like c:\Program Files\git\WX # BUILD_JAVA_BINDINGS=OFF because we get "Error occurred during initialization of VM. Corrupted ZIP library: C:\Miniconda\envs\gdalenv\Library\bin\zip.dll" when running java. Not reproducible on a standard VM # Build PDF driver as plugin due to the PDFium build including libopenjp2 symbols which would conflict with external libopenjp2 - # /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR to workaround issue with too old vcruntime140.dll on image 20240603.1.0 (https://github.com/actions/runner-images/issues/10004) run: | mkdir -p $GITHUB_WORKSPACE/build - cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Release -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON + cmake -G "${generator}" -Werror=dev "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal" "-DUSE_CCACHE=ON" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DGDAL_ENABLE_PLUGINS:BOOL=ON -DGDAL_ENABLE_PLUGINS_NO_DEPS:BOOL=ON -DGDAL_USE_PUBLICDECOMPWT:BOOL=ON -DPUBLICDECOMPWT_URL=https://github.com/rouault/PublicDecompWT -DBUILD_JAVA_BINDINGS=OFF -DBUILD_CSHARP_BINDINGS=ON -DGDAL_USE_MYSQL:BOOL=OFF -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DWERROR_DEV_FLAG="-Werror=dev" -DCMAKE_BUILD_TYPE=Release -DPDFIUM_ROOT=$GITHUB_WORKSPACE/install-pdfium -DGDAL_ENABLE_DRIVER_PDF_PLUGIN:BOOL=ON -DCMAKE_UNITY_BUILD=ON - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config Release -j 2 @@ -527,10 +526,9 @@ jobs: cmake --version - name: Configure shell: bash -l {0} - # /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR to workaround issue with too old vcruntime140.dll on image 20240603.1.0 (https://github.com/actions/runner-images/issues/10004) run: | mkdir -p $GITHUB_WORKSPACE/build - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 @@ -538,12 +536,12 @@ jobs: shell: bash -l {0} run: | rm -f build/CMakeCache.txt - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Configure with even less dependencies, and disabling all optional drivers shell: bash -l {0} run: | rm -f build/CMakeCache.txt - cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX /D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF -DOGR_BUILD_OPTIONAL_DRIVERS=OFF -DWERROR_DEV_FLAG="-Werror=dev" + cmake -A ${architecture} -G "${generator}" "-DCMAKE_PREFIX_PATH=${CONDA}/envs/gdalenv" -Werror=dev "-DCMAKE_CXX_COMPILER_LAUNCHER=clcache" -DCMAKE_UNITY_BUILD=${CMAKE_UNITY_BUILD} -S "$GITHUB_WORKSPACE" -B "$GITHUB_WORKSPACE/build" -DCMAKE_C_FLAGS=" /WX" -DCMAKE_CXX_FLAGS=" /WX" -DGDAL_USE_EXTERNAL_LIBS:BOOL=OFF -DGDAL_USE_PNG_INTERNAL=OFF -DGDAL_USE_JPEG_INTERNAL=OFF -DGDAL_USE_JPEG12_INTERNAL=OFF -DGDAL_USE_GIF_INTERNAL=OFF -DGDAL_USE_LERC_INTERNAL=OFF -DGDAL_USE_LERCV1_INTERNAL=OFF -DGDAL_USE_QHULL_INTERNAL=OFF -DGDAL_USE_OPENCAD_INTERNAL=OFF -DGDAL_BUILD_OPTIONAL_DRIVERS=OFF -DOGR_BUILD_OPTIONAL_DRIVERS=OFF -DWERROR_DEV_FLAG="-Werror=dev" - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 From 95d092d2c59961b7580add8d8736434a6c43e587 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 12 Jun 2024 21:32:11 +0200 Subject: [PATCH 0158/1119] CI: build-windows-minimum: workaround crash in Java programs Try to work around https://github.com/actions/runner-images/issues/10055 --- .github/workflows/cmake_builds.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index c83846e711fe..b98a52c2a32c 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -545,6 +545,13 @@ jobs: - name: Build shell: bash -l {0} run: cmake --build $GITHUB_WORKSPACE/build --config RelWithDebInfo -j 2 + + # Works around https://github.com/actions/runner-images/issues/10055 + - name: Remove conflicting libraries + shell: bash -l {0} + run: | + find "C:/hostedtoolcache/windows/Java_Temurin-Hotspot_jdk" -name "msvcp140.dll" -exec rm {} \; + - name: test (with ctest) shell: bash -l {0} run: | From 19705546f3fc20e3ada67246be884c0c5b999673 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 13 Jun 2024 00:14:47 +0200 Subject: [PATCH 0159/1119] Fix crash in ogr2ogr (or Arrow based workflows) from GeoPackage/GeoParquet to PMTiles Fixes #10199 --- ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp | 22 ++++++++++++++-------- ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp | 2 ++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp b/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp index 4717db14cdd0..3ccdaf488df4 100644 --- a/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp +++ b/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp @@ -5862,10 +5862,13 @@ bool OGRLayer::CreateFieldFromArrowSchemaInternal( if (poDS) { auto poDriver = poDS->GetDriver(); - const char *pszMetadataItem = - poDriver->GetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES); - if (pszMetadataItem) - aosNativeTypes = CSLTokenizeString2(pszMetadataItem, " ", 0); + if (poDriver) + { + const char *pszMetadataItem = + poDriver->GetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES); + if (pszMetadataItem) + aosNativeTypes = CSLTokenizeString2(pszMetadataItem, " ", 0); + } } if (schema->dictionary && @@ -7374,10 +7377,13 @@ bool OGRLayer::WriteArrowBatch(const struct ArrowSchema *schema, if (poDS) { auto poDriver = poDS->GetDriver(); - const char *pszMetadataItem = - poDriver->GetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES); - if (pszMetadataItem) - aosNativeTypes = CSLTokenizeString2(pszMetadataItem, " ", 0); + if (poDriver) + { + const char *pszMetadataItem = + poDriver->GetMetadataItem(GDAL_DMD_CREATIONFIELDDATATYPES); + if (pszMetadataItem) + aosNativeTypes = CSLTokenizeString2(pszMetadataItem, " ", 0); + } } std::vector asFieldInfo; diff --git a/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp b/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp index 1a9d14dcec01..3302b5d938d9 100644 --- a/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp +++ b/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp @@ -6185,6 +6185,8 @@ GDALDataset *OGRMVTWriterDataset::Create(const char *pszFilename, int nXSize, } poDS->SetDescription(pszFilename); + poDS->poDriver = GDALDriver::FromHandle(GDALGetDriverByName("MVT")); + return poDS; } From 6a92d57622acdd5db16b2b00b5f89557073bb0de Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 13 Jun 2024 15:35:13 +0200 Subject: [PATCH 0160/1119] HDF5: make logic to assign per-band attribute more generic --- autotest/gdrivers/data/hdf5/fwhm.h5 | Bin 8192 -> 6144 bytes autotest/gdrivers/hdf5.py | 6 +++ frmts/hdf5/hdf5imagedataset.cpp | 59 ++++++++++++++++++---------- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/autotest/gdrivers/data/hdf5/fwhm.h5 b/autotest/gdrivers/data/hdf5/fwhm.h5 index d8112dfc5f3f5b1cdeb958f0a260730583e05a55..a38ee931a8c7e24054683db62262da662e906c6a 100644 GIT binary patch delta 203 zcmZp0XfT+d$tbZ=>jx7f_he<}sjNH<0SpY2YgvP31sDVvK%jyZOfe)dLTE6#S(n9^ zk=X;N^eMB000%?{qyVD-Dr>MR4?_f4n2|vkC@R5_oSIjXS`=TNSeBZTnwMUZQ4Exx z{FYT)Uk7UP1SkzQ11QM|bOA_bZe@IOerj4;W^yLbjN-}fSv5Ifj$zukQJ-;jx7f*JNepsk%Z891I}92*k5bJxvi{5CHQlSiuy-2?)i&!{EWd uGP#!3fdeAXz_?kL#g~ye04TbbIhd7)0mM4X8qCbVAh6ky<0|JO4gmmUD-(?X diff --git a/autotest/gdrivers/hdf5.py b/autotest/gdrivers/hdf5.py index 25fe8d22c970..873416a25e69 100755 --- a/autotest/gdrivers/hdf5.py +++ b/autotest/gdrivers/hdf5.py @@ -1305,6 +1305,8 @@ def test_hdf5_band_specific_attribute(): ds.attrs["fwhm"] = [0.01, 0.02] ds.attrs["fwhm_units"] = "Micrometers" ds.attrs["bad_band_list"] = [0, 1] + ds.attrs["center_wavelengths"] = [300, 400] + ds.attrs["my_coefficients"] = [1, 2] f.close() ds = gdal.Open("data/hdf5/fwhm.h5") @@ -1315,11 +1317,15 @@ def test_hdf5_band_specific_attribute(): "fwhm": "0.01", "fwhm_units": "Micrometers", "bad_band": "0", + "center_wavelength": "300", + "my_coefficient": "1", } assert ds.GetRasterBand(2).GetMetadata_Dict() == { "fwhm": "0.02", "fwhm_units": "Micrometers", "bad_band": "1", + "center_wavelength": "400", + "my_coefficient": "2", } ds = None diff --git a/frmts/hdf5/hdf5imagedataset.cpp b/frmts/hdf5/hdf5imagedataset.cpp index 4d140c523f28..fb9824068d9b 100644 --- a/frmts/hdf5/hdf5imagedataset.cpp +++ b/frmts/hdf5/hdf5imagedataset.cpp @@ -1224,34 +1224,53 @@ GDALDataset *HDF5ImageDataset::Open(GDALOpenInfo *poOpenInfo) { HDF5Dataset::CreateMetadata(poDS->m_hHDF5, poDS->poH5Objects, H5G_DATASET, false, aosMetadata); - if (nBands > 1) + if (nBands > 1 && poDS->nRasterXSize != nBands && + poDS->nRasterYSize != nBands) { - // Logic specific of Planet data cubes with per-band metadata items - static const struct + // Heuristics to detect non-scalar attributes, that are intended + // to be attached to a specific band. + const CPLStringList aosMetadataDup(aosMetadata); + for (const auto &[pszKey, pszValue] : + cpl::IterateNameValue(aosMetadataDup)) { - const char *pszSrcName; - const char *pszDstName; - } asItems[] = { - {"calibration_coefficients", "calibration_coefficient"}, - {"center_wavelengths", "center_wavelength"}, - {"fwhm", "fwhm"}, - {"bad_band_list", "bad_band"}, - }; - - for (const auto &sItem : asItems) - { - const char *pszVal = - aosMetadata.FetchNameValue(sItem.pszSrcName); - if (pszVal) + const hid_t hAttrID = H5Aopen_name(poDS->dataset_id, pszKey); + const hid_t hAttrSpace = H5Aget_space(hAttrID); + if (H5Sget_simple_extent_ndims(hAttrSpace) == 1 && + H5Sget_simple_extent_npoints(hAttrSpace) == nBands) { - CPLStringList aosTokens(CSLTokenizeString2(pszVal, " ", 0)); + CPLStringList aosTokens( + CSLTokenizeString2(pszValue, " ", 0)); if (aosTokens.size() == nBands) { - oMapBandSpecificMetadata[sItem.pszDstName] = + std::string osAttrName(pszKey); + if (osAttrName.size() > strlen("_coefficients") && + osAttrName.substr(osAttrName.size() - + strlen("_coefficients")) == + "_coefficients") + { + osAttrName.pop_back(); + } + else if (osAttrName.size() > strlen("_wavelengths") && + osAttrName.substr(osAttrName.size() - + strlen("_wavelengths")) == + "_wavelengths") + { + osAttrName.pop_back(); + } + else if (osAttrName.size() > strlen("_list") && + osAttrName.substr(osAttrName.size() - + strlen("_list")) == "_list") + { + osAttrName.resize(osAttrName.size() - + strlen("_list")); + } + oMapBandSpecificMetadata[osAttrName] = std::move(aosTokens); - aosMetadata.SetNameValue(sItem.pszSrcName, nullptr); + aosMetadata.SetNameValue(pszKey, nullptr); } } + H5Sclose(hAttrSpace); + H5Aclose(hAttrID); } } } From cab1cc95a3e9343d003a7e21acea4c3efe56b88d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 13 Jun 2024 16:06:54 +0200 Subject: [PATCH 0161/1119] OGRSpatialReference::importFromUSGS(): use plain WGS84 datum with WGS84 ellipsoid, and identify EPSG code --- autotest/gdrivers/fast.py | 19 ++++--------------- autotest/osr/osr_usgs.py | 18 ++++++++++++++++++ ogr/ogr_srs_usgs.cpp | 22 +++++++++++++++++++--- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/autotest/gdrivers/fast.py b/autotest/gdrivers/fast.py index 19b163946e77..7cc2dbe50969 100755 --- a/autotest/gdrivers/fast.py +++ b/autotest/gdrivers/fast.py @@ -32,7 +32,7 @@ import gdaltest import pytest -from osgeo import gdal +from osgeo import gdal, osr pytestmark = pytest.mark.require_driver("FAST") @@ -185,20 +185,9 @@ def test_fast_7(): gt = (676565.09, 5, 0, 5348341.5, 0, -5) # Expected definition of the projection - proj = """PROJCS["UTM Zone 32, Northern Hemisphere", - GEOGCS["Unknown datum based upon the WGS 84 ellipsoid", - DATUM["Not specified (based on WGS 84 spheroid)", - SPHEROID["WGS 84",6378137,298.257223563, - AUTHORITY["EPSG","7030"]]], - PRIMEM["Greenwich",0], - UNIT["degree",0.0174532925199433]], - PROJECTION["Transverse_Mercator"], - PARAMETER["latitude_of_origin",0], - PARAMETER["central_meridian",9], - PARAMETER["scale_factor",0.9996], - PARAMETER["false_easting",500000], - PARAMETER["false_northing",0], - UNIT["Meter",1]]""" + srs = osr.SpatialReference() + srs.ImportFromEPSG(32632) + proj = srs.ExportToWkt() tst.testOpen(check_gt=gt, check_prj=proj) diff --git a/autotest/osr/osr_usgs.py b/autotest/osr/osr_usgs.py index 7b614c445a3b..04d8da586e98 100755 --- a/autotest/osr/osr_usgs.py +++ b/autotest/osr/osr_usgs.py @@ -113,3 +113,21 @@ def test_osr_usgs_2(): and gdal.PackedDMSToDec(params[4]) == pytest.approx(-117.4745429, abs=0.0000005) and gdal.PackedDMSToDec(params[5]) == pytest.approx(33.76446203, abs=0.0000005) ), "Can not import Lambert Conformal Conic projection." + + +############################################################################### +# Test the osr.SpatialReference.ImportFromUSGS() function with WGS 84 +# + + +def test_osr_usgs_wgs84(): + + srs = osr.SpatialReference() + srs.ImportFromEPSG(32631) + (proj_code, zone, params, datum_code) = srs.ExportToUSGS() + + srs2 = osr.SpatialReference() + srs2.ImportFromUSGS(proj_code, zone, params, datum_code) + + assert srs2.IsSame(srs) + assert srs2.GetAuthorityCode(None) == "32631" diff --git a/ogr/ogr_srs_usgs.cpp b/ogr/ogr_srs_usgs.cpp index 97ddf2619f7f..52faad8db080 100644 --- a/ogr/ogr_srs_usgs.cpp +++ b/ogr/ogr_srs_usgs.cpp @@ -732,9 +732,14 @@ OGRErr OGRSpatialReference::importFromUSGS(long iProjSys, long iZone, } else if (iDatum < NUMBER_OF_USGS_ELLIPSOIDS && aoEllipsUSGS[iDatum]) { - if (OSRGetEllipsoidInfo(aoEllipsUSGS[iDatum], &pszName, - &dfSemiMajor, - &dfInvFlattening) == OGRERR_NONE) + if (aoEllipsUSGS[iDatum] == 7030) // WGS 84 ellipsoid + { + // Assume a WGS 84 datum + SetWellKnownGeogCS("WGS84"); + } + else if (OSRGetEllipsoidInfo(aoEllipsUSGS[iDatum], &pszName, + &dfSemiMajor, + &dfInvFlattening) == OGRERR_NONE) { SetGeogCS( CPLString().Printf( @@ -772,6 +777,17 @@ OGRErr OGRSpatialReference::importFromUSGS(long iProjSys, long iZone, if (IsLocal() || IsProjected()) SetLinearUnits(SRS_UL_METER, 1.0); + if (iDatum < NUMBER_OF_USGS_ELLIPSOIDS && aoEllipsUSGS[iDatum] == 7030) + { + if (AutoIdentifyEPSG() == OGRERR_NONE) + { + const char *pszAuthName = GetAuthorityName(nullptr); + const char *pszAuthCode = GetAuthorityCode(nullptr); + if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG")) + CPL_IGNORE_RET_VAL(importFromEPSG(atoi(pszAuthCode))); + } + } + return OGRERR_NONE; } From 122315094e422f47d2fd13e328145959b28266ca Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 13 Jun 2024 22:25:53 +0200 Subject: [PATCH 0162/1119] DXF: avoid slight numeric imprecision with closed lwpolyline and arcs Fixes #10153 --- .../data/dxf/closed_polyline_with_bulge.dxf | 64 +++++++++++++++++++ autotest/ogr/ogr_dxf.py | 18 ++++++ .../dxf/ogrdxf_polyline_smooth.cpp | 14 ++-- 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 autotest/ogr/data/dxf/closed_polyline_with_bulge.dxf diff --git a/autotest/ogr/data/dxf/closed_polyline_with_bulge.dxf b/autotest/ogr/data/dxf/closed_polyline_with_bulge.dxf new file mode 100644 index 000000000000..b367a69a8c3d --- /dev/null +++ b/autotest/ogr/data/dxf/closed_polyline_with_bulge.dxf @@ -0,0 +1,64 @@ + 0 +SECTION + 2 +ENTITIES + 0 +LWPOLYLINE + 5 +215 +330 +1F +100 +AcDbEntity + 8 +test +100 +AcDbPolyline + 90 + 8 + 70 + 129 + 43 +0.5 + 10 +40585366.70650577 + 20 +3433935.538090975 + 10 +40585329.92564863 + 20 +3433998.440817071 + 42 +0.2621272319479089 + 10 +40585297.73920335 + 20 +3434017.254552271 + 10 +40585271.13131783 + 20 +3434017.686781913 + 10 +40585252.16981492 + 20 +3433885.990375476 + 10 +40585256.74147 + 20 +3433885.916111596 + 42 +0.1393571566538866 + 10 +40585329.65156154 + 20 +3433905.365607637 + 10 +40585364.24837356 + 20 +3433925.992208718 + 42 +0.4117239835866821 + 0 +ENDSEC + 0 +EOF diff --git a/autotest/ogr/ogr_dxf.py b/autotest/ogr/ogr_dxf.py index 1e11b9d389e8..21b283e779e9 100644 --- a/autotest/ogr/ogr_dxf.py +++ b/autotest/ogr/ogr_dxf.py @@ -3993,3 +3993,21 @@ def test_ogr_dxf_read_broken_file_2(): lyr = ds.GetLayer(0) for f in lyr: pass + + +############################################################################### + + +def test_ogr_dxf_read_closed_polyline_with_bulge(): + """Test https://github.com/OSGeo/gdal/issues/10153""" + + ds = ogr.Open("data/dxf/closed_polyline_with_bulge.dxf") + lyr = ds.GetLayer(0) + f = lyr.GetNextFeature() + g = f.GetGeometryRef() + assert g.GetX(0) == g.GetX(g.GetPointCount() - 1) + assert g.GetY(0) == g.GetY(g.GetPointCount() - 1) + assert ( + g.ExportToWkt() + == "LINESTRING (40585366.7065058 3433935.53809098,40585329.9256486 3433998.44081707,40585329.9256486 3433998.44081707,40585328.5387678 3434000.63680805,40585327.0051198 3434002.73293274,40585325.3318693 3434004.71939884,40585323.526833 3434006.58692634,40585321.5984435 3434008.32679087,40585319.5557093 3434009.93086443,40585317.4081735 3434011.39165342,40585315.1658683 3434012.70233358,40585312.8392691 3434013.85678191,40585310.4392448 3434014.84960528,40585307.9770074 3434015.67616559,40585305.4640596 3434016.33260146,40585302.9121409 3434016.81584629,40585300.3331728 3434017.12364253,40585297.7392033 3434017.25455227,40585271.1313178 3434017.68678191,40585252.1698149 3433885.99037548,40585256.74147 3433885.9161116,40585256.74147 3433885.9161116,40585266.2920614 3433886.0916242,40585275.8076317 3433886.92740148,40585285.2425893 3433888.41943902,40585294.551729 3433890.56058809,40585303.6904483 3433893.34058991,40585312.6149614 3433896.74612477,40585321.2825086 3433900.76087591,40585329.6515615 3433905.36560764,40585364.2483736 3433925.99220872,40585364.2483736 3433925.99220872,40585364.6481964 3433926.24937651,40585365.0296424 3433926.53308859,40585365.3909523 3433926.84203644,40585365.7304596 3433927.17479516,40585366.0465985 3433927.52983003,40585366.337911 3433927.90550359,40585366.6030535 3433928.30008319,40585366.840803 3433928.71174899,40585367.0500632 3433929.13860232,40585367.2298688 3433929.5786745,40585367.3793906 3433930.02993587,40585367.4979389 3433930.49030515,40585367.5849671 3433930.95765907,40585367.6400736 3433931.42984214,40585367.6630045 3433931.9046766,40585367.6536538 3433932.37997246,40585367.6120647 3433932.85353759,40585367.5384291 3433933.32318787,40585367.4330866 3433933.7867572,40585367.2965229 3433934.24210757,40585367.129368 3433934.68713883,40585366.9323928 3433935.11979846,40585366.7065058 3433935.53809098)" + ) diff --git a/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.cpp b/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.cpp index be1ebad4281b..17ee916fae6c 100644 --- a/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.cpp +++ b/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.cpp @@ -251,16 +251,22 @@ void DXFSmoothPolyline::EmitArc(const DXFSmoothPolylineVertex &start, if (fabs(ogrArcEndAngle - ogrArcStartAngle) <= 361.0) { - OGRLineString *poArcpoLS = + auto poArc = std::unique_ptr( OGRGeometryFactory::approximateArcAngles( ogrArcCenter.x, ogrArcCenter.y, dfZ, ogrArcRadius, ogrArcRadius, ogrArcRotation, ogrArcStartAngle, ogrArcEndAngle, 0.0, m_bUseMaxGapWhenTessellatingArcs) - ->toLineString(); + ->toLineString()); - poLS->addSubLineString(poArcpoLS); + // Make sure extremities exactly match input start and end point. + // This is in particular important if the polyline is closed. + if (poArc->getNumPoints() >= 2) + { + poArc->setPoint(0, start.x, start.y); + poArc->setPoint(poArc->getNumPoints() - 1, end.x, end.y); + } - delete poArcpoLS; + poLS->addSubLineString(poArc.get()); } else { From 5481008668c818303c2b429b53860a2d787c189b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 14 Jun 2024 12:09:40 +0200 Subject: [PATCH 0163/1119] OpenFileGDB: avoid harmless unsigned integer overflow (master only) (ossfuzz#69618) --- ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp index 8e1c37019a3e..20d715ea3eef 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp @@ -747,6 +747,10 @@ bool FileGDBTable::ReadTableXHeaderV4() m_nTablxOffsetSize = GetUInt32(abyHeader + 12, 0); returnErrorIf(m_nTablxOffsetSize < 4 || m_nTablxOffsetSize > 6); + returnErrorIf(m_n1024BlocksPresent > + (std::numeric_limits::max() - 16) / + (m_nTablxOffsetSize * 1024)); + m_nOffsetTableXTrailer = 16 + m_nTablxOffsetSize * 1024 * static_cast(m_n1024BlocksPresent); From 68b400fa68b3af0247469287e84eb916bfa45a0f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 10 Jun 2024 00:17:07 +0200 Subject: [PATCH 0164/1119] Contour: include minimum raster value as contour line when it exactly matches the first level Fixes #10167 There's currently an asymmetry since the maximum raster value is included. This is due how the marching square thresholding compares pixel values to the candidate contour levels. Add a special case to modify the comparison for the level that matches the minimum raster value. --- alg/contour.cpp | 63 ++++++---- alg/marching_squares/level_generator.h | 49 +++++--- alg/marching_squares/square.h | 114 ++++++++++-------- alg/marching_squares/utility.h | 11 +- autotest/alg/contour.py | 42 +++++++ .../cpp/test_marching_squares_contour.cpp | 14 ++- .../cpp/test_marching_squares_polygon.cpp | 18 ++- autotest/cpp/test_marching_squares_square.cpp | 74 ++++++++---- autotest/cpp/test_marching_squares_tile.cpp | 21 +++- autotest/utilities/test_gdal_contour.py | 22 ++-- 10 files changed, 291 insertions(+), 137 deletions(-) diff --git a/alg/contour.cpp b/alg/contour.cpp index c95b28e41ea5..b95d1a87ba0d 100644 --- a/alg/contour.cpp +++ b/alg/contour.cpp @@ -43,6 +43,8 @@ #include "ogr_srs_api.h" #include "ogr_geometry.h" +#include + static CPLErr OGRPolygonContourWriter(double dfLevelMin, double dfLevelMax, const OGRMultiPolygon &multipoly, void *pInfo) @@ -670,24 +672,25 @@ CPLErr GDALContourGenerateEx(GDALRasterBandH hBand, void *hLayer, GDALGetGeoTransform(hSrcDS, oCWI.adfGeoTransform); oCWI.nNextID = 0; + int bSuccessMin = FALSE; + double dfMinimum = GDALGetRasterMinimum(hBand, &bSuccessMin); + int bSuccessMax = FALSE; + double dfMaximum = GDALGetRasterMaximum(hBand, &bSuccessMax); + if ((!bSuccessMin || !bSuccessMax)) + { + double adfMinMax[2]; + if (GDALComputeRasterMinMax(hBand, false, adfMinMax) == CE_None) + { + dfMinimum = adfMinMax[0]; + dfMaximum = adfMinMax[1]; + } + } + bool ok = false; try { if (polygonize) { - int bSuccessMin = FALSE; - double dfMinimum = GDALGetRasterMinimum(hBand, &bSuccessMin); - int bSuccessMax = FALSE; - double dfMaximum = GDALGetRasterMaximum(hBand, &bSuccessMax); - if ((!bSuccessMin || !bSuccessMax) && !fixedLevels.empty()) - { - double adfMinMax[2]; - if (GDALComputeRasterMinMax(hBand, false, adfMinMax) == CE_None) - { - dfMinimum = adfMinMax[0]; - dfMaximum = adfMinMax[1]; - } - } if (!fixedLevels.empty()) { // If the minimum raster value is larger than the first requested @@ -711,8 +714,12 @@ CPLErr GDALContourGenerateEx(GDALRasterBandH hBand, void *hLayer, RingAppender appender(w); if (!fixedLevels.empty()) { - FixedLevelRangeIterator levels(&fixedLevels[0], - fixedLevels.size(), dfMaximum); + // Do not provide the actual minimum value to level iterator + // in polygonal case, otherwise it can result in a polygon + // with a degenerate min=max range. + FixedLevelRangeIterator levels( + &fixedLevels[0], fixedLevels.size(), + -std::numeric_limits::infinity(), dfMaximum); SegmentMerger writer( appender, levels, /* polygonize */ true); ContourGeneratorFromRaster 0.0) { - ExponentialLevelRangeIterator levels(expBase); + // Do not provide the actual minimum value to level iterator + // in polygonal case, otherwise it can result in a polygon + // with a degenerate min=max range. + ExponentialLevelRangeIterator levels( + expBase, -std::numeric_limits::infinity()); SegmentMerger writer(appender, levels, /* polygonize */ true); ContourGeneratorFromRaster::infinity()); SegmentMerger writer( appender, levels, /* polygonize */ true); ContourGeneratorFromRaster writer( appender, levels, /* polygonize */ false); ContourGeneratorFromRaster 0.0) { - ExponentialLevelRangeIterator levels(expBase); + ExponentialLevelRangeIterator levels(expBase, dfMinimum); SegmentMerger writer(appender, levels, /* polygonize */ false); ContourGeneratorFromRaster writer(appender, levels, /* polygonize */ false); ContourGeneratorFromRaster::infinity()), + writer(pfnWriter, pCBData), merger(writer, levels, /* polygonize */ false), contourGenerator(nWidth, nHeight, bNoDataSet != 0, dfNoDataValue, merger, levels) diff --git a/alg/marching_squares/level_generator.h b/alg/marching_squares/level_generator.h index 3209233c89af..32d35622b160 100644 --- a/alg/marching_squares/level_generator.h +++ b/alg/marching_squares/level_generator.h @@ -96,9 +96,10 @@ class FixedLevelRangeIterator public: typedef RangeIterator Iterator; - FixedLevelRangeIterator(const double *levels, size_t count, - double maxLevel = Inf) - : levels_(levels), count_(count), maxLevel_(maxLevel) + FixedLevelRangeIterator(const double *levels, size_t count, double minLevel, + double maxLevel) + : levels_(levels), count_(count), minLevel_(minLevel), + maxLevel_(maxLevel) { } @@ -107,13 +108,15 @@ class FixedLevelRangeIterator if (min > max) std::swap(min, max); size_t b = 0; - for (; b != count_ && levels_[b] < fudge(levels_[b], min); b++) + for (; b != count_ && levels_[b] < fudge(min, minLevel_, levels_[b]); + b++) ; if (min == max) return Range(Iterator(*this, int(b)), Iterator(*this, int(b))); size_t e = b; - for (; e != count_ && levels_[e] <= fudge(levels_[e], max); e++) + for (; e != count_ && levels_[e] <= fudge(max, minLevel_, levels_[e]); + e++) ; return Range(Iterator(*this, int(b)), Iterator(*this, int(e))); @@ -126,10 +129,16 @@ class FixedLevelRangeIterator return levels_[size_t(idx)]; } + double minLevel() const + { + return minLevel_; + } + private: const double *levels_; size_t count_; - double maxLevel_; + const double minLevel_; + const double maxLevel_; }; struct TooManyLevelsException : public std::exception @@ -150,8 +159,8 @@ struct IntervalLevelRangeIterator typedef RangeIterator Iterator; // Construction by a offset and an interval - IntervalLevelRangeIterator(double offset, double interval) - : offset_(offset), interval_(interval) + IntervalLevelRangeIterator(double offset, double interval, double minLevel) + : offset_(offset), interval_(interval), minLevel_(minLevel) { } @@ -165,7 +174,7 @@ struct IntervalLevelRangeIterator if (!(df_i1 >= INT_MIN && df_i1 < INT_MAX)) throw TooManyLevelsException(); int i1 = static_cast(df_i1); - double l1 = fudge(level(i1), min); + double l1 = fudge(min, minLevel_, level(i1)); if (l1 > min) { df_i1 = ceil((l1 - offset_) / interval_); @@ -183,7 +192,7 @@ struct IntervalLevelRangeIterator if (!(df_i2 >= INT_MIN && df_i2 < INT_MAX)) throw TooManyLevelsException(); int i2 = static_cast(df_i2); - double l2 = fudge(level(i2), max); + double l2 = fudge(max, minLevel_, level(i2)); if (l2 > max) { df_i2 = floor((l2 - offset_) / interval_) + 1; @@ -206,9 +215,15 @@ struct IntervalLevelRangeIterator return idx * interval_ + offset_; } + double minLevel() const + { + return minLevel_; + } + private: const double offset_; const double interval_; + const double minLevel_; }; class ExponentialLevelRangeIterator @@ -216,8 +231,8 @@ class ExponentialLevelRangeIterator public: typedef RangeIterator Iterator; - ExponentialLevelRangeIterator(double base) - : base_(base), base_ln_(std::log(base_)) + ExponentialLevelRangeIterator(double base, double minLevel) + : base_(base), base_ln_(std::log(base_)), minLevel_(minLevel) { } @@ -234,7 +249,7 @@ class ExponentialLevelRangeIterator std::swap(min, max); int i1 = index1(min); - double l1 = fudge(level(i1), min); + double l1 = fudge(min, minLevel_, level(i1)); if (l1 > min) i1 = index1(l1); Iterator b(*this, i1); @@ -243,7 +258,7 @@ class ExponentialLevelRangeIterator return Range(b, b); int i2 = index2(max); - double l2 = fudge(level(i2), max); + double l2 = fudge(max, minLevel_, level(i2)); if (l2 > max) i2 = index2(l2); Iterator e(*this, i2); @@ -256,6 +271,11 @@ class ExponentialLevelRangeIterator return Range(b, e); } + double minLevel() const + { + return minLevel_; + } + private: int index1(double plevel) const { @@ -280,6 +300,7 @@ class ExponentialLevelRangeIterator // exponentiation base const double base_; const double base_ln_; + const double minLevel_; }; } // namespace marching_squares diff --git a/alg/marching_squares/square.h b/alg/marching_squares/square.h index 23316ec8b40d..caf1698fdf05 100644 --- a/alg/marching_squares/square.h +++ b/alg/marching_squares/square.h @@ -203,9 +203,9 @@ struct Square // // ^ // - | + - Segments segments(double level) const + Segments segments(double level, double minLevel) const { - switch (marchingCase(level)) + switch (marchingCase(level, minLevel)) { case (ALL_LOW): // debug("ALL_LOW"); @@ -215,52 +215,64 @@ struct Square return Segments(); case (UPPER_LEFT): // debug("UPPER_LEFT"); - return Segments(Segment(interpolate(UPPER_BORDER, level), - interpolate(LEFT_BORDER, level))); + return Segments( + Segment(interpolate(UPPER_BORDER, level, minLevel), + interpolate(LEFT_BORDER, level, minLevel))); case (LOWER_LEFT): // debug("LOWER_LEFT"); - return Segments(Segment(interpolate(LEFT_BORDER, level), - interpolate(LOWER_BORDER, level))); + return Segments( + Segment(interpolate(LEFT_BORDER, level, minLevel), + interpolate(LOWER_BORDER, level, minLevel))); case (LOWER_RIGHT): // debug("LOWER_RIGHT"); - return Segments(Segment(interpolate(LOWER_BORDER, level), - interpolate(RIGHT_BORDER, level))); + return Segments( + Segment(interpolate(LOWER_BORDER, level, minLevel), + interpolate(RIGHT_BORDER, level, minLevel))); case (UPPER_RIGHT): // debug("UPPER_RIGHT"); - return Segments(Segment(interpolate(RIGHT_BORDER, level), - interpolate(UPPER_BORDER, level))); + return Segments( + Segment(interpolate(RIGHT_BORDER, level, minLevel), + interpolate(UPPER_BORDER, level, minLevel))); case (UPPER_LEFT | LOWER_LEFT): // debug("UPPER_LEFT | LOWER_LEFT"); - return Segments(Segment(interpolate(UPPER_BORDER, level), - interpolate(LOWER_BORDER, level))); + return Segments( + Segment(interpolate(UPPER_BORDER, level, minLevel), + interpolate(LOWER_BORDER, level, minLevel))); case (LOWER_LEFT | LOWER_RIGHT): // debug("LOWER_LEFT | LOWER_RIGHT"); - return Segments(Segment(interpolate(LEFT_BORDER, level), - interpolate(RIGHT_BORDER, level))); + return Segments( + Segment(interpolate(LEFT_BORDER, level, minLevel), + interpolate(RIGHT_BORDER, level, minLevel))); case (LOWER_RIGHT | UPPER_RIGHT): // debug("LOWER_RIGHT | UPPER_RIGHT"); - return Segments(Segment(interpolate(LOWER_BORDER, level), - interpolate(UPPER_BORDER, level))); + return Segments( + Segment(interpolate(LOWER_BORDER, level, minLevel), + interpolate(UPPER_BORDER, level, minLevel))); case (UPPER_RIGHT | UPPER_LEFT): // debug("UPPER_RIGHT | UPPER_LEFT"); - return Segments(Segment(interpolate(RIGHT_BORDER, level), - interpolate(LEFT_BORDER, level))); + return Segments( + Segment(interpolate(RIGHT_BORDER, level, minLevel), + interpolate(LEFT_BORDER, level, minLevel))); case (ALL_HIGH & ~UPPER_LEFT): // debug("ALL_HIGH & ~UPPER_LEFT"); - return Segments(Segment(interpolate(LEFT_BORDER, level), - interpolate(UPPER_BORDER, level))); + return Segments( + Segment(interpolate(LEFT_BORDER, level, minLevel), + interpolate(UPPER_BORDER, level, minLevel))); case (ALL_HIGH & ~LOWER_LEFT): // debug("ALL_HIGH & ~LOWER_LEFT"); - return Segments(Segment(interpolate(LOWER_BORDER, level), - interpolate(LEFT_BORDER, level))); + return Segments( + Segment(interpolate(LOWER_BORDER, level, minLevel), + interpolate(LEFT_BORDER, level, minLevel))); case (ALL_HIGH & ~LOWER_RIGHT): // debug("ALL_HIGH & ~LOWER_RIGHT"); - return Segments(Segment(interpolate(RIGHT_BORDER, level), - interpolate(LOWER_BORDER, level))); + return Segments( + Segment(interpolate(RIGHT_BORDER, level, minLevel), + interpolate(LOWER_BORDER, level, minLevel))); case (ALL_HIGH & ~UPPER_RIGHT): // debug("ALL_HIGH & ~UPPER_RIGHT"); - return Segments(Segment(interpolate(UPPER_BORDER, level), - interpolate(RIGHT_BORDER, level))); + return Segments( + Segment(interpolate(UPPER_BORDER, level, minLevel), + interpolate(RIGHT_BORDER, level, minLevel))); case (SADDLE_NE): case (SADDLE_NW): // From the two possible saddle configurations, we always return @@ -275,10 +287,11 @@ struct Square // Arbitrarily choosing one of the two possible configurations // is not really that worse than deciding based on the center // point. - return Segments(Segment(interpolate(LEFT_BORDER, level), - interpolate(LOWER_BORDER, level)), - Segment(interpolate(RIGHT_BORDER, level), - interpolate(UPPER_BORDER, level))); + return Segments( + Segment(interpolate(LEFT_BORDER, level, minLevel), + interpolate(LOWER_BORDER, level, minLevel)), + Segment(interpolate(RIGHT_BORDER, level, minLevel), + interpolate(UPPER_BORDER, level, minLevel))); } assert(false); return Segments(); @@ -335,7 +348,8 @@ struct Square const int levelIdx = (*it).first; const double level = (*it).second; - const Point nextPoint(interpolate(border, level)); + const Point nextPoint( + interpolate(border, level, levelGenerator.minLevel())); if (reverse) writer.addBorderSegment(levelIdx, nextPoint, lastPoint); else @@ -361,7 +375,8 @@ struct Square const int levelIdx = (*it).first; const double level = (*it).second; - const Segments segments_ = segments(level); + const Segments segments_ = + segments(level, levelGenerator.minLevel()); for (std::size_t i = 0; i < segments_.size(); i++) { @@ -447,17 +462,20 @@ struct Square : .5 * (upperLeft.value + upperRight.value))); } - uint8_t marchingCase(double level) const + uint8_t marchingCase(double level, double minLevel) const { - return (level < fudge(level, upperLeft.value) ? UPPER_LEFT : ALL_LOW) | - (level < fudge(level, lowerLeft.value) ? LOWER_LEFT : ALL_LOW) | - (level < fudge(level, lowerRight.value) ? LOWER_RIGHT - : ALL_LOW) | - (level < fudge(level, upperRight.value) ? UPPER_RIGHT : ALL_LOW); + return (level < fudge(upperLeft.value, minLevel, level) ? UPPER_LEFT + : ALL_LOW) | + (level < fudge(lowerLeft.value, minLevel, level) ? LOWER_LEFT + : ALL_LOW) | + (level < fudge(lowerRight.value, minLevel, level) ? LOWER_RIGHT + : ALL_LOW) | + (level < fudge(upperRight.value, minLevel, level) ? UPPER_RIGHT + : ALL_LOW); } static double interpolate_(double level, double x1, double x2, double y1, - double y2, bool need_split) + double y2, bool need_split, double minLevel) { if (need_split) { @@ -471,8 +489,8 @@ struct Square // the end const double xm = .5 * (x1 + x2); const double ym = .5 * (y1 + y2); - const double fy1 = fudge(level, y1); - const double fym = fudge(level, ym); + const double fy1 = fudge(y1, minLevel, level); + const double fym = fudge(ym, minLevel, level); if ((fy1 < level && level < fym) || (fy1 > level && level > fym)) { x2 = xm; @@ -484,12 +502,12 @@ struct Square y1 = ym; } } - const double fy1 = fudge(level, y1); - const double ratio = (level - fy1) / (fudge(level, y2) - fy1); + const double fy1 = fudge(y1, minLevel, level); + const double ratio = (level - fy1) / (fudge(y2, minLevel, level) - fy1); return x1 * (1. - ratio) + x2 * ratio; } - Point interpolate(uint8_t border, double level) const + Point interpolate(uint8_t border, double level, double minLevel) const { switch (border) { @@ -497,21 +515,21 @@ struct Square return Point(upperLeft.x, interpolate_(level, lowerLeft.y, upperLeft.y, lowerLeft.value, upperLeft.value, - !split)); + !split, minLevel)); case LOWER_BORDER: return Point(interpolate_(level, lowerLeft.x, lowerRight.x, lowerLeft.value, lowerRight.value, - !split), + !split, minLevel), lowerLeft.y); case RIGHT_BORDER: return Point(upperRight.x, interpolate_(level, lowerRight.y, upperRight.y, lowerRight.value, upperRight.value, - !split)); + !split, minLevel)); case UPPER_BORDER: return Point(interpolate_(level, upperLeft.x, upperRight.x, upperLeft.value, upperRight.value, - !split), + !split, minLevel), upperLeft.y); } assert(false); diff --git a/alg/marching_squares/utility.h b/alg/marching_squares/utility.h index 1e3513361c1c..eef8252a30cf 100644 --- a/alg/marching_squares/utility.h +++ b/alg/marching_squares/utility.h @@ -36,14 +36,14 @@ namespace marching_squares // This is used to determine the maximum level value for polygons, // the one that spans all the remaining plane -const double Inf = std::numeric_limits::max(); +constexpr double Inf = std::numeric_limits::infinity(); -const double NaN = std::numeric_limits::quiet_NaN(); +constexpr double NaN = std::numeric_limits::quiet_NaN(); #define debug(format, ...) CPLDebug("MarchingSquare", format, ##__VA_ARGS__) // Perturb a value if it is too close to a level value -inline double fudge(double level, double value) +inline double fudge(double value, double minLevel, double level) { // FIXME // This is too "hard coded". The perturbation to apply really depend on @@ -55,6 +55,11 @@ inline double fudge(double level, double value) // are within a user-provided minimum distance. const double absTol = 1e-6; + // Do not fudge the level that would correspond to the absolute minimum + // level of the raster, so it gets included. + // Cf scenario of https://github.com/OSGeo/gdal/issues/10167 + if (level == minLevel) + return value; return std::abs(level - value) < absTol ? value + absTol : value; } diff --git a/autotest/alg/contour.py b/autotest/alg/contour.py index c35a63dc0c65..de1f67e2566a 100755 --- a/autotest/alg/contour.py +++ b/autotest/alg/contour.py @@ -416,3 +416,45 @@ def test_contour_invalid_LEVEL_INTERVAL(): gdal.ContourGenerateEx( ds.GetRasterBand(1), ogr_lyr, options=["LEVEL_INTERVAL=-1"] ) + + +############################################################################### +# Test scenario of https://github.com/OSGeo/gdal/issues/10167 + + +@pytest.mark.require_driver("AAIGRID") +def test_contour_min_value_is_multiple_of_interval(tmp_vsimem): + + ogr_ds = ogr.GetDriverByName("Memory").CreateDataSource("") + lyr = ogr_ds.CreateLayer("contour", geom_type=ogr.wkbLineString) + lyr.CreateField(ogr.FieldDefn("ID", ogr.OFTInteger)) + lyr.CreateField(ogr.FieldDefn("ELEV", ogr.OFTReal)) + + content = """ncols 2 +nrows 2 +xllcorner 0 +yllcorner 0 +cellsize 1 +1 3 +1 3""" + + srcfilename = str(tmp_vsimem / "test.asc") + with gdaltest.tempfile(srcfilename, content): + ds = gdal.Open(srcfilename) + gdal.ContourGenerateEx( + ds.GetRasterBand(1), + lyr, + options=["LEVEL_INTERVAL=1", "ID_FIELD=0", "ELEV_FIELD=1"], + ) + + f = lyr.GetNextFeature() + assert f["ELEV"] == 1 + ogrtest.check_feature_geometry(f, "LINESTRING (0.5 0.0,0.5 0.5,0.5 1.5,0.5 2.0)") + + f = lyr.GetNextFeature() + assert f["ELEV"] == 2 + ogrtest.check_feature_geometry(f, "LINESTRING (1.0 0.0,1.0 0.5,1.0 1.5,1.0 2.0)") + + f = lyr.GetNextFeature() + assert f["ELEV"] == 3 + ogrtest.check_feature_geometry(f, "LINESTRING (1.5 0.0,1.5 0.5,1.5 1.5,1.5 2.0)") diff --git a/autotest/cpp/test_marching_squares_contour.cpp b/autotest/cpp/test_marching_squares_contour.cpp index 2048fcfd46c1..aea8a210979d 100644 --- a/autotest/cpp/test_marching_squares_contour.cpp +++ b/autotest/cpp/test_marching_squares_contour.cpp @@ -35,6 +35,8 @@ #include "marching_squares/segment_merger.h" #include "marching_squares/contour_generator.h" +#include + #include "gtest_include.h" namespace marching_squares @@ -179,7 +181,8 @@ TEST_F(test_ms_contour, dummy) std::vector data = {2.0}; TestRingAppender w; { - IntervalLevelRangeIterator levels(0.0, 10.0); + IntervalLevelRangeIterator levels( + 0.0, 10.0, -std::numeric_limits::infinity()); SegmentMerger writer( w, levels, /* polygonize */ true); ContourGenerator cg( @@ -207,7 +210,8 @@ TEST_F(test_ms_contour, two_pixels) TestRingAppender w; { - IntervalLevelRangeIterator levels(8.0, 10.0); + IntervalLevelRangeIterator levels( + 8.0, 10.0, -std::numeric_limits::infinity()); SegmentMerger writer( w, levels, /* polygonize */ true); ContourGenerator cg( @@ -319,7 +323,8 @@ TEST_F(test_ms_contour, four_pixels) TestRingAppender w; { - IntervalLevelRangeIterator levels(8.0, 10.0); + IntervalLevelRangeIterator levels( + 8.0, 10.0, -std::numeric_limits::infinity()); SegmentMerger writer( w, levels, /* polygonize */ true); ContourGenerator cg( @@ -440,7 +445,8 @@ TEST_F(test_ms_contour, saddle_point) TestRingAppender w; { - IntervalLevelRangeIterator levels(8.0, 10.0); + IntervalLevelRangeIterator levels( + 8.0, 10.0, -std::numeric_limits::infinity()); SegmentMerger writer( w, levels, /* polygonize */ true); ContourGenerator cg( diff --git a/autotest/cpp/test_marching_squares_polygon.cpp b/autotest/cpp/test_marching_squares_polygon.cpp index 4712eb9cfb68..d2d3fff8da5c 100644 --- a/autotest/cpp/test_marching_squares_polygon.cpp +++ b/autotest/cpp/test_marching_squares_polygon.cpp @@ -40,6 +40,8 @@ #include #endif +#include + #include "gtest_include.h" namespace marching_squares @@ -171,7 +173,8 @@ TEST_F(test_ms_polygon, dummy) TestPolygonWriter w; { PolygonRingAppender appender(w); - IntervalLevelRangeIterator levels(0.0, 10.0); + IntervalLevelRangeIterator levels( + 0.0, 10.0, -std::numeric_limits::infinity()); SegmentMerger, IntervalLevelRangeIterator> writer(appender, levels, /* polygonize */ true); @@ -234,7 +237,8 @@ TEST_F(test_ms_polygon, four_pixels) TestPolygonWriter w; { PolygonRingAppender appender(w); - IntervalLevelRangeIterator levels(0.0, 10.0); + IntervalLevelRangeIterator levels( + 0.0, 10.0, -std::numeric_limits::infinity()); SegmentMerger, IntervalLevelRangeIterator> writer(appender, levels, /* polygonize */ true); @@ -304,7 +308,9 @@ TEST_F(test_ms_polygon, four_pixels_2) { PolygonRingAppender appender(w); const double levels[] = {155.0}; - FixedLevelRangeIterator levelGenerator(levels, 1); + FixedLevelRangeIterator levelGenerator( + levels, 1, -std::numeric_limits::infinity(), + std::numeric_limits::infinity()); SegmentMerger, FixedLevelRangeIterator> writer(appender, levelGenerator, /* polygonize */ true); @@ -400,7 +406,8 @@ TEST_F(test_ms_polygon, nine_pixels) TestPolygonWriter w; { PolygonRingAppender appender(w); - IntervalLevelRangeIterator levels(1.0, 10.0); + IntervalLevelRangeIterator levels( + 1.0, 10.0, -std::numeric_limits::infinity()); SegmentMerger, IntervalLevelRangeIterator> writer(appender, levels, /* polygonize */ true); @@ -452,7 +459,8 @@ TEST_F(test_ms_polygon, three_nested_rings) TestPolygonWriter w; { PolygonRingAppender appender(w); - IntervalLevelRangeIterator levels(1.0, 2.0); + IntervalLevelRangeIterator levels( + 1.0, 2.0, -std::numeric_limits::infinity()); SegmentMerger, IntervalLevelRangeIterator> writer(appender, levels, /* polygonize */ true); diff --git a/autotest/cpp/test_marching_squares_square.cpp b/autotest/cpp/test_marching_squares_square.cpp index 8418dd8a8bd7..bab7d6131434 100644 --- a/autotest/cpp/test_marching_squares_square.cpp +++ b/autotest/cpp/test_marching_squares_square.cpp @@ -34,6 +34,7 @@ #include "marching_squares/square.h" #include "marching_squares/level_generator.h" #include +#include #include #include @@ -72,7 +73,9 @@ TEST_F(test_ms_square, dummy) { { const double levels[] = {0, 4}; - FixedLevelRangeIterator levelGenerator(levels, 2); + FixedLevelRangeIterator levelGenerator( + levels, 2, -std::numeric_limits::infinity(), + std::numeric_limits::infinity()); auto r = levelGenerator.range(0, 5.0); auto b = r.begin(); EXPECT_EQ((*b).first, 1); @@ -82,7 +85,8 @@ TEST_F(test_ms_square, dummy) EXPECT_EQ((*e).second, Inf); } { - IntervalLevelRangeIterator levelGenerator(0, 4); + IntervalLevelRangeIterator levelGenerator( + 0, 4, -std::numeric_limits::infinity()); auto r = levelGenerator.range(0, 5.0); auto b = r.begin(); EXPECT_EQ((*b).first, 1); @@ -92,7 +96,8 @@ TEST_F(test_ms_square, dummy) EXPECT_EQ((*e).second, 8.0); } { - IntervalLevelRangeIterator levelGenerator(0, 10); + IntervalLevelRangeIterator levelGenerator( + 0, 10, -std::numeric_limits::infinity()); auto r = levelGenerator.range(-18, 5.0); auto b = r.begin(); EXPECT_EQ((*b).first, -1); @@ -102,7 +107,8 @@ TEST_F(test_ms_square, dummy) EXPECT_EQ((*e).second, 10.0); } { - ExponentialLevelRangeIterator levelGenerator(2); + ExponentialLevelRangeIterator levelGenerator( + 2, -std::numeric_limits::infinity()); auto r = levelGenerator.range(0, 5.0); auto b = r.begin(); EXPECT_EQ((*b).first, 1); @@ -124,7 +130,8 @@ TEST_F(test_ms_square, only_zero) // Square with only 0, level = 0.1 Square square(ValuedPoint(0, 1, 0), ValuedPoint(1, 1, 0), ValuedPoint(0, 0, 0), ValuedPoint(1, 0, 0)); - Square::Segments segments(square.segments(.1)); + Square::Segments segments( + square.segments(.1, -std::numeric_limits::infinity())); // // 0 0 // +------------------+ @@ -157,7 +164,8 @@ TEST_F(test_ms_square, only_one) // | | // +------------------+ // 1 1 - Square::Segments segments(square.segments(.1)); + Square::Segments segments( + square.segments(.1, -std::numeric_limits::infinity())); EXPECT_EQ(segments.size(), size_t(0)); } @@ -178,7 +186,8 @@ TEST_F(test_ms_square, only_zero_level_1) // | | // +------------------+ // 1 1 - Square::Segments segments(square.segments(1.0)); + Square::Segments segments( + square.segments(1.0, -std::numeric_limits::infinity())); EXPECT_EQ(segments.size(), size_t(0)); } @@ -199,7 +208,8 @@ TEST_F(test_ms_square, one_segment) // | \ | // +---o--------------+ // 1 0 - Square::Segments segments(square.segments(.1)); + Square::Segments segments( + square.segments(.1, -std::numeric_limits::infinity())); EXPECT_EQ(segments.size(), size_t(1)); EXPECT_TRUE(segments[0].first == Point(.9, 1)); EXPECT_TRUE(segments[0].second == Point(0, .1)); @@ -224,11 +234,13 @@ TEST_F(test_ms_square, fudge_test_1) // 1 1 // (0,0) { - Square::Segments segments(square.segments(0.0)); + Square::Segments segments( + square.segments(0.0, -std::numeric_limits::infinity())); EXPECT_EQ(segments.size(), size_t(0)); } { - Square::Segments segments(square.segments(1.0)); + Square::Segments segments( + square.segments(1.0, -std::numeric_limits::infinity())); EXPECT_EQ(segments.size(), size_t(1)); EXPECT_NEAR(segments[0].first.x, 0.0, 0.001); EXPECT_NEAR(segments[0].first.y, 0.0, 0.001); @@ -256,7 +268,8 @@ TEST_F(test_ms_square, fudge_test_2) // 0 0 // (0,0) { - Square::Segments segments(square.segments(1.0)); + Square::Segments segments( + square.segments(1.0, -std::numeric_limits::infinity())); EXPECT_EQ(segments.size(), 1); EXPECT_NEAR(segments[0].first.x, 0.0, 0.001); EXPECT_NEAR(segments[0].first.y, 1.0, 0.001); @@ -264,7 +277,8 @@ TEST_F(test_ms_square, fudge_test_2) EXPECT_NEAR(segments[0].second.y, 1.0, 0.001); } { - Square::Segments segments(square.segments(0.0)); + Square::Segments segments( + square.segments(0.0, -std::numeric_limits::infinity())); EXPECT_EQ(segments.size(), 0); } } @@ -320,8 +334,10 @@ TEST_F(test_ms_square, nan) EXPECT_EQ(ul.lowerRight.y, ll.upperRight.y); EXPECT_EQ(ul.lowerRight.value, ll.upperRight.value); - const Square::Segments segments_up(ul.segments(225)); - const Square::Segments segments_down(ll.segments(225)); + const Square::Segments segments_up( + ul.segments(225, -std::numeric_limits::infinity())); + const Square::Segments segments_down( + ll.segments(225, -std::numeric_limits::infinity())); // segments on 225 // @@ -378,8 +394,10 @@ TEST_F(test_ms_square, border_test_1) // +--------+---------+ // 272.87 272.90000 272.93 - Square::Segments segments_l(ll.segments(272.9)); - Square::Segments segments_r(lr.segments(272.9)); + Square::Segments segments_l( + ll.segments(272.9, -std::numeric_limits::infinity())); + Square::Segments segments_r( + lr.segments(272.9, -std::numeric_limits::infinity())); // the level falls exactly on corners // thanks to the fudge, each corner should be shifted away a bit @@ -443,7 +461,8 @@ TEST_F(test_ms_square, multiple_levels) Writer writer; // levels starting at min and increasing by 0.1 - IntervalLevelRangeIterator levelGenerator(0, .1); + IntervalLevelRangeIterator levelGenerator( + 0, .1, -std::numeric_limits::infinity()); ul.process(levelGenerator, writer); @@ -505,7 +524,8 @@ TEST_F(test_ms_square, border_test_3) { // ... with a level interval Writer writer; - IntervalLevelRangeIterator levelGenerator(7, 5); + IntervalLevelRangeIterator levelGenerator( + 7, 5, -std::numeric_limits::infinity()); ul.process(levelGenerator, writer); // we have one contour at 7 and 12 @@ -546,7 +566,9 @@ TEST_F(test_ms_square, border_test_3) { Writer writer; std::vector levels = {7.0}; - FixedLevelRangeIterator levelGenerator(&levels[0], 1); + FixedLevelRangeIterator levelGenerator( + &levels[0], 1, -std::numeric_limits::infinity(), + std::numeric_limits::infinity()); ul.process(levelGenerator, writer); // we have one contour at 7 and 12 @@ -604,7 +626,9 @@ TEST_F(test_ms_square, level_value_below_square_values) { Writer writer; std::vector levels = {2.0}; - FixedLevelRangeIterator levelGenerator(&levels[0], 1); + FixedLevelRangeIterator levelGenerator( + &levels[0], 1, -std::numeric_limits::infinity(), + std::numeric_limits::infinity()); square.process(levelGenerator, writer); EXPECT_TRUE((writer.borders.size() == 0)); EXPECT_TRUE((writer.contours.size() == 0)); @@ -630,7 +654,8 @@ TEST_F(test_ms_square, full_border_test_1) // NaN 5 { Writer writer; - IntervalLevelRangeIterator levelGenerator(0, 10.0); + IntervalLevelRangeIterator levelGenerator( + 0, 10.0, -std::numeric_limits::infinity()); square.process(levelGenerator, writer); EXPECT_TRUE((writer.borders.size() == 1)); EXPECT_TRUE((writer.borders[1].size() == 2)); @@ -672,7 +697,8 @@ TEST_F(test_ms_square, full_border_test_2) // NaN 5 { Writer writer; - IntervalLevelRangeIterator levelGenerator(5.0, 5.0); + IntervalLevelRangeIterator levelGenerator( + 5.0, 5.0, -std::numeric_limits::infinity()); square.process(levelGenerator, writer); EXPECT_TRUE((writer.borders.size() == 1)); EXPECT_TRUE((writer.borders[1].size() == 2)); @@ -696,7 +722,9 @@ TEST_F(test_ms_square, full_border_test_2) { Writer writer; std::vector levels = {5.0}; - FixedLevelRangeIterator levelGenerator(&levels[0], 1); + FixedLevelRangeIterator levelGenerator( + &levels[0], 1, -std::numeric_limits::infinity(), + std::numeric_limits::infinity()); square.process(levelGenerator, writer); EXPECT_TRUE((writer.borders.size() == 1)); EXPECT_TRUE((writer.borders[1].size() == 2)); diff --git a/autotest/cpp/test_marching_squares_tile.cpp b/autotest/cpp/test_marching_squares_tile.cpp index cc69b7698920..ac3b0adaddc8 100644 --- a/autotest/cpp/test_marching_squares_tile.cpp +++ b/autotest/cpp/test_marching_squares_tile.cpp @@ -34,6 +34,7 @@ #include "marching_squares/point.h" #include "marching_squares/level_generator.h" #include "marching_squares/contour_generator.h" +#include #include #include @@ -139,7 +140,8 @@ TEST_F(test_ms_tile, dummy) // only one pixel of value 2.0 // levels = 0, 10 std::vector data = {2.0}; - IntervalLevelRangeIterator levels(0.0, 10.0); + IntervalLevelRangeIterator levels(0.0, 10.0, + -std::numeric_limits::infinity()); Writer writer; ContourGenerator cg( @@ -173,7 +175,9 @@ TEST_F(test_ms_tile, tile_one_pixel) // levels = 0, 10 std::vector data = {2.0}; const double levels[] = {0.0}; - FixedLevelRangeIterator levelGenerator(levels, 1); + FixedLevelRangeIterator levelGenerator( + levels, 1, -std::numeric_limits::infinity(), + std::numeric_limits::infinity()); Writer writer; ContourGenerator cg( @@ -207,7 +211,8 @@ TEST_F(test_ms_tile, tile_one_pixel_two) // only one pixel of value 2.0 // levels = 2, 10 std::vector data = {2.0}; - IntervalLevelRangeIterator levels(2.0, 10.0); + IntervalLevelRangeIterator levels(2.0, 10.0, + -std::numeric_limits::infinity()); Writer writer; ContourGenerator cg( @@ -299,7 +304,8 @@ TEST_F(test_ms_tile, tile_two_pixels) std::vector data = {10.0, 7.0}; { - IntervalLevelRangeIterator levels(8.0, 10.0); + IntervalLevelRangeIterator levels( + 8.0, 10.0, -std::numeric_limits::infinity()); Writer writer; ContourGenerator cg( 2, 1, /* hasNoData */ false, NaN, writer, levels); @@ -421,7 +427,8 @@ TEST_F(test_ms_tile, tile_four_pixels) // NaN NaN NaN NaN std::vector data = {10.0, 7.0, 4.0, 5.0}; { - IntervalLevelRangeIterator levels(8.0, 10.0); + IntervalLevelRangeIterator levels( + 8.0, 10.0, -std::numeric_limits::infinity()); Writer writer; ContourGenerator cg( 2, 2, /* hasNoData */ false, NaN, writer, levels); @@ -494,7 +501,9 @@ TEST_F(test_ms_tile, tile_four_pixels_2) std::vector data = {155.0, 155.01, 154.99, 155.0}; { const double levels[] = {155.0}; - FixedLevelRangeIterator levelGenerator(levels, 1); + FixedLevelRangeIterator levelGenerator( + levels, 1, -std::numeric_limits::infinity(), + std::numeric_limits::infinity()); Writer writer; ContourGenerator cg( 2, 2, /* hasNoData */ false, NaN, writer, levelGenerator); diff --git a/autotest/utilities/test_gdal_contour.py b/autotest/utilities/test_gdal_contour.py index 772c503d1581..e1e76a4e98c6 100755 --- a/autotest/utilities/test_gdal_contour.py +++ b/autotest/utilities/test_gdal_contour.py @@ -122,10 +122,11 @@ def test_gdal_contour_1(gdal_contour_path, testdata_tif, tmp_path): ds = ogr.Open(contour_shp) expected_envelopes = [ - [1.25, 1.75, 49.25, 49.75], - [1.25 + 0.125, 1.75 - 0.125, 49.25 + 0.125, 49.75 - 0.125], + [1.246875, 1.753125, 49.246875, 49.753125], + [1.253125, 1.746875, 49.253125, 49.746875], + [1.378125, 1.621875, 49.378125, 49.621875], ] - expected_height = [10, 20] + expected_height = [0, 10, 20] lyr = ds.ExecuteSQL("select * from contour order by elev asc") @@ -141,21 +142,18 @@ def test_gdal_contour_1(gdal_contour_path, testdata_tif, tmp_path): precision = 1.0 / size i = 0 - feat = lyr.GetNextFeature() - while feat is not None: - envelope = feat.GetGeometryRef().GetEnvelope() + for feat in lyr: + geom = feat.GetGeometryRef() + envelope = geom.GetEnvelope() assert feat.GetField("elev") == expected_height[i] for j in range(4): - if expected_envelopes[i][j] != pytest.approx( - envelope[j], abs=precision / 2 * 1.001 - ): - print("i=%d, wkt=%s" % (i, feat.GetGeometryRef().ExportToWkt())) - print(feat.GetGeometryRef().GetEnvelope()) + if expected_envelopes[i][j] != pytest.approx(envelope[j], rel=1e-8): + print("i=%d, wkt=%s" % (i, geom.ExportToWkt())) + print(geom.GetEnvelope()) pytest.fail( "%f, %f" % (expected_envelopes[i][j] - envelope[j], precision / 2) ) i = i + 1 - feat = lyr.GetNextFeature() ds.ReleaseResultSet(lyr) From 257fb20f11d4c4679d688c7f079499df675225fe Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 14 Jun 2024 19:26:21 +0200 Subject: [PATCH 0165/1119] GRIB: make .idx reading compatible of /vsisubfile/ Fixes #10214 --- autotest/gdrivers/grib.py | 42 +++++++++++++++++ frmts/grib/gribdataset.cpp | 93 ++++++++++++++++++++++++++------------ 2 files changed, 105 insertions(+), 30 deletions(-) diff --git a/autotest/gdrivers/grib.py b/autotest/gdrivers/grib.py index 3675b3205ecc..fc20331cad99 100755 --- a/autotest/gdrivers/grib.py +++ b/autotest/gdrivers/grib.py @@ -2252,6 +2252,48 @@ def test_grib_grib2_sidecar(): ) == ds_idx.GetRasterBand(i).GetMetadataItem(key) +def test_grib_grib2_sidecar_vsisubfile(): + + ds = gdal.Open("/vsisubfile/0_5359,data/grib/gfs.t06z.pgrb2.10p0.f010.grib2") + assert ds.RasterCount == 1 + assert ds.GetRasterBand(1).GetDescription() == "REFD:1 hybrid level:10 hour fcst" + + ds_ref = gdal.OpenEx( + "/vsisubfile/0_5359,data/grib/gfs.t06z.pgrb2.10p0.f010.grib2", + open_options=["USE_IDX=NO"], + ) + assert ds_ref.RasterCount == 1 + assert ds_ref.GetRasterBand(1).GetDescription() == '1[-] HYBL="Hybrid level"' + assert ds.GetRasterBand(1).Checksum() == ds_ref.GetRasterBand(1).Checksum() + + size = 16077 - 5359 + ds = gdal.Open(f"/vsisubfile/5359_{size},data/grib/gfs.t06z.pgrb2.10p0.f010.grib2") + assert ds.RasterCount == 2 + assert ds.GetRasterBand(1).GetDescription() == "REFD:2 hybrid level:10 hour fcst" + assert ds.GetRasterBand(2).GetDescription() == "REFC:entire atmosphere:10 hour fcst" + + ds_ref = gdal.OpenEx( + f"/vsisubfile/5359_{size},data/grib/gfs.t06z.pgrb2.10p0.f010.grib2", + open_options=["USE_IDX=NO"], + ) + assert ds_ref.RasterCount == 2 + assert ds_ref.GetRasterBand(1).GetDescription() == '2[-] HYBL="Hybrid level"' + assert ds.GetRasterBand(1).Checksum() == ds_ref.GetRasterBand(1).Checksum() + assert ds.GetRasterBand(2).Checksum() == ds_ref.GetRasterBand(2).Checksum() + + ds = gdal.Open("/vsisubfile/16077_-1,data/grib/gfs.t06z.pgrb2.10p0.f010.grib2") + assert ds.RasterCount == 3 + assert ds.GetRasterBand(1).GetDescription() == "VIS:surface:10 hour fcst" + assert ( + ds.GetRasterBand(2).GetDescription() + == "UGRD:planetary boundary layer:10 hour fcst" + ) + assert ( + ds.GetRasterBand(3).GetDescription() + == "VGRD:planetary boundary layer:10 hour fcst" + ) + + # Test reading a (broken) mix of GRIBv2/GRIBv1 bands diff --git a/frmts/grib/gribdataset.cpp b/frmts/grib/gribdataset.cpp index b4d7867c794e..7a9c4499beb6 100644 --- a/frmts/grib/gribdataset.cpp +++ b/frmts/grib/gribdataset.cpp @@ -922,11 +922,16 @@ char **GRIBRasterBand::GetMetadata(const char *pszDomain) const char *GRIBRasterBand::GetMetadataItem(const char *pszName, const char *pszDomain) { - FindMetaData(); - if (m_nGribVersion == 2 && - CPLTestBool(CPLGetConfigOption("GRIB_PDS_ALL_BANDS", "ON"))) + if (!((!pszDomain || pszDomain[0] == 0) && + (EQUAL(pszName, "STATISTICS_MINIMUM") || + EQUAL(pszName, "STATISTICS_MAXIMUM")))) { - FindPDSTemplateGRIB2(); + FindMetaData(); + if (m_nGribVersion == 2 && + CPLTestBool(CPLGetConfigOption("GRIB_PDS_ALL_BANDS", "ON"))) + { + FindPDSTemplateGRIB2(); + } } return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain); } @@ -1156,7 +1161,8 @@ class InventoryWrapperGrib : public gdal::grib::InventoryWrapper class InventoryWrapperSidecar : public gdal::grib::InventoryWrapper { public: - explicit InventoryWrapperSidecar(VSILFILE *fp) + explicit InventoryWrapperSidecar(VSILFILE *fp, uint64_t nStartOffset, + int64_t nSize) : gdal::grib::InventoryWrapper() { result_ = -1; @@ -1164,26 +1170,25 @@ class InventoryWrapperSidecar : public gdal::grib::InventoryWrapper size_t length = static_cast(VSIFTellL(fp)); if (length > 4 * 1024 * 1024) return; - std::string psSidecar; - psSidecar.resize(length); + std::string osSidecar; + osSidecar.resize(length); VSIFSeekL(fp, 0, SEEK_SET); - if (VSIFReadL(&psSidecar[0], length, 1, fp) != 1) + if (VSIFReadL(&osSidecar[0], length, 1, fp) != 1) return; - CPLStringList aosMsgs( - CSLTokenizeString2(psSidecar.c_str(), "\n", + const CPLStringList aosMsgs( + CSLTokenizeString2(osSidecar.c_str(), "\n", CSLT_PRESERVEQUOTES | CSLT_STRIPLEADSPACES)); - inv_len_ = aosMsgs.size(); inv_ = static_cast( - CPLMalloc(inv_len_ * sizeof(inventoryType))); + CPLCalloc(aosMsgs.size(), sizeof(inventoryType))); - for (size_t i = 0; i < inv_len_; ++i) + for (const char *pszMsg : aosMsgs) { // We are parsing // "msgNum[.subgNum]:start:dontcare:name1:name2:name3" For NOMADS: // "msgNum[.subgNum]:start:reftime:var:level:time" - CPLStringList aosTokens(CSLTokenizeString2( - aosMsgs[i], ":", CSLT_PRESERVEQUOTES | CSLT_ALLOWEMPTYTOKENS)); + const CPLStringList aosTokens(CSLTokenizeString2( + pszMsg, ":", CSLT_PRESERVEQUOTES | CSLT_ALLOWEMPTYTOKENS)); CPLStringList aosNum; if (aosTokens.size() < 6) @@ -1200,7 +1205,7 @@ class InventoryWrapperSidecar : public gdal::grib::InventoryWrapper goto err_sidecar; if (aosNum.size() < 2) - inv_[i].subgNum = 0; + inv_[inv_len_].subgNum = 0; else { auto subgNum = strtol(aosNum[1], &endptr, 10); @@ -1211,21 +1216,29 @@ class InventoryWrapperSidecar : public gdal::grib::InventoryWrapper // .idx file use a 1-based indexing, whereas DEGRIB uses a // 0-based one subgNum--; - inv_[i].subgNum = static_cast(subgNum); + inv_[inv_len_].subgNum = static_cast(subgNum); } - inv_[i].start = strtoll(aosTokens[1], &endptr, 10); + inv_[inv_len_].start = strtoll(aosTokens[1], &endptr, 10); if (*endptr != 0) goto err_sidecar; - inv_[i].unitName = nullptr; - inv_[i].comment = nullptr; - inv_[i].element = nullptr; - inv_[i].shortFstLevel = nullptr; + if (inv_[inv_len_].start < nStartOffset) + continue; + if (nSize > 0 && inv_[inv_len_].start >= nStartOffset + nSize) + break; + + inv_[inv_len_].start -= nStartOffset; + + inv_[inv_len_].unitName = nullptr; + inv_[inv_len_].comment = nullptr; + inv_[inv_len_].element = nullptr; + inv_[inv_len_].shortFstLevel = nullptr; // This is going into the description field -> // the only one available before loading the metadata - inv_[i].longFstLevel = VSIStrdup(CPLSPrintf( + inv_[inv_len_].longFstLevel = VSIStrdup(CPLSPrintf( "%s:%s:%s", aosTokens[3], aosTokens[4], aosTokens[5])); + ++inv_len_; continue; @@ -1233,8 +1246,7 @@ class InventoryWrapperSidecar : public gdal::grib::InventoryWrapper CPLDebug("GRIB", "Failed parsing sidecar entry '%s', " "falling back to constructing an inventory", - aosMsgs[i]); - inv_len_ = static_cast(i); + pszMsg); return; } @@ -1309,22 +1321,43 @@ GRIBDataset::Inventory(VSILFILE *fp, GDALOpenInfo *poOpenInfo) std::unique_ptr pInventories; VSIFSeekL(fp, 0, SEEK_SET); - CPLString sSideCarFilename = CPLString(poOpenInfo->pszFilename) + ".idx"; + std::string osSideCarFilename(poOpenInfo->pszFilename); + uint64_t nStartOffset = 0; + int64_t nSize = -1; + if (STARTS_WITH(poOpenInfo->pszFilename, "/vsisubfile/")) + { + const char *pszPtr = poOpenInfo->pszFilename + strlen("/vsisubfile/"); + const char *pszComma = strchr(pszPtr, ','); + if (pszComma) + { + const CPLStringList aosTokens(CSLTokenizeString2( + std::string(pszPtr, pszComma - pszPtr).c_str(), "_", 0)); + if (aosTokens.size() == 2) + { + nStartOffset = std::strtoull(aosTokens[0], nullptr, 10); + nSize = std::strtoll(aosTokens[1], nullptr, 10); + osSideCarFilename = pszComma + 1; + } + } + } + osSideCarFilename += ".idx"; VSILFILE *fpSideCar = nullptr; if (CPLTestBool(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "USE_IDX", "YES")) && - ((fpSideCar = VSIFOpenL(sSideCarFilename, "rb")) != nullptr)) + ((fpSideCar = VSIFOpenL(osSideCarFilename.c_str(), "rb")) != nullptr)) { CPLDebug("GRIB", "Reading inventories from sidecar file %s", - sSideCarFilename.c_str()); + osSideCarFilename.c_str()); // Contains an GRIB2 message inventory of the file. - pInventories = std::make_unique(fpSideCar); + pInventories = std::make_unique( + fpSideCar, nStartOffset, nSize); if (pInventories->result() <= 0 || pInventories->length() == 0) pInventories = nullptr; VSIFCloseL(fpSideCar); } else - CPLDebug("GRIB", "Failed opening sidecar %s", sSideCarFilename.c_str()); + CPLDebug("GRIB", "Failed opening sidecar %s", + osSideCarFilename.c_str()); if (pInventories == nullptr) { From 981d2233301b2b8c15249777d802b4c6c96391aa Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Mon, 17 Jun 2024 18:31:55 -0400 Subject: [PATCH 0166/1119] Python bindings: Avoid crash when using orphaned subgeometry Fixes https://github.com/OSGeo/gdal/issues/9920 --- autotest/ogr/ogr_geom.py | 10 ++++++++++ swig/include/python/ogr_python.i | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/autotest/ogr/ogr_geom.py b/autotest/ogr/ogr_geom.py index 51245d12808f..df26bc55037a 100755 --- a/autotest/ogr/ogr_geom.py +++ b/autotest/ogr/ogr_geom.py @@ -4519,3 +4519,13 @@ def test_ogr_geom_buffer_with_args(): with pytest.raises(Exception, match="Unsupported buffer option"): geom.Buffer(1, {"QUALITY": "HIGH"}) + + +def test_ogr_subgeom_use_after_parent_free(): + + g = ogr.CreateGeometryFromWkt("POLYGON ((0 0, 1 0, 1 1, 0 0))") + + exterior_ring = g.GetGeometryRef(0) + del g + + assert exterior_ring.GetPointCount() > 0 # does not crash diff --git a/swig/include/python/ogr_python.i b/swig/include/python/ogr_python.i index f8a9af40580b..d8b179e3b2a8 100644 --- a/swig/include/python/ogr_python.i +++ b/swig/include/python/ogr_python.i @@ -990,8 +990,13 @@ def _WarnIfUserHasNotSpecifiedIfUsingExceptions(): def __iter__(self): for i in range(self.GetGeometryCount()): yield self.GetGeometryRef(i) +%} +%feature("pythonappend") GetGeometryRef %{ + if val is not None: + val._parent_geom = self %} + } From 2072ca361be477216c5c7ff43e4ffcf399fed8d7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 17 Jun 2024 21:05:28 +0200 Subject: [PATCH 0167/1119] CI: disable running gnm_test on build-windows-minimum --- .github/workflows/cmake_builds.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index b98a52c2a32c..d3578e9330fe 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -555,6 +555,9 @@ jobs: - name: test (with ctest) shell: bash -l {0} run: | + # gnm_test has suddenly started failing around June 16th 2024 + # Related to image windows-latest 20240603.1.0 / actions/runner-images#10004 + echo "def test_dummy(): pass" > $GITHUB_WORKSPACE/autotest/gnm/gnm_test.py ctest --test-dir $GITHUB_WORKSPACE/build -C RelWithDebInfo -V -j 3 env: SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES From 81cb7e354d925fabac1aac80f5613566cf96ec65 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 17 Jun 2024 17:34:25 +0200 Subject: [PATCH 0168/1119] CI: disable running Windows tests on build-windows-conda to workaround issue with image windows-latest 20240603.1.0 --- .github/workflows/cmake_builds.yml | 35 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index d3578e9330fe..0c5485b69de4 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -466,25 +466,30 @@ jobs: shell: bash -l {0} run: | cmake --build $GITHUB_WORKSPACE/build --config Release --target quicktest - - name: test (with ctest) - shell: bash -l {0} - run: | - ctest --test-dir $GITHUB_WORKSPACE/build -C Release -V -j 3 - env: - SKIP_OGR_GMLAS_HUGE_PROCESSING_TIME: YES - SKIP_OGR_GMLAS_HTTP_RELATED: YES - SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES - BUILD_NAME: "build-windows-conda" + # FIXME !! Disabled because of actions/runner-images#10004 + #- name: test (with ctest) + # shell: bash -l {0} + # run: | + # ctest --test-dir $GITHUB_WORKSPACE/build -C Release -V -j 3 + # env: + # SKIP_OGR_GMLAS_HUGE_PROCESSING_TIME: YES + # SKIP_OGR_GMLAS_HTTP_RELATED: YES + # SKIP_GDAL_HTTP_SSL_VERIFYSTATUS: YES + # BUILD_NAME: "build-windows-conda" - name: Install shell: bash -l {0} run: | cmake --build $GITHUB_WORKSPACE/build --config Release --target install - export PATH=$GITHUB_WORKSPACE/install-gdal/bin:$PATH - gdalinfo --version - python -VV - PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages python -c "from osgeo import gdal;print(gdal.VersionInfo(None))" - export PATH=$GITHUB_WORKSPACE/install-gdal/Scripts:$PATH - PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages gdal_edit --version + # FIXME !! Disabled because of actions/runner-images#10004 + #- name: Test install + # shell: bash -l {0} + # run: | + # export PATH=$GITHUB_WORKSPACE/install-gdal/bin:$PATH + # gdalinfo --version + # python -VV + # PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages python -c "from osgeo import gdal;print(gdal.VersionInfo(None))" + # export PATH=$GITHUB_WORKSPACE/install-gdal/Scripts:$PATH + # PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/site-packages gdal_edit --version - name: Show gdal.pc shell: bash -l {0} run: cat $GITHUB_WORKSPACE/build/gdal.pc From 2e92c04589d504e8372fa5ea05e33ce4fc9cc75d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Jun 2024 12:33:54 +0200 Subject: [PATCH 0169/1119] netCDF vector: use CF-1.8 with FORMAT=NC4 and GEOMETRY_ENCODING=WKT --- frmts/netcdf/netcdfdataset.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/frmts/netcdf/netcdfdataset.cpp b/frmts/netcdf/netcdfdataset.cpp index 6ee312b5213f..d706cba221ac 100644 --- a/frmts/netcdf/netcdfdataset.cpp +++ b/frmts/netcdf/netcdfdataset.cpp @@ -8372,13 +8372,12 @@ GDALDataset *netCDFDataset::Open(GDALOpenInfo *poOpenInfo) bool bHasSimpleGeometries = false; // but not necessarily valid if (poDS->nCFVersion >= 1.8) { - poDS->bSGSupport = true; bHasSimpleGeometries = poDS->DetectAndFillSGLayers(cdfid); - poDS->vcdf.enableFullVirtualMode(); - } - else - { - poDS->bSGSupport = false; + if (bHasSimpleGeometries) + { + poDS->bSGSupport = true; + poDS->vcdf.enableFullVirtualMode(); + } } char szConventions[NC_MAX_NAME + 1]; @@ -9347,9 +9346,12 @@ GDALDataset *netCDFDataset::Create(const char *pszFilename, int nXSize, // Add Conventions, GDAL info and history. if (poDS->cdfid >= 0) { - const char *CF_Vector_Conv = poDS->bSGSupport - ? NCDF_CONVENTIONS_CF_V1_8 - : NCDF_CONVENTIONS_CF_V1_6; + const char *CF_Vector_Conv = + poDS->bSGSupport || + // Use of variable length strings require CF-1.8 + EQUAL(aosOptions.FetchNameValueDef("FORMAT", ""), "NC4") + ? NCDF_CONVENTIONS_CF_V1_8 + : NCDF_CONVENTIONS_CF_V1_6; poDS->bWriteGDALVersion = CPLTestBool( CSLFetchNameValueDef(papszOptions, "WRITE_GDAL_VERSION", "YES")); poDS->bWriteGDALHistory = CPLTestBool( From 860eee15c81c0484efa675d930ad7bf5e8118d29 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Jun 2024 12:34:11 +0200 Subject: [PATCH 0170/1119] netcdf.py: disable cfchecker tests that can't pass --- autotest/gdrivers/netcdf.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/autotest/gdrivers/netcdf.py b/autotest/gdrivers/netcdf.py index ff21d67235b5..8e7b2d57c741 100755 --- a/autotest/gdrivers/netcdf.py +++ b/autotest/gdrivers/netcdf.py @@ -2179,10 +2179,12 @@ def test_netcdf_52(): f = None ds = None - import netcdf_cf - - if netcdf_cf.cfchecks_available(): - netcdf_cf.netcdf_cf_check_file("tmp/netcdf_52.nc", "auto") + # Latest release version of cfchecker (4.1.0) doesn't support variable-length + # strings as valid variable types, but next one will: + # https://github.com/cedadev/cf-checker/blob/c0486c606f7cf4d38d3b484b427726ce1bde73ee/src/cfchecker/cfchecks.py#L745 + # import netcdf_cf + # if netcdf_cf.cfchecks_available(): + # netcdf_cf.netcdf_cf_check_file("tmp/netcdf_52.nc", "auto") gdal.Unlink("tmp/netcdf_52.nc") gdal.Unlink("tmp/netcdf_52.csv") @@ -2650,10 +2652,13 @@ def test_netcdf_62(): assert "char station(profile" in hdr assert "char foo(record" in hdr - import netcdf_cf - - if netcdf_cf.cfchecks_available(): - netcdf_cf.netcdf_cf_check_file("tmp/netcdf_62.nc", "auto") + # Disable cfchecker validation as it fails with a '(5): co-ordinate variable not monotonic' + # error which I believe is incorrect given the particular nature of + # a https://cfconventions.org/Data/cf-conventions/cf-conventions-1.11/cf-conventions.html#_indexed_ragged_array_representation_of_profiles + # where coordinate variables can clearly not be sorted in any order. + # import netcdf_cf + # if netcdf_cf.cfchecks_available(): + # netcdf_cf.netcdf_cf_check_file("tmp/netcdf_62.nc", "auto") gdal.Unlink("tmp/netcdf_62.nc") From 8ccd1bc4d5f797053e5e13b551a5701e70fb6465 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 17 Jun 2024 00:26:46 +0200 Subject: [PATCH 0171/1119] CI: cfchecker: move it to Ubuntu 24.04 as the latest version of python netCDF4 (1.7.0) is not compatible of 20.04 --- .github/workflows/ubuntu_20.04/Dockerfile.ci | 1 - .github/workflows/ubuntu_24.04/Dockerfile.ci | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ubuntu_20.04/Dockerfile.ci b/.github/workflows/ubuntu_20.04/Dockerfile.ci index 571bdd4495db..af9107f7e3bb 100644 --- a/.github/workflows/ubuntu_20.04/Dockerfile.ci +++ b/.github/workflows/ubuntu_20.04/Dockerfile.ci @@ -262,4 +262,3 @@ RUN ldconfig COPY requirements.txt /tmp/ RUN python3 -m pip install -U -r /tmp/requirements.txt -RUN python3 -m pip install cfchecker diff --git a/.github/workflows/ubuntu_24.04/Dockerfile.ci b/.github/workflows/ubuntu_24.04/Dockerfile.ci index ca7d79c92bb1..16bb2c18b01a 100644 --- a/.github/workflows/ubuntu_24.04/Dockerfile.ci +++ b/.github/workflows/ubuntu_24.04/Dockerfile.ci @@ -129,3 +129,6 @@ RUN ln -s /usr/lib/x86_64-linux-gnu/ogdi/4.1/libvrf.so /usr/lib/x86_64-linux-gnu COPY requirements.txt /tmp/ RUN python3 -m pip install -U --break-system-packages -r /tmp/requirements.txt +# cfchecker requires udunits2 +RUN apt-get install -y --allow-unauthenticated libudunits2-0 libudunits2-data +RUN python3 -m pip install --break-system-packages cfchecker From 9ea74340c2a654e692df09d6b78e9f1b81262d60 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 17 Jun 2024 18:51:17 +0200 Subject: [PATCH 0172/1119] Python bindings: make them compatible of SWIG 4.3.0dev --- swig/include/python/typemaps_python.i | 107 +++++++++++++++++++------- 1 file changed, 81 insertions(+), 26 deletions(-) diff --git a/swig/include/python/typemaps_python.i b/swig/include/python/typemaps_python.i index 410ebd03cb98..5ff42d4f31c3 100644 --- a/swig/include/python/typemaps_python.i +++ b/swig/include/python/typemaps_python.i @@ -92,12 +92,15 @@ if ( !*$2 ) { Py_INCREF(Py_None); r = Py_None; - $result = t_output_helper($result,r); } else { r = PyFloat_FromDouble( *$1 ); - $result = t_output_helper($result,r); } +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,r,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,r); +%#endif } @@ -112,12 +115,15 @@ if ( !*$2 ) { Py_INCREF(Py_None); r = Py_None; - $result = t_output_helper($result,r); } else { r = PyLong_FromLongLong( *$1 ); - $result = t_output_helper($result,r); } +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,r,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,r); +%#endif } @@ -132,12 +138,15 @@ if ( !*$2 ) { Py_INCREF(Py_None); r = Py_None; - $result = t_output_helper($result,r); } else { r = PyLong_FromUnsignedLongLong( *$1 ); - $result = t_output_helper($result,r); } +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,r,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,r); +%#endif } @@ -151,7 +160,7 @@ %enddef %define TYPEMAP_ARGOUT_ARGOUT_ARRAY_IS_VALID(num_values) -%typemap(argout, fragment="t_output_helper,CreateTupleFromDoubleArray") (double argout[num_values], int* isvalid) +%typemap(argout, fragment="CreateTupleFromDoubleArray") (double argout[num_values], int* isvalid) { /* %typemap(argout) (double argout[num_values], int* isvalid) */ PyObject *r; @@ -162,7 +171,11 @@ else { r = CreateTupleFromDoubleArray($1, num_values); } - $result = t_output_helper($result,r); +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,r,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,r); +%#endif } %enddef @@ -281,11 +294,15 @@ CreateTupleFromDoubleArray( const double *first, size_t size ) { memset(argout, 0, sizeof(argout)); $1 = argout; } -%typemap(argout,fragment="t_output_helper,CreateTupleFromDoubleArray") ( double argout[ANY]) +%typemap(argout,fragment="CreateTupleFromDoubleArray") ( double argout[ANY]) { /* %typemap(argout) (double argout[ANY]) */ PyObject *out = CreateTupleFromDoubleArray( $1, $dim0 ); - $result = t_output_helper($result,out); +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,out,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,out); +%#endif } %typemap(in,numinputs=0) ( double *argout[ANY]) (double *argout) @@ -294,11 +311,15 @@ CreateTupleFromDoubleArray( const double *first, size_t size ) { argout = NULL; $1 = &argout; } -%typemap(argout,fragment="t_output_helper,CreateTupleFromDoubleArray") ( double *argout[ANY]) +%typemap(argout,fragment="CreateTupleFromDoubleArray") ( double *argout[ANY]) { /* %typemap(argout) (double *argout[ANY]) */ PyObject *out = CreateTupleFromDoubleArray( *$1, $dim0 ); - $result = t_output_helper($result,out); +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,out,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,out); +%#endif } %typemap(freearg) (double *argout[ANY]) { @@ -1460,7 +1481,7 @@ static PyObject* CSLToList( char** stringarray, bool *pbErr ) /* %typemap(in,numinputs=0) (char **argout) */ $1 = &argout; } -%typemap(argout,fragment="t_output_helper") (char **argout) +%typemap(argout) (char **argout) { /* %typemap(argout) (char **argout) */ PyObject *o; @@ -1471,7 +1492,11 @@ static PyObject* CSLToList( char** stringarray, bool *pbErr ) o = Py_None; Py_INCREF( o ); } - $result = t_output_helper($result, o); +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,o,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,o); +%#endif } %typemap(freearg) (char **argout) { @@ -2588,7 +2613,11 @@ DecomposeSequenceOf4DCoordinates( PyObject *seq, int nCount, double *x, double * PyTuple_SetItem( r, 0, PyLong_FromLong(*$1) ); PyTuple_SetItem( r, 1, PyLong_FromLong(*$2) ); } - $result = t_output_helper($result,r); +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,r,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,r); +%#endif } %typemap(in,numinputs=0) (OGRLayerShadow** ppoBelongingLayer, double* pdfProgressPct) ( OGRLayerShadow* poBelongingLayer = NULL, double dfProgressPct = 0 ) @@ -2617,14 +2646,19 @@ DecomposeSequenceOf4DCoordinates( PyObject *seq, int nCount, double *x, double * PyList_SetItem($result, 0, Py_None); } + PyObject* r; if ( !*$1 ) { + r = Py_None; Py_INCREF(Py_None); - $result = SWIG_Python_AppendOutput($result, Py_None); } else { - $result = SWIG_Python_AppendOutput($result, - SWIG_NewPointerObj(SWIG_as_voidptr( *$1), SWIGTYPE_p_OGRLayerShadow, 0 )); + r = SWIG_NewPointerObj(SWIG_as_voidptr( *$1), SWIGTYPE_p_OGRLayerShadow, 0 ); } +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,r,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,r); +%#endif } if( arg3 ) @@ -2634,7 +2668,12 @@ DecomposeSequenceOf4DCoordinates( PyObject *seq, int nCount, double *x, double * $result = PyList_New(1); PyList_SetItem($result, 0, Py_None); } - $result = SWIG_Python_AppendOutput($result, PyFloat_FromDouble( *$2)); + PyObject* r = PyFloat_FromDouble( *$2); +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,r,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,r); +%#endif } } @@ -2907,7 +2946,7 @@ OBJECT_LIST_INPUT_ITEM_MAY_BE_NULL(GDALDimensionHS); $1 = &vals; $2 = &nCount; } -%typemap(argout, fragment="t_output_helper,CreateTupleFromIntArray") (int** pvals, size_t* pnCount) +%typemap(argout, fragment="CreateTupleFromIntArray") (int** pvals, size_t* pnCount) { /* %typemap(argout) (int** pvals, size_t* pnCount) */ PyObject *list = CreateTupleFromIntArray(*$1, *$2); @@ -2930,7 +2969,7 @@ OBJECT_LIST_INPUT_ITEM_MAY_BE_NULL(GDALDimensionHS); $1 = &vals; $2 = &nCount; } -%typemap(argout, fragment="t_output_helper,CreateTupleFromInt64Array") (long long** pvals, size_t* pnCount) +%typemap(argout, fragment="CreateTupleFromInt64Array") (long long** pvals, size_t* pnCount) { /* %typemap(argout) (int** pvals, size_t* pnCount) */ PyObject *list = CreateTupleFromInt64Array(*$1, *$2); @@ -2953,7 +2992,7 @@ OBJECT_LIST_INPUT_ITEM_MAY_BE_NULL(GDALDimensionHS); $1 = &vals; $2 = &nCount; } -%typemap(argout, fragment="t_output_helper,CreateTupleFromDoubleArray") (double** pvals, size_t* pnCount) +%typemap(argout, fragment="CreateTupleFromDoubleArray") (double** pvals, size_t* pnCount) { /* %typemap(argout) (double** pvals, size_t* pnCount) */ PyObject *list = CreateTupleFromDoubleArray(*$1, *$2); @@ -3019,7 +3058,11 @@ OBJECT_LIST_INPUT(GDALEDTComponentHS) PyTuple_SetItem( r, 2, PyFloat_FromDouble($1[2])); PyTuple_SetItem( r, 3, PyFloat_FromDouble($1[3])); PyTuple_SetItem( r, 4, PyLong_FromLong($2[0])); - $result = t_output_helper($result,r); +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,r,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,r); +%#endif } @@ -3556,7 +3599,11 @@ OBJECT_LIST_INPUT(GDALMDArrayHS); PyTuple_SetItem( r, 0, PyBool_FromLong(*$1) ); PyTuple_SetItem( r, 1, PyLong_FromLong(*$2) ); PyTuple_SetItem( r, 2, PyLong_FromLong(*$3) ); - $result = t_output_helper($result,r); +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,r,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,r); +%#endif } @@ -3583,7 +3630,11 @@ OBJECT_LIST_INPUT(GDALMDArrayHS); Py_INCREF(Py_None); PyTuple_SetItem( r, 1, Py_None ); } - $result = t_output_helper($result,r); +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,r,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,r); +%#endif } @@ -3634,6 +3685,10 @@ OBJECT_LIST_INPUT(GDALMDArrayHS); PyTuple_SetItem( r, 3, PyLong_FromUnsignedLongLong(*$5) ); PyTuple_SetItem( r, 4, PyLong_FromUnsignedLongLong(*$6) ); PyTuple_SetItem( r, 5, PyLong_FromUnsignedLongLong(*$7) ); - $result = t_output_helper($result,r); +%#if SWIG_VERSION >= 0x040300 + $result = SWIG_Python_AppendOutput($result,r,$isvoid); +%#else + $result = SWIG_Python_AppendOutput($result,r); +%#endif } } From bdd05e2510b813eb10ca95a2a39caf717d7dd6c0 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 17 Jun 2024 18:51:26 +0200 Subject: [PATCH 0173/1119] CI: test swig master --- .github/workflows/alpine/Dockerfile.ci | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/alpine/Dockerfile.ci b/.github/workflows/alpine/Dockerfile.ci index 7adca61bd2fc..2506ff2739da 100644 --- a/.github/workflows/alpine/Dockerfile.ci +++ b/.github/workflows/alpine/Dockerfile.ci @@ -62,7 +62,6 @@ RUN apk add \ sfcgal-dev \ snappy-dev \ sqlite-dev \ - swig \ tiledb-dev \ tiff-dev \ unixodbc-dev \ @@ -73,3 +72,11 @@ RUN apk add \ COPY requirements.txt /tmp/ RUN python3 -m pip install --break-system-packages -U -r /tmp/requirements.txt + +RUN apk add git autoconf automake libtool bison && \ + git clone --branch "${SWIG_GIT_TAG:-master}" --depth 1 https://github.com/swig/swig.git swig-git && \ + cd swig-git && \ + ./autogen.sh && \ + ./configure --prefix=/usr && \ + make -j$(nproc) && \ + make install From 5818deac9fddd2a30b4828cf4c13851a9e89714d Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Tue, 18 Jun 2024 14:54:41 +0200 Subject: [PATCH 0174/1119] [gpkg] Implement ALTER TABLE RENAME for rasters (#10213) Fix #10201 --- autotest/gdrivers/gpkg.py | 50 +++ autotest/ogr/ogr_gpkg.py | 39 ++ ogr/ogrsf_frmts/gpkg/ogr_geopackage.h | 5 + .../gpkg/ogrgeopackagedatasource.cpp | 413 ++++++++++++++---- 4 files changed, 412 insertions(+), 95 deletions(-) diff --git a/autotest/gdrivers/gpkg.py b/autotest/gdrivers/gpkg.py index 1a9bb542a4cf..6a5237b52d8b 100755 --- a/autotest/gdrivers/gpkg.py +++ b/autotest/gdrivers/gpkg.py @@ -4298,3 +4298,53 @@ def test_gpkg_gti_gpkg_ext(tmp_vsimem): ds = gdal.Open(filename) assert ds.GetDriver().ShortName == "GPKG" assert ds.GetRasterBand(1).Checksum() == 4672 + + +############################################################################### +# Test rename a raster table with SQL + + +@pytest.mark.parametrize("data_type", [gdal.GDT_Byte, gdal.GDT_UInt16]) +def test_gpkg_rename_raster_table(data_type, tmp_vsimem): + + test_layer_path = str(tmp_vsimem / "test_gpkg_rename_raster_table.gpkg") + + if data_type == gdal.GDT_UInt16: + src_ds = gdal.Open("data/int16.tif") + else: + src_ds = gdal.Open("data/small_world.tif") + + ds = gdaltest.gpkg_dr.CreateCopy( + test_layer_path, + src_ds, + options=[ + "TILE_FORMAT=PNG", + "RASTER_TABLE=weird'layer\"name", + ], + ) + ds = None + src_ds = None + + ds = gdal.OpenEx(test_layer_path, gdal.OF_RASTER | gdal.OF_UPDATE) + # Get layer name + layer_name = ds.GetMetadataItem("IDENTIFIER") + assert layer_name == "weird'layer\"name" + + checksum = ds.GetRasterBand(1).Checksum() + + ds.ExecuteSQL('ALTER TABLE "weird\'layer""name" RENAME TO bar') + ds.ExecuteSQL("VACUUM") + ds = None + + ds = gdal.Open(test_layer_path, gdal.OF_RASTER) + layer_name = ds.GetMetadataItem("IDENTIFIER") + assert layer_name == "bar" + assert ds.GetRasterBand(1).Checksum() == checksum + ds = None + + # Check that there is no more any reference to the layer + f = gdal.VSIFOpenL(test_layer_path, "rb") + content = gdal.VSIFReadL(1, 1000000, f).decode("latin1") + gdal.VSIFCloseL(f) + + assert "weird" not in content diff --git a/autotest/ogr/ogr_gpkg.py b/autotest/ogr/ogr_gpkg.py index 6b8cfd24aec6..133caca0d42f 100755 --- a/autotest/ogr/ogr_gpkg.py +++ b/autotest/ogr/ogr_gpkg.py @@ -10510,3 +10510,42 @@ def test_ogr_gpkg_launder(tmp_vsimem): assert lyr.GetGeometryColumn() == "my_geom" lyr.CreateField(ogr.FieldDefn("_")) assert lyr.GetLayerDefn().GetFieldDefn(0).GetNameRef() == "x_" + + +############################################################################### +# Test rename a "hidden" table with SQL + + +def test_gpkg_rename_hidden_table(tmp_vsimem): + + test_layer_path = str(tmp_vsimem / "test_gpkg_rename_hidden_table.gpkg") + + src_ds = gdal.OpenEx("../ogr/data/poly.shp") + gdal.VectorTranslate(test_layer_path, src_ds) + src_ds = None + + dst_ds = gdal.OpenEx( + test_layer_path, gdal.OF_UPDATE, open_options=["LIST_ALL_TABLES=NO"] + ) + dst_ds.ExecuteSQL("CREATE TABLE hidden_foo_table(id integer primary key);") + dst_ds = None + + dst_ds = gdal.OpenEx( + test_layer_path, gdal.OF_UPDATE, open_options=["LIST_ALL_TABLES=NO"] + ) + dst_ds.ExecuteSQL("ALTER TABLE hidden_foo_table RENAME TO hidden_bar_table") + dst_ds.ExecuteSQL("VACUUM") + dst_ds = None + + dst_ds = gdal.OpenEx(test_layer_path) + # Verify that layer exists + lyr = dst_ds.GetLayerByName("hidden_bar_table") + assert lyr is not None + dst_ds = None + + # Check that there is no more any reference to the layer + f = gdal.VSIFOpenL(test_layer_path, "rb") + content = gdal.VSIFReadL(1, 1000000, f).decode("latin1") + gdal.VSIFCloseL(f) + + assert "hidden_foo_table" not in content diff --git a/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h b/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h index f2509a11a3f6..46fc95e25509 100644 --- a/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h +++ b/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h @@ -275,6 +275,10 @@ class GDALGeoPackageDataset final : public OGRSQLiteBaseDataSource, OGRErr DeleteLayerCommon(const char *pszLayerName); OGRErr DeleteRasterLayer(const char *pszLayerName); bool DeleteVectorOrRasterLayer(const char *pszLayerName); + bool RenameVectorOrRasterLayer(const char *pszLayerName, + const char *pszNewName); + bool RenameRasterLayer(const char *pszLayerName, + const char *pszNewLayerName); bool ConvertGpkgSpatialRefSysToExtensionWkt2(bool bForceEpoch); void DetectSpatialRefSysColumns(); @@ -477,6 +481,7 @@ class GDALGeoPackageDataset final : public OGRSQLiteBaseDataSource, bool OpenOrCreateDB(int flags); void InstallSQLFunctions(); bool HasGDALAspatialExtension(); + std::string CreateRasterTriggersSQL(const std::string &osTableName); }; /************************************************************************/ diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp index c677d10fdf7c..8f5ca7c1edc9 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp @@ -5613,97 +5613,8 @@ int GDALGeoPackageDataset::Create(const char *pszFilename, int nXSize, if (bCreateTriggers) { - /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger - * Definition SQL */ - pszSQL = sqlite3_mprintf( - "CREATE TRIGGER \"%w_zoom_insert\" " - "BEFORE INSERT ON \"%w\" " - "FOR EACH ROW BEGIN " - "SELECT RAISE(ABORT, 'insert on table ''%q'' violates " - "constraint: zoom_level not specified for table in " - "gpkg_tile_matrix') " - "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM " - "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; " - "END; " - "CREATE TRIGGER \"%w_zoom_update\" " - "BEFORE UPDATE OF zoom_level ON \"%w\" " - "FOR EACH ROW BEGIN " - "SELECT RAISE(ABORT, 'update on table ''%q'' violates " - "constraint: zoom_level not specified for table in " - "gpkg_tile_matrix') " - "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM " - "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; " - "END; " - "CREATE TRIGGER \"%w_tile_column_insert\" " - "BEFORE INSERT ON \"%w\" " - "FOR EACH ROW BEGIN " - "SELECT RAISE(ABORT, 'insert on table ''%q'' violates " - "constraint: tile_column cannot be < 0') " - "WHERE (NEW.tile_column < 0) ; " - "SELECT RAISE(ABORT, 'insert on table ''%q'' violates " - "constraint: tile_column must by < matrix_width specified for " - "table and zoom level in gpkg_tile_matrix') " - "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM " - "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND " - "zoom_level = NEW.zoom_level)); " - "END; " - "CREATE TRIGGER \"%w_tile_column_update\" " - "BEFORE UPDATE OF tile_column ON \"%w\" " - "FOR EACH ROW BEGIN " - "SELECT RAISE(ABORT, 'update on table ''%q'' violates " - "constraint: tile_column cannot be < 0') " - "WHERE (NEW.tile_column < 0) ; " - "SELECT RAISE(ABORT, 'update on table ''%q'' violates " - "constraint: tile_column must by < matrix_width specified for " - "table and zoom level in gpkg_tile_matrix') " - "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM " - "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND " - "zoom_level = NEW.zoom_level)); " - "END; " - "CREATE TRIGGER \"%w_tile_row_insert\" " - "BEFORE INSERT ON \"%w\" " - "FOR EACH ROW BEGIN " - "SELECT RAISE(ABORT, 'insert on table ''%q'' violates " - "constraint: tile_row cannot be < 0') " - "WHERE (NEW.tile_row < 0) ; " - "SELECT RAISE(ABORT, 'insert on table ''%q'' violates " - "constraint: tile_row must by < matrix_height specified for " - "table and zoom level in gpkg_tile_matrix') " - "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM " - "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND " - "zoom_level = NEW.zoom_level)); " - "END; " - "CREATE TRIGGER \"%w_tile_row_update\" " - "BEFORE UPDATE OF tile_row ON \"%w\" " - "FOR EACH ROW BEGIN " - "SELECT RAISE(ABORT, 'update on table ''%q'' violates " - "constraint: tile_row cannot be < 0') " - "WHERE (NEW.tile_row < 0) ; " - "SELECT RAISE(ABORT, 'update on table ''%q'' violates " - "constraint: tile_row must by < matrix_height specified for " - "table and zoom level in gpkg_tile_matrix') " - "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM " - "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND " - "zoom_level = NEW.zoom_level)); " - "END; ", - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str(), - m_osRasterTable.c_str(), m_osRasterTable.c_str()); - osSQL += ";"; - osSQL += pszSQL; - sqlite3_free(pszSQL); + osSQL += CreateRasterTriggersSQL(m_osRasterTable); } OGRErr eErr = SQLCommand(hDB, osSQL); @@ -7324,6 +7235,228 @@ bool GDALGeoPackageDataset::DeleteVectorOrRasterLayer(const char *pszLayerName) return false; } +bool GDALGeoPackageDataset::RenameVectorOrRasterLayer( + const char *pszLayerName, const char *pszNewLayerName) +{ + int idx = FindLayerIndex(pszLayerName); + if (idx >= 0) + { + m_papoLayers[idx]->Rename(pszNewLayerName); + return true; + } + + char *pszSQL = + sqlite3_mprintf("SELECT 1 FROM gpkg_contents WHERE " + "lower(table_name) = lower('%q') " + "AND data_type IN ('tiles', '2d-gridded-coverage')", + pszLayerName); + const bool bIsRasterTable = SQLGetInteger(hDB, pszSQL, nullptr) == 1; + sqlite3_free(pszSQL); + + if (bIsRasterTable) + { + return RenameRasterLayer(pszLayerName, pszNewLayerName); + } + + return false; +} + +bool GDALGeoPackageDataset::RenameRasterLayer(const char *pszLayerName, + const char *pszNewLayerName) +{ + std::string osSQL; + + char *pszSQL = sqlite3_mprintf( + "SELECT 1 FROM sqlite_master WHERE lower(name) = lower('%q') " + "AND type IN ('table', 'view')", + pszNewLayerName); + const bool bAlreadyExists = SQLGetInteger(GetDB(), pszSQL, nullptr) == 1; + sqlite3_free(pszSQL); + if (bAlreadyExists) + { + CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists", + pszNewLayerName); + return false; + } + + // Temporary remove foreign key checks + const GPKGTemporaryForeignKeyCheckDisabler + oGPKGTemporaryForeignKeyCheckDisabler(this); + + if (SoftStartTransaction() != OGRERR_NONE) + { + return false; + } + + pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET table_name = '%q' WHERE " + "lower(table_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL = pszSQL; + sqlite3_free(pszSQL); + + pszSQL = sqlite3_mprintf("UPDATE gpkg_contents SET identifier = '%q' WHERE " + "lower(identifier) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + + pszSQL = + sqlite3_mprintf("UPDATE gpkg_tile_matrix SET table_name = '%q' WHERE " + "lower(table_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + + pszSQL = sqlite3_mprintf( + "UPDATE gpkg_tile_matrix_set SET table_name = '%q' WHERE " + "lower(table_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + + if (HasGriddedCoverageAncillaryTable()) + { + pszSQL = sqlite3_mprintf("UPDATE gpkg_2d_gridded_coverage_ancillary " + "SET tile_matrix_set_name = '%q' WHERE " + "lower(tile_matrix_set_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + + pszSQL = sqlite3_mprintf( + "UPDATE gpkg_2d_gridded_tile_ancillary SET tpudt_name = '%q' WHERE " + "lower(tpudt_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + } + + if (HasExtensionsTable()) + { + pszSQL = sqlite3_mprintf( + "UPDATE gpkg_extensions SET table_name = '%q' WHERE " + "lower(table_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + } + + if (HasMetadataTables()) + { + pszSQL = sqlite3_mprintf( + "UPDATE gpkg_metadata_reference SET table_name = '%q' WHERE " + "lower(table_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + } + + if (HasDataColumnsTable()) + { + pszSQL = sqlite3_mprintf( + "UPDATE gpkg_data_columns SET table_name = '%q' WHERE " + "lower(table_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + } + + if (HasQGISLayerStyles()) + { + // Update QGIS styles + pszSQL = + sqlite3_mprintf("UPDATE layer_styles SET f_table_name = '%q' WHERE " + "lower(f_table_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + } + +#ifdef ENABLE_GPKG_OGR_CONTENTS + if (m_bHasGPKGOGRContents) + { + pszSQL = sqlite3_mprintf( + "UPDATE gpkg_ogr_contents SET table_name = '%q' WHERE " + "lower(table_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + } +#endif + + if (HasGpkgextRelationsTable()) + { + pszSQL = sqlite3_mprintf( + "UPDATE gpkgext_relations SET base_table_name = '%q' WHERE " + "lower(base_table_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + + pszSQL = sqlite3_mprintf( + "UPDATE gpkgext_relations SET related_table_name = '%q' WHERE " + "lower(related_table_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + + pszSQL = sqlite3_mprintf( + "UPDATE gpkgext_relations SET mapping_table_name = '%q' WHERE " + "lower(mapping_table_name) = lower('%q');", + pszNewLayerName, pszLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + } + + // Drop all triggers for the layer + pszSQL = sqlite3_mprintf("SELECT name FROM sqlite_master WHERE type = " + "'trigger' AND tbl_name = '%q'", + pszLayerName); + auto oTriggerResult = SQLQuery(GetDB(), pszSQL); + sqlite3_free(pszSQL); + if (oTriggerResult) + { + for (int i = 0; i < oTriggerResult->RowCount(); i++) + { + const char *pszTriggerName = oTriggerResult->GetValue(0, i); + pszSQL = sqlite3_mprintf("DROP TRIGGER IF EXISTS \"%w\";", + pszTriggerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + } + } + + pszSQL = sqlite3_mprintf("ALTER TABLE \"%w\" RENAME TO \"%w\";", + pszLayerName, pszNewLayerName); + osSQL += pszSQL; + sqlite3_free(pszSQL); + + // Recreate all zoom/tile triggers + if (oTriggerResult) + { + osSQL += CreateRasterTriggersSQL(pszNewLayerName); + } + + OGRErr eErr = SQLCommand(GetDB(), osSQL.c_str()); + + // Check foreign key integrity + if (eErr == OGRERR_NONE) + { + eErr = PragmaCheck("foreign_key_check", "", 0); + } + + if (eErr == OGRERR_NONE) + { + eErr = SoftCommitTransaction(); + } + else + { + SoftRollbackTransaction(); + } + + return eErr == OGRERR_NONE; +} + /************************************************************************/ /* TestCapability() */ /************************************************************************/ @@ -7516,12 +7649,9 @@ OGRLayer *GDALGeoPackageDataset::ExecuteSQL(const char *pszSQLCommand, { const char *pszSrcTableName = papszTokens[2]; const char *pszDstTableName = papszTokens[5]; - OGRGeoPackageTableLayer *poSrcLayer = - dynamic_cast( - GetLayerByName(SQLUnescape(pszSrcTableName))); - if (poSrcLayer) + if (RenameVectorOrRasterLayer(SQLUnescape(pszSrcTableName), + SQLUnescape(pszDstTableName))) { - poSrcLayer->Rename(SQLUnescape(pszDstTableName)); CSLDestroy(papszTokens); return nullptr; } @@ -7933,6 +8063,99 @@ bool GDALGeoPackageDataset::HasGDALAspatialExtension() return bHasExtension; } +std::string +GDALGeoPackageDataset::CreateRasterTriggersSQL(const std::string &osTableName) +{ + char *pszSQL; + std::string osSQL; + /* From D.5. sample_tile_pyramid Table 43. tiles table Trigger + * Definition SQL */ + pszSQL = sqlite3_mprintf( + "CREATE TRIGGER \"%w_zoom_insert\" " + "BEFORE INSERT ON \"%w\" " + "FOR EACH ROW BEGIN " + "SELECT RAISE(ABORT, 'insert on table ''%q'' violates " + "constraint: zoom_level not specified for table in " + "gpkg_tile_matrix') " + "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM " + "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; " + "END; " + "CREATE TRIGGER \"%w_zoom_update\" " + "BEFORE UPDATE OF zoom_level ON \"%w\" " + "FOR EACH ROW BEGIN " + "SELECT RAISE(ABORT, 'update on table ''%q'' violates " + "constraint: zoom_level not specified for table in " + "gpkg_tile_matrix') " + "WHERE NOT (NEW.zoom_level IN (SELECT zoom_level FROM " + "gpkg_tile_matrix WHERE lower(table_name) = lower('%q'))) ; " + "END; " + "CREATE TRIGGER \"%w_tile_column_insert\" " + "BEFORE INSERT ON \"%w\" " + "FOR EACH ROW BEGIN " + "SELECT RAISE(ABORT, 'insert on table ''%q'' violates " + "constraint: tile_column cannot be < 0') " + "WHERE (NEW.tile_column < 0) ; " + "SELECT RAISE(ABORT, 'insert on table ''%q'' violates " + "constraint: tile_column must by < matrix_width specified for " + "table and zoom level in gpkg_tile_matrix') " + "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM " + "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND " + "zoom_level = NEW.zoom_level)); " + "END; " + "CREATE TRIGGER \"%w_tile_column_update\" " + "BEFORE UPDATE OF tile_column ON \"%w\" " + "FOR EACH ROW BEGIN " + "SELECT RAISE(ABORT, 'update on table ''%q'' violates " + "constraint: tile_column cannot be < 0') " + "WHERE (NEW.tile_column < 0) ; " + "SELECT RAISE(ABORT, 'update on table ''%q'' violates " + "constraint: tile_column must by < matrix_width specified for " + "table and zoom level in gpkg_tile_matrix') " + "WHERE NOT (NEW.tile_column < (SELECT matrix_width FROM " + "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND " + "zoom_level = NEW.zoom_level)); " + "END; " + "CREATE TRIGGER \"%w_tile_row_insert\" " + "BEFORE INSERT ON \"%w\" " + "FOR EACH ROW BEGIN " + "SELECT RAISE(ABORT, 'insert on table ''%q'' violates " + "constraint: tile_row cannot be < 0') " + "WHERE (NEW.tile_row < 0) ; " + "SELECT RAISE(ABORT, 'insert on table ''%q'' violates " + "constraint: tile_row must by < matrix_height specified for " + "table and zoom level in gpkg_tile_matrix') " + "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM " + "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND " + "zoom_level = NEW.zoom_level)); " + "END; " + "CREATE TRIGGER \"%w_tile_row_update\" " + "BEFORE UPDATE OF tile_row ON \"%w\" " + "FOR EACH ROW BEGIN " + "SELECT RAISE(ABORT, 'update on table ''%q'' violates " + "constraint: tile_row cannot be < 0') " + "WHERE (NEW.tile_row < 0) ; " + "SELECT RAISE(ABORT, 'update on table ''%q'' violates " + "constraint: tile_row must by < matrix_height specified for " + "table and zoom level in gpkg_tile_matrix') " + "WHERE NOT (NEW.tile_row < (SELECT matrix_height FROM " + "gpkg_tile_matrix WHERE lower(table_name) = lower('%q') AND " + "zoom_level = NEW.zoom_level)); " + "END; ", + osTableName.c_str(), osTableName.c_str(), osTableName.c_str(), + osTableName.c_str(), osTableName.c_str(), osTableName.c_str(), + osTableName.c_str(), osTableName.c_str(), osTableName.c_str(), + osTableName.c_str(), osTableName.c_str(), osTableName.c_str(), + osTableName.c_str(), osTableName.c_str(), osTableName.c_str(), + osTableName.c_str(), osTableName.c_str(), osTableName.c_str(), + osTableName.c_str(), osTableName.c_str(), osTableName.c_str(), + osTableName.c_str(), osTableName.c_str(), osTableName.c_str(), + osTableName.c_str(), osTableName.c_str(), osTableName.c_str(), + osTableName.c_str()); + osSQL = pszSQL; + sqlite3_free(pszSQL); + return osSQL; +} + /************************************************************************/ /* CreateExtensionsTableIfNecessary() */ /************************************************************************/ From b7b9352caf829b4274afc411850445d7672ada32 Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Tue, 18 Jun 2024 10:01:43 -0400 Subject: [PATCH 0175/1119] Doc: Updates to download.rst - Move old releases to "Past Releases" page - Migrate information about additional download sources from Trac - Organize section headings --- doc/source/download.rst | 331 ++++++----------------------------- doc/source/download_past.rst | 261 +++++++++++++++++++++++++++ 2 files changed, 317 insertions(+), 275 deletions(-) create mode 100644 doc/source/download_past.rst diff --git a/doc/source/download.rst b/doc/source/download.rst index ce15eeaeae8b..4affb7bd8c6c 100644 --- a/doc/source/download.rst +++ b/doc/source/download.rst @@ -10,8 +10,13 @@ Download :depth: 3 :backlinks: none +The GDAL project distributes GDAL as source code and :ref:`Containers` only. :ref:`Binaries` produced by others are available for a variety of platforms and package managers. + +Source Code +----------- + Current Release ------------------------------------------------------------------------------- +............... * **2024-05-10** `gdal-3.9.0.tar.gz`_ `3.9.0 Release Notes`_ (`3.9.0 md5`_) @@ -19,8 +24,8 @@ Current Release .. _`gdal-3.9.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.9.0/gdal-3.9.0.tar.gz .. _`3.9.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.9.0/gdal-3.9.0.tar.gz.md5 -Past Releases ------------------------------------------------------------------------------- +Additional Supported Releases +............................. * **2024-04-04** `gdal-3.8.5.tar.gz`_ `3.8.5 Release Notes`_ (`3.8.5 md5`_) @@ -28,263 +33,15 @@ Past Releases .. _`gdal-3.8.5.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.5/gdal-3.8.5.tar.gz .. _`3.8.5 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.5/gdal-3.8.5.tar.gz.md5 -* **2024-02-18** `gdal-3.8.4.tar.gz`_ `3.8.4 Release Notes`_ (`3.8.4 md5`_) - -.. _`3.8.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.4/NEWS.md -.. _`gdal-3.8.4.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.4/gdal-3.8.4.tar.gz -.. _`3.8.4 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.4/gdal-3.8.4.tar.gz.md5 - -* **2024-01-08** `gdal-3.8.3.tar.gz`_ `3.8.3 Release Notes`_ (`3.8.3 md5`_) - -.. _`3.8.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.3/NEWS.md -.. _`gdal-3.8.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.3/gdal-3.8.3.tar.gz -.. _`3.8.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.3/gdal-3.8.3.tar.gz.md5 - -* **2023-12-20** `gdal-3.8.2.tar.gz`_ `3.8.2 Release Notes`_ (`3.8.2 md5`_) - -.. _`3.8.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.2/NEWS.md -.. _`gdal-3.8.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.2/gdal-3.8.2.tar.gz -.. _`3.8.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.2/gdal-3.8.2.tar.gz.md5 - -* **2023-11-30** `gdal-3.8.1.tar.gz`_ `3.8.1 Release Notes`_ (`3.8.1 md5`_) - -.. _`3.8.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.1/NEWS.md -.. _`gdal-3.8.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.1/gdal-3.8.1.tar.gz -.. _`3.8.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.1/gdal-3.8.1.tar.gz.md5 - -* **2023-11-13** `gdal-3.8.0.tar.gz`_ `3.8.0 Release Notes`_ (`3.8.0 md5`_) - -.. _`3.8.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.0/NEWS.md -.. _`gdal-3.8.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.0/gdal-3.8.0.tar.gz -.. _`3.8.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.0/gdal-3.8.0.tar.gz.md5 - -* **2023-11-03** `gdal-3.7.3.tar.gz`_ `3.7.3 Release Notes`_ (`3.7.3 md5`_) - -.. _`3.7.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.7.3/NEWS.md -.. _`gdal-3.7.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.7.3/gdal-3.7.3.tar.gz -.. _`3.7.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.7.3/gdal-3.7.3.tar.gz.md5 - -* **2023-09-13** `gdal-3.7.2.tar.gz`_ `3.7.2 Release Notes`_ (`3.7.2 md5`_) - -.. _`3.7.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.7.2/NEWS.md -.. _`gdal-3.7.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.7.2/gdal-3.7.2.tar.gz -.. _`3.7.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.7.2/gdal-3.7.2.tar.gz.md5 - -* **2023-07-13** `gdal-3.7.1.tar.gz`_ `3.7.1 Release Notes`_ (`3.7.1 md5`_) - -.. _`3.7.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.7.1/NEWS.md -.. _`gdal-3.7.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.7.1/gdal-3.7.1.tar.gz -.. _`3.7.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.7.1/gdal-3.7.1.tar.gz.md5 - -* **2023-05-10** `gdal-3.7.0.tar.gz`_ `3.7.0 Release Notes`_ (`3.7.0 md5`_) - -.. _`3.7.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.7.0/NEWS.md -.. _`gdal-3.7.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.7.0/gdal-3.7.0.tar.gz -.. _`3.7.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.7.0/gdal-3.7.0.tar.gz.md5 - -* **2023-04-21** `gdal-3.6.4.tar.gz`_ `3.6.4 Release Notes`_ (`3.6.4 md5`_) - -.. _`3.6.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.6.4/NEWS.md -.. _`gdal-3.6.4.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.6.4/gdal-3.6.4.tar.gz -.. _`3.6.4 md5`: https://github.com/OSGeo/gdal/releases/download/v3.6.4/gdal-3.6.4.tar.gz.md5 - -* **2023-03-13** `gdal-3.6.3.tar.gz`_ `3.6.3 Release Notes`_ (`3.6.3 md5`_) - -.. _`3.6.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.6.3/NEWS.md -.. _`gdal-3.6.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.6.3/gdal-3.6.3.tar.gz -.. _`3.6.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.6.3/gdal-3.6.3.tar.gz.md5 - -* **2023-01-05** `gdal-3.6.2.tar.gz`_ `3.6.2 Release Notes`_ (`3.6.2 md5`_) - -.. _`3.6.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.6.2/NEWS.md -.. _`gdal-3.6.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.6.2/gdal-3.6.2.tar.gz -.. _`3.6.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.6.2/gdal-3.6.2.tar.gz.md5 - -* **2022-12-11** `gdal-3.6.1.tar.gz`_ `3.6.1 Release Notes`_ (`3.6.1 md5`_) - -.. _`3.6.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.6.1/NEWS.md -.. _`gdal-3.6.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.6.1/gdal-3.6.1.tar.gz -.. _`3.6.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.6.1/gdal-3.6.1.tar.gz.md5 - -* **2022-11-06** `3.6.0 Release Notes`_ *Warning*: this version has been officially retracted and superseded per 3.6.1 - -.. _`3.6.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.6.0/NEWS.md - -* **2022-10-21** `gdal-3.5.3.tar.gz`_ `3.5.3 Release Notes`_ (`3.5.3 md5`_) - -.. _`3.5.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.5.3/NEWS.md -.. _`gdal-3.5.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.5.3/gdal-3.5.3.tar.gz -.. _`3.5.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.5.3/gdal-3.5.3.tar.gz.md5 - -* **2022-09-12** `gdal-3.5.2.tar.gz`_ `3.5.2 Release Notes`_ (`3.5.2 md5`_) - -.. _`3.5.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.5.2/NEWS.md -.. _`gdal-3.5.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.5.2/gdal-3.5.2.tar.gz -.. _`3.5.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.5.2/gdal-3.5.2.tar.gz.md5 - -* **2022-07-06** `gdal-3.5.1.tar.gz`_ `3.5.1 Release Notes`_ (`3.5.1 md5`_) - -.. _`3.5.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.5.1/NEWS.md -.. _`gdal-3.5.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.5.1/gdal-3.5.1.tar.gz -.. _`3.5.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.5.1/gdal-3.5.1.tar.gz.md5 - -* **2022-05-13** `gdal-3.5.0.tar.gz`_ `3.5.0 Release Notes`_ (`3.5.0 md5`_) - -.. _`3.5.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.5.0/NEWS.md -.. _`gdal-3.5.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.5.0/gdal-3.5.0.tar.gz -.. _`3.5.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.5.0/gdal-3.5.0.tar.gz.md5 - -* **2022-04-22** `gdal-3.4.3.tar.gz`_ `3.4.3 Release Notes`_ (`3.4.3 md5`_) - -.. _`3.4.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.4.3/gdal/NEWS.md -.. _`gdal-3.4.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.4.3/gdal-3.4.3.tar.gz -.. _`3.4.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.4.3/gdal-3.4.3.tar.gz.md5 - -* **2022-03-08** `gdal-3.4.2.tar.gz`_ `3.4.2 Release Notes`_ (`3.4.2 md5`_) - -.. _`3.4.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.4.2/gdal/NEWS.md -.. _`gdal-3.4.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.4.2/gdal-3.4.2.tar.gz -.. _`3.4.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.4.2/gdal-3.4.2.tar.gz.md5 - -* **2021-12-27** `gdal-3.4.1.tar.gz`_ `3.4.1 Release Notes`_ (`3.4.1 md5`_) - -.. _`3.4.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.4.1/gdal/NEWS.md -.. _`gdal-3.4.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.4.1/gdal-3.4.1.tar.gz -.. _`3.4.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.4.1/gdal-3.4.1.tar.gz.md5 - -* **2021-11-08** `gdal-3.4.0.tar.gz`_ `3.4.0 Release Notes`_ (`3.4.0 md5`_) - -.. _`3.4.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.4.0/gdal/NEWS.md -.. _`gdal-3.4.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.4.0/gdal-3.4.0.tar.gz -.. _`3.4.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.4.0/gdal-3.4.0.tar.gz.md5 - -* **2021-10-29** `gdal-3.3.3.tar.gz`_ `3.3.3 Release Notes`_ (`3.3.3 md5`_) - -.. _`3.3.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.3.3/gdal/NEWS -.. _`gdal-3.3.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.3.3/gdal-3.3.3.tar.gz -.. _`3.3.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.3.3/gdal-3.3.3.tar.gz.md5 - -* **2021-09-01** `gdal-3.3.2.tar.gz`_ `3.3.2 Release Notes`_ (`3.3.2 md5`_) - -.. _`3.3.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.3.2/gdal/NEWS -.. _`gdal-3.3.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.3.2/gdal-3.3.2.tar.gz -.. _`3.3.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.3.2/gdal-3.3.2.tar.gz.md5 - -* **2021-07-05** `gdal-3.3.1.tar.gz`_ `3.3.1 Release Notes`_ (`3.3.1 md5`_) - -.. _`3.3.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.3.1/gdal/NEWS -.. _`gdal-3.3.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.3.1/gdal-3.3.1.tar.gz -.. _`3.3.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.3.1/gdal-3.3.1.tar.gz.md5 - -* **2021-05-03** `gdal-3.3.0.tar.gz`_ `3.3.0 Release Notes`_ (`3.3.0 md5`_) - -.. _`3.3.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.3.0/gdal/NEWS -.. _`gdal-3.3.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.3.0/gdal-3.3.0.tar.gz -.. _`3.3.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.3.0/gdal-3.3.0.tar.gz.md5 - -* **2021-05-04** `gdal-3.2.3.tar.gz`_ `3.2.3 Release Notes`_ (`3.2.3 md5`_) - -.. _`3.2.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.2.3/gdal/NEWS -.. _`gdal-3.2.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.2.3/gdal-3.2.3.tar.gz -.. _`3.2.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.2.3/gdal-3.2.3.tar.gz.md5 - -* **2021-03-10** `gdal-3.2.2.tar.gz`_ `3.2.2 Release Notes`_ (`3.2.2 md5`_) - -.. _`3.2.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.2.2/gdal/NEWS -.. _`gdal-3.2.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.2.2/gdal-3.2.2.tar.gz -.. _`3.2.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.2.2/gdal-3.2.2.tar.gz.md5 - -* **2020-12-28** `gdal-3.2.1.tar.gz`_ `3.2.1 Release Notes`_ (`3.2.1 md5`_) - -.. _`3.2.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.2.1/gdal/NEWS -.. _`gdal-3.2.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.2.1/gdal-3.2.1.tar.gz -.. _`3.2.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.2.1/gdal-3.2.1.tar.gz.md5 - -* **2020-10-26** `gdal-3.2.0.tar.gz`_ `3.2.0 Release Notes`_ (`3.2.0 md5`_) - -.. _`3.2.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.2.0/gdal/NEWS -.. _`gdal-3.2.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.2.0/gdal-3.2.0.tar.gz -.. _`3.2.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.2.0/gdal-3.2.0.tar.gz.md5 - -* **2020-10-23** `gdal-3.1.4.tar.gz`_ `3.1.4 Release Notes`_ (`3.1.4 md5`_) - -.. _`3.1.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.1.4/gdal/NEWS -.. _`gdal-3.1.4.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.1.4/gdal-3.1.4.tar.gz -.. _`3.1.4 md5`: https://github.com/OSGeo/gdal/releases/download/v3.1.4/gdal-3.1.4.tar.gz.md5 - -* **2020-09-01** `gdal-3.1.3.tar.gz`_ `3.1.3 Release Notes`_ (`3.1.3 md5`_) - -.. _`3.1.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.1.3/gdal/NEWS -.. _`gdal-3.1.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.1.3/gdal-3.1.3.tar.gz -.. _`3.1.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.1.3/gdal-3.1.3.tar.gz.md5 - -* **2020-07-07** `gdal-3.1.2.tar.gz`_ `3.1.2 Release Notes`_ (`3.1.2 md5`_) - -.. _`3.1.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.1.2/gdal/NEWS -.. _`gdal-3.1.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.1.2/gdal-3.1.2.tar.gz -.. _`3.1.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.1.2/gdal-3.1.2.tar.gz.md5 - -* **2020-06-22** `gdal-3.1.1.tar.gz`_ `3.1.1 Release Notes`_ (`3.1.1 md5`_) - -.. _`3.1.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.1.1/gdal/NEWS -.. _`gdal-3.1.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.1.1/gdal-3.1.1.tar.gz -.. _`3.1.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.1.1/gdal-3.1.1.tar.gz.md5 - - -* **2020-05-03** `gdal-3.1.0.tar.gz`_ `3.1.0 Release Notes`_ (`3.1.0 md5`_) - -.. _`3.1.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.1.0/gdal/NEWS -.. _`gdal-3.1.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.1.0/gdal-3.1.0.tar.gz -.. _`3.1.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.1.0/gdal-3.1.0.tar.gz.md5 - -* **2020-01-28** `gdal-3.0.4.tar.gz`_ `3.0.4 Release Notes`_ (`3.0.4 md5`_) - -.. _`3.0.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.0.4/gdal/NEWS -.. _`gdal-3.0.4.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.0.4/gdal-3.0.4.tar.gz -.. _`3.0.4 md5`: https://github.com/OSGeo/gdal/releases/download/v3.0.4/gdal-3.0.4.tar.gz.md5 - -* **2020-01-08** `gdal-2.4.4.tar.gz`_ `2.4.4 Release Notes`_ (`2.4.4 md5`_) - -.. _`2.4.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.4.4/gdal/NEWS -.. _`gdal-2.4.4.tar.gz`: https://download.osgeo.org/gdal/2.4.4/gdal-2.4.4.tar.gz -.. _`2.4.4 md5`: https://download.osgeo.org/gdal/2.4.4/gdal-2.4.4.tar.gz.md5 - -* **2020-01-08** `gdal-3.0.3.tar.gz`_ `3.0.3 Release Notes`_ (`3.0.3 md5`_) - -.. _`3.0.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.0.3/gdal/NEWS -.. _`gdal-3.0.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.0.3/gdal-3.0.3.tar.gz -.. _`3.0.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.0.3/gdal-3.0.3.tar.gz.md5 - -* **2019-10-28** `gdal-3.0.2.tar.gz`_ `3.0.2 Release Notes`_ (`3.0.2 md5`_) - -.. _`3.0.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.0.2/gdal/NEWS -.. _`gdal-3.0.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.0.2/gdal-3.0.2.tar.gz -.. _`3.0.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.0.2/gdal-3.0.2.tar.gz.md5 - -* **2019-10-28** `gdal-2.4.3.tar.gz`_ `2.4.3 Release Notes`_ (`2.4.3 md5`_) - -.. _`2.4.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.4.3/gdal/NEWS -.. _`gdal-2.4.3.tar.gz`: https://download.osgeo.org/gdal/2.4.3/gdal-2.4.3.tar.gz -.. _`2.4.3 md5`: https://download.osgeo.org/gdal/2.4.3/gdal-2.4.3.tar.gz.md5 - - -* **2019-06-28** `gdal-3.0.1.tar.gz`_ `3.0.1 Release Notes`_ (`3.0.1 md5`_) - -.. _`3.0.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.0.1/gdal/NEWS -.. _`gdal-3.0.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.0.1/gdal-3.0.1.tar.gz -.. _`3.0.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.0.1/gdal-3.0.1.tar.gz.md5 - - -* **2019-06-28** `gdal-2.4.2.tar.gz`_ `2.4.2 Release Notes`_ (`2.4.2 md5`_) +Past Releases +............. -.. _`2.4.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.4.2/gdal/NEWS -.. _`gdal-2.4.2.tar.gz`: https://download.osgeo.org/gdal/2.4.2/gdal-2.4.2.tar.gz -.. _`2.4.2 md5`: https://download.osgeo.org/gdal/2.4.2/gdal-2.4.2.tar.gz.md5 +Links to :ref:`download_past` are also available. .. _source: Development Source ------------------------------------------------------------------------------- +.................. The main repository for GDAL is located on GitHub at https://github.com/OSGeo/GDAL. @@ -299,6 +56,9 @@ command Additional information is available about :ref:`build_requirements` and :ref:`building_from_source`. + +.. _binaries: + Binaries ------------------------------------------------------------------------------ @@ -313,25 +73,43 @@ Windows ................................................................................ Windows builds are available via `Conda Forge`_ (64-bit only). See the -:ref:`conda` section for more detailed information. +:ref:`conda` section for more detailed information. GDAL is also distributed +by `GISInternals`_ and `OSGeo4W`_ and through the `NuGet`_ and :ref:`vcpkg` package managers. .. _`Conda Forge`: https://anaconda.org/conda-forge/gdal +.. _`GISInternals`: https://www.gisinternals.com/index.html +.. _`OSGeo4W`: https://trac.osgeo.org/osgeo4w/ +.. _`NuGet`: https://www.nuget.org/packages?q=GDAL -Debian +Linux ................................................................................ -Debian packages are now available on `Debian`_. +Packages are available for `Debian`_, `Alpine_`, `Fedora_`, and other distributions. .. _`Debian`: https://tracker.debian.org/pkg/gdal +.. _`Alpine`: https://pkgs.alpinelinux.org/package/edge/community/x86/gdal +.. _`Fedora`: https://packages.fedoraproject.org/pkgs/gdal/ + + +Mac OS +...... + +GDAL packages are available on `Homebrew`_. + +.. _`Homebrew`: https://formulae.brew.sh/formula/gdal + + +Cross-Platform Package Managers +............................... .. _conda: Conda -................................................................................ +^^^^^ `Conda `__ can be used on multiple platforms (Windows, macOS, and Linux) to install software packages and manage environments. Conda packages for GDAL are -available at https://anaconda.org/conda-forge/gdal. +available through `conda-forge `__. .. only:: html @@ -381,12 +159,13 @@ Then install GDAL from the ``gdal-master`` channel: mamba install -c gdal-master libgdal-arrow-parquet # if you need the Arrow and Parquet drivers -Vcpkg -................................................................................ +.. _vcpkg: + +vcpkg +^^^^^ -The gdal port in vcpkg is kept up to date by Microsoft team members and community contributors. -The url of vcpkg is: https://github.com/Microsoft/vcpkg . -You can download and install gdal using the vcpkg dependency manager: +The GDAL port in the `vcpkg `__ dependency manager is kept up to date by Microsoft team members and community contributors. +You can download and install GDAL using the vcpkg as follows: :: @@ -399,7 +178,7 @@ You can download and install gdal using the vcpkg dependency manager: If the version is out of date, please `create an issue or pull request `__ on the vcpkg repository. Spack -................................................................................ +^^^^^ Spack is a package management tool designed to support multiple versions and configurations of software on a wide variety of platforms and environments. @@ -424,19 +203,21 @@ For a build with netcdf driver enabled: ./spack install gdal +netcdf -Linux Docker images -................................................................................ +.. _containers: + +Containers +---------- -Images with nightly builds of GDAL master and tagged releases are available at -`GitHub Container registry `_ +Docker images with nightly builds of GDAL master and tagged releases are available at +`GitHub Container registry `_. Information on the content of the different configurations can be found at -`https://github.com/OSGeo/gdal/tree/master/docker `_ +`https://github.com/OSGeo/gdal/tree/master/docker `_. -Documentation only ------------------------------------------------------------------------------- +Documentation +------------- Besides being included when downloading the software, the documentation is -also available independently as a `PDF file `_, -and `a .ZIP of individual HTML pages `_ for offline browsing. (The .ZIP also includes that .PDF.) The documentation reflects the latest state of the development branch of the software. +also available independently as a `PDF file `_, +and `a ZIP of individual HTML pages `_ for offline browsing. (The ZIP also includes the PDF.) The documentation reflects the latest state of the development branch of the software. diff --git a/doc/source/download_past.rst b/doc/source/download_past.rst new file mode 100644 index 000000000000..c4837ab64a0e --- /dev/null +++ b/doc/source/download_past.rst @@ -0,0 +1,261 @@ +:orphan: + +.. _download_past: + +Past Releases +============= + +* **2024-02-18** `gdal-3.8.4.tar.gz`_ `3.8.4 Release Notes`_ (`3.8.4 md5`_) + +.. _`3.8.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.4/NEWS.md +.. _`gdal-3.8.4.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.4/gdal-3.8.4.tar.gz +.. _`3.8.4 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.4/gdal-3.8.4.tar.gz.md5 + +* **2024-01-08** `gdal-3.8.3.tar.gz`_ `3.8.3 Release Notes`_ (`3.8.3 md5`_) + +.. _`3.8.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.3/NEWS.md +.. _`gdal-3.8.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.3/gdal-3.8.3.tar.gz +.. _`3.8.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.3/gdal-3.8.3.tar.gz.md5 + +* **2023-12-20** `gdal-3.8.2.tar.gz`_ `3.8.2 Release Notes`_ (`3.8.2 md5`_) + +.. _`3.8.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.2/NEWS.md +.. _`gdal-3.8.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.2/gdal-3.8.2.tar.gz +.. _`3.8.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.2/gdal-3.8.2.tar.gz.md5 + +* **2023-11-30** `gdal-3.8.1.tar.gz`_ `3.8.1 Release Notes`_ (`3.8.1 md5`_) + +.. _`3.8.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.1/NEWS.md +.. _`gdal-3.8.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.1/gdal-3.8.1.tar.gz +.. _`3.8.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.1/gdal-3.8.1.tar.gz.md5 + +* **2023-11-13** `gdal-3.8.0.tar.gz`_ `3.8.0 Release Notes`_ (`3.8.0 md5`_) + +.. _`3.8.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.0/NEWS.md +.. _`gdal-3.8.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.0/gdal-3.8.0.tar.gz +.. _`3.8.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.0/gdal-3.8.0.tar.gz.md5 + +* **2023-11-03** `gdal-3.7.3.tar.gz`_ `3.7.3 Release Notes`_ (`3.7.3 md5`_) + +.. _`3.7.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.7.3/NEWS.md +.. _`gdal-3.7.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.7.3/gdal-3.7.3.tar.gz +.. _`3.7.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.7.3/gdal-3.7.3.tar.gz.md5 + +* **2023-09-13** `gdal-3.7.2.tar.gz`_ `3.7.2 Release Notes`_ (`3.7.2 md5`_) + +.. _`3.7.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.7.2/NEWS.md +.. _`gdal-3.7.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.7.2/gdal-3.7.2.tar.gz +.. _`3.7.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.7.2/gdal-3.7.2.tar.gz.md5 + +* **2023-07-13** `gdal-3.7.1.tar.gz`_ `3.7.1 Release Notes`_ (`3.7.1 md5`_) + +.. _`3.7.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.7.1/NEWS.md +.. _`gdal-3.7.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.7.1/gdal-3.7.1.tar.gz +.. _`3.7.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.7.1/gdal-3.7.1.tar.gz.md5 + +* **2023-05-10** `gdal-3.7.0.tar.gz`_ `3.7.0 Release Notes`_ (`3.7.0 md5`_) + +.. _`3.7.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.7.0/NEWS.md +.. _`gdal-3.7.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.7.0/gdal-3.7.0.tar.gz +.. _`3.7.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.7.0/gdal-3.7.0.tar.gz.md5 + +* **2023-04-21** `gdal-3.6.4.tar.gz`_ `3.6.4 Release Notes`_ (`3.6.4 md5`_) + +.. _`3.6.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.6.4/NEWS.md +.. _`gdal-3.6.4.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.6.4/gdal-3.6.4.tar.gz +.. _`3.6.4 md5`: https://github.com/OSGeo/gdal/releases/download/v3.6.4/gdal-3.6.4.tar.gz.md5 + +* **2023-03-13** `gdal-3.6.3.tar.gz`_ `3.6.3 Release Notes`_ (`3.6.3 md5`_) + +.. _`3.6.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.6.3/NEWS.md +.. _`gdal-3.6.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.6.3/gdal-3.6.3.tar.gz +.. _`3.6.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.6.3/gdal-3.6.3.tar.gz.md5 + +* **2023-01-05** `gdal-3.6.2.tar.gz`_ `3.6.2 Release Notes`_ (`3.6.2 md5`_) + +.. _`3.6.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.6.2/NEWS.md +.. _`gdal-3.6.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.6.2/gdal-3.6.2.tar.gz +.. _`3.6.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.6.2/gdal-3.6.2.tar.gz.md5 + +* **2022-12-11** `gdal-3.6.1.tar.gz`_ `3.6.1 Release Notes`_ (`3.6.1 md5`_) + +.. _`3.6.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.6.1/NEWS.md +.. _`gdal-3.6.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.6.1/gdal-3.6.1.tar.gz +.. _`3.6.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.6.1/gdal-3.6.1.tar.gz.md5 + +* **2022-11-06** `3.6.0 Release Notes`_ *Warning*: this version has been officially retracted and superseded per 3.6.1 + +.. _`3.6.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.6.0/NEWS.md + +* **2022-10-21** `gdal-3.5.3.tar.gz`_ `3.5.3 Release Notes`_ (`3.5.3 md5`_) + +.. _`3.5.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.5.3/NEWS.md +.. _`gdal-3.5.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.5.3/gdal-3.5.3.tar.gz +.. _`3.5.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.5.3/gdal-3.5.3.tar.gz.md5 + +* **2022-09-12** `gdal-3.5.2.tar.gz`_ `3.5.2 Release Notes`_ (`3.5.2 md5`_) + +.. _`3.5.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.5.2/NEWS.md +.. _`gdal-3.5.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.5.2/gdal-3.5.2.tar.gz +.. _`3.5.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.5.2/gdal-3.5.2.tar.gz.md5 + +* **2022-07-06** `gdal-3.5.1.tar.gz`_ `3.5.1 Release Notes`_ (`3.5.1 md5`_) + +.. _`3.5.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.5.1/NEWS.md +.. _`gdal-3.5.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.5.1/gdal-3.5.1.tar.gz +.. _`3.5.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.5.1/gdal-3.5.1.tar.gz.md5 + +* **2022-05-13** `gdal-3.5.0.tar.gz`_ `3.5.0 Release Notes`_ (`3.5.0 md5`_) + +.. _`3.5.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.5.0/NEWS.md +.. _`gdal-3.5.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.5.0/gdal-3.5.0.tar.gz +.. _`3.5.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.5.0/gdal-3.5.0.tar.gz.md5 + +* **2022-04-22** `gdal-3.4.3.tar.gz`_ `3.4.3 Release Notes`_ (`3.4.3 md5`_) + +.. _`3.4.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.4.3/gdal/NEWS.md +.. _`gdal-3.4.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.4.3/gdal-3.4.3.tar.gz +.. _`3.4.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.4.3/gdal-3.4.3.tar.gz.md5 + +* **2022-03-08** `gdal-3.4.2.tar.gz`_ `3.4.2 Release Notes`_ (`3.4.2 md5`_) + +.. _`3.4.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.4.2/gdal/NEWS.md +.. _`gdal-3.4.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.4.2/gdal-3.4.2.tar.gz +.. _`3.4.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.4.2/gdal-3.4.2.tar.gz.md5 + +* **2021-12-27** `gdal-3.4.1.tar.gz`_ `3.4.1 Release Notes`_ (`3.4.1 md5`_) + +.. _`3.4.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.4.1/gdal/NEWS.md +.. _`gdal-3.4.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.4.1/gdal-3.4.1.tar.gz +.. _`3.4.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.4.1/gdal-3.4.1.tar.gz.md5 + +* **2021-11-08** `gdal-3.4.0.tar.gz`_ `3.4.0 Release Notes`_ (`3.4.0 md5`_) + +.. _`3.4.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.4.0/gdal/NEWS.md +.. _`gdal-3.4.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.4.0/gdal-3.4.0.tar.gz +.. _`3.4.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.4.0/gdal-3.4.0.tar.gz.md5 + +* **2021-10-29** `gdal-3.3.3.tar.gz`_ `3.3.3 Release Notes`_ (`3.3.3 md5`_) + +.. _`3.3.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.3.3/gdal/NEWS +.. _`gdal-3.3.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.3.3/gdal-3.3.3.tar.gz +.. _`3.3.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.3.3/gdal-3.3.3.tar.gz.md5 + +* **2021-09-01** `gdal-3.3.2.tar.gz`_ `3.3.2 Release Notes`_ (`3.3.2 md5`_) + +.. _`3.3.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.3.2/gdal/NEWS +.. _`gdal-3.3.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.3.2/gdal-3.3.2.tar.gz +.. _`3.3.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.3.2/gdal-3.3.2.tar.gz.md5 + +* **2021-07-05** `gdal-3.3.1.tar.gz`_ `3.3.1 Release Notes`_ (`3.3.1 md5`_) + +.. _`3.3.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.3.1/gdal/NEWS +.. _`gdal-3.3.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.3.1/gdal-3.3.1.tar.gz +.. _`3.3.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.3.1/gdal-3.3.1.tar.gz.md5 + +* **2021-05-03** `gdal-3.3.0.tar.gz`_ `3.3.0 Release Notes`_ (`3.3.0 md5`_) + +.. _`3.3.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.3.0/gdal/NEWS +.. _`gdal-3.3.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.3.0/gdal-3.3.0.tar.gz +.. _`3.3.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.3.0/gdal-3.3.0.tar.gz.md5 + +* **2021-05-04** `gdal-3.2.3.tar.gz`_ `3.2.3 Release Notes`_ (`3.2.3 md5`_) + +.. _`3.2.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.2.3/gdal/NEWS +.. _`gdal-3.2.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.2.3/gdal-3.2.3.tar.gz +.. _`3.2.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.2.3/gdal-3.2.3.tar.gz.md5 + +* **2021-03-10** `gdal-3.2.2.tar.gz`_ `3.2.2 Release Notes`_ (`3.2.2 md5`_) + +.. _`3.2.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.2.2/gdal/NEWS +.. _`gdal-3.2.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.2.2/gdal-3.2.2.tar.gz +.. _`3.2.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.2.2/gdal-3.2.2.tar.gz.md5 + +* **2020-12-28** `gdal-3.2.1.tar.gz`_ `3.2.1 Release Notes`_ (`3.2.1 md5`_) + +.. _`3.2.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.2.1/gdal/NEWS +.. _`gdal-3.2.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.2.1/gdal-3.2.1.tar.gz +.. _`3.2.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.2.1/gdal-3.2.1.tar.gz.md5 + +* **2020-10-26** `gdal-3.2.0.tar.gz`_ `3.2.0 Release Notes`_ (`3.2.0 md5`_) + +.. _`3.2.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.2.0/gdal/NEWS +.. _`gdal-3.2.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.2.0/gdal-3.2.0.tar.gz +.. _`3.2.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.2.0/gdal-3.2.0.tar.gz.md5 + +* **2020-10-23** `gdal-3.1.4.tar.gz`_ `3.1.4 Release Notes`_ (`3.1.4 md5`_) + +.. _`3.1.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.1.4/gdal/NEWS +.. _`gdal-3.1.4.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.1.4/gdal-3.1.4.tar.gz +.. _`3.1.4 md5`: https://github.com/OSGeo/gdal/releases/download/v3.1.4/gdal-3.1.4.tar.gz.md5 + +* **2020-09-01** `gdal-3.1.3.tar.gz`_ `3.1.3 Release Notes`_ (`3.1.3 md5`_) + +.. _`3.1.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.1.3/gdal/NEWS +.. _`gdal-3.1.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.1.3/gdal-3.1.3.tar.gz +.. _`3.1.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.1.3/gdal-3.1.3.tar.gz.md5 + +* **2020-07-07** `gdal-3.1.2.tar.gz`_ `3.1.2 Release Notes`_ (`3.1.2 md5`_) + +.. _`3.1.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.1.2/gdal/NEWS +.. _`gdal-3.1.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.1.2/gdal-3.1.2.tar.gz +.. _`3.1.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.1.2/gdal-3.1.2.tar.gz.md5 + +* **2020-06-22** `gdal-3.1.1.tar.gz`_ `3.1.1 Release Notes`_ (`3.1.1 md5`_) + +.. _`3.1.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.1.1/gdal/NEWS +.. _`gdal-3.1.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.1.1/gdal-3.1.1.tar.gz +.. _`3.1.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.1.1/gdal-3.1.1.tar.gz.md5 + + +* **2020-05-03** `gdal-3.1.0.tar.gz`_ `3.1.0 Release Notes`_ (`3.1.0 md5`_) + +.. _`3.1.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.1.0/gdal/NEWS +.. _`gdal-3.1.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.1.0/gdal-3.1.0.tar.gz +.. _`3.1.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.1.0/gdal-3.1.0.tar.gz.md5 + +* **2020-01-28** `gdal-3.0.4.tar.gz`_ `3.0.4 Release Notes`_ (`3.0.4 md5`_) + +.. _`3.0.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.0.4/gdal/NEWS +.. _`gdal-3.0.4.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.0.4/gdal-3.0.4.tar.gz +.. _`3.0.4 md5`: https://github.com/OSGeo/gdal/releases/download/v3.0.4/gdal-3.0.4.tar.gz.md5 + +* **2020-01-08** `gdal-2.4.4.tar.gz`_ `2.4.4 Release Notes`_ (`2.4.4 md5`_) + +.. _`2.4.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.4.4/gdal/NEWS +.. _`gdal-2.4.4.tar.gz`: https://download.osgeo.org/gdal/2.4.4/gdal-2.4.4.tar.gz +.. _`2.4.4 md5`: https://download.osgeo.org/gdal/2.4.4/gdal-2.4.4.tar.gz.md5 + +* **2020-01-08** `gdal-3.0.3.tar.gz`_ `3.0.3 Release Notes`_ (`3.0.3 md5`_) + +.. _`3.0.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.0.3/gdal/NEWS +.. _`gdal-3.0.3.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.0.3/gdal-3.0.3.tar.gz +.. _`3.0.3 md5`: https://github.com/OSGeo/gdal/releases/download/v3.0.3/gdal-3.0.3.tar.gz.md5 + +* **2019-10-28** `gdal-3.0.2.tar.gz`_ `3.0.2 Release Notes`_ (`3.0.2 md5`_) + +.. _`3.0.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.0.2/gdal/NEWS +.. _`gdal-3.0.2.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.0.2/gdal-3.0.2.tar.gz +.. _`3.0.2 md5`: https://github.com/OSGeo/gdal/releases/download/v3.0.2/gdal-3.0.2.tar.gz.md5 + +* **2019-10-28** `gdal-2.4.3.tar.gz`_ `2.4.3 Release Notes`_ (`2.4.3 md5`_) + +.. _`2.4.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.4.3/gdal/NEWS +.. _`gdal-2.4.3.tar.gz`: https://download.osgeo.org/gdal/2.4.3/gdal-2.4.3.tar.gz +.. _`2.4.3 md5`: https://download.osgeo.org/gdal/2.4.3/gdal-2.4.3.tar.gz.md5 + + +* **2019-06-28** `gdal-3.0.1.tar.gz`_ `3.0.1 Release Notes`_ (`3.0.1 md5`_) + +.. _`3.0.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.0.1/gdal/NEWS +.. _`gdal-3.0.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.0.1/gdal-3.0.1.tar.gz +.. _`3.0.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.0.1/gdal-3.0.1.tar.gz.md5 + + +* **2019-06-28** `gdal-2.4.2.tar.gz`_ `2.4.2 Release Notes`_ (`2.4.2 md5`_) + +.. _`2.4.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.4.2/gdal/NEWS +.. _`gdal-2.4.2.tar.gz`: https://download.osgeo.org/gdal/2.4.2/gdal-2.4.2.tar.gz +.. _`2.4.2 md5`: https://download.osgeo.org/gdal/2.4.2/gdal-2.4.2.tar.gz.md5 + + From 42adada1f1ff55a5109de17565d3c107de48c531 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Jun 2024 14:47:29 +0200 Subject: [PATCH 0176/1119] Rasterband methods (histogram, statistics): make them compatible of a dataset with more 2 billion blocks Related to #10229 --- gcore/gdal_misc.cpp | 18 +++++---- gcore/gdalrasterband.cpp | 79 +++++++++++++++++++++++----------------- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/gcore/gdal_misc.cpp b/gcore/gdal_misc.cpp index bedc056a4e8c..e07e97c024a1 100644 --- a/gcore/gdal_misc.cpp +++ b/gcore/gdal_misc.cpp @@ -1180,8 +1180,10 @@ int CPL_STDCALL GDALGetRandomRasterSample(GDALRasterBandH hBand, int nSamples, const int nBlocksPerColumn = (poBand->GetYSize() + nBlockYSize - 1) / nBlockYSize; - const int nBlockPixels = nBlockXSize * nBlockYSize; - const int nBlockCount = nBlocksPerRow * nBlocksPerColumn; + const GIntBig nBlockPixels = + static_cast(nBlockXSize) * nBlockYSize; + const GIntBig nBlockCount = + static_cast(nBlocksPerRow) * nBlocksPerColumn; if (nBlocksPerRow == 0 || nBlocksPerColumn == 0 || nBlockPixels == 0 || nBlockCount == 0) @@ -1206,18 +1208,18 @@ int CPL_STDCALL GDALGetRandomRasterSample(GDALRasterBandH hBand, int nSamples, int nBlockSampleRate = 1; if ((nSamples / ((nBlockCount - 1) / nSampleRate + 1)) != 0) - nBlockSampleRate = - std::max(1, nBlockPixels / - (nSamples / ((nBlockCount - 1) / nSampleRate + 1))); + nBlockSampleRate = static_cast(std::max( + 1, + nBlockPixels / (nSamples / ((nBlockCount - 1) / nSampleRate + 1)))); int nActualSamples = 0; - for (int iSampleBlock = 0; iSampleBlock < nBlockCount; + for (GIntBig iSampleBlock = 0; iSampleBlock < nBlockCount; iSampleBlock += nSampleRate) { - const int iYBlock = iSampleBlock / nBlocksPerRow; - const int iXBlock = iSampleBlock - nBlocksPerRow * iYBlock; + const int iYBlock = static_cast(iSampleBlock / nBlocksPerRow); + const int iXBlock = static_cast(iSampleBlock % nBlocksPerRow); GDALRasterBlock *const poBlock = poBand->GetLockedBlockRef(iXBlock, iYBlock); diff --git a/gcore/gdalrasterband.cpp b/gcore/gdalrasterband.cpp index d971f38f8dac..1495e75720e4 100644 --- a/gcore/gdalrasterband.cpp +++ b/gcore/gdalrasterband.cpp @@ -98,9 +98,10 @@ GDALRasterBand::~GDALRasterBand() static_cast(nBlocksPerRow) * nBlocksPerColumn && nBand == 1 && poDS != nullptr) { - CPLDebug("GDAL", "%d block reads on %d block band 1 of %s.", - nBlockReads, nBlocksPerRow * nBlocksPerColumn, - poDS->GetDescription()); + CPLDebug( + "GDAL", "%d block reads on " CPL_FRMT_GIB " block band 1 of %s.", + nBlockReads, static_cast(nBlocksPerRow) * nBlocksPerColumn, + poDS->GetDescription()); } InvalidateMaskBand(); @@ -3752,12 +3753,13 @@ CPLErr GDALRasterBand::GetHistogram(double dfMin, double dfMax, int nBuckets, /* Read the blocks, and add to histogram. */ /* -------------------------------------------------------------------- */ - for (int iSampleBlock = 0; - iSampleBlock < nBlocksPerRow * nBlocksPerColumn; + for (GIntBig iSampleBlock = 0; + iSampleBlock < + static_cast(nBlocksPerRow) * nBlocksPerColumn; iSampleBlock += nSampleRate) { if (!pfnProgress( - iSampleBlock / + static_cast(iSampleBlock) / (static_cast(nBlocksPerRow) * nBlocksPerColumn), "Compute Histogram", pProgressData)) { @@ -3765,8 +3767,8 @@ CPLErr GDALRasterBand::GetHistogram(double dfMin, double dfMax, int nBuckets, return CE_Failure; } - const int iYBlock = iSampleBlock / nBlocksPerRow; - const int iXBlock = iSampleBlock - nBlocksPerRow * iYBlock; + const int iYBlock = static_cast(iSampleBlock / nBlocksPerRow); + const int iXBlock = static_cast(iSampleBlock % nBlocksPerRow); GDALRasterBlock *poBlock = GetLockedBlockRef(iXBlock, iYBlock); if (poBlock == nullptr) @@ -5951,12 +5953,15 @@ CPLErr GDALRasterBand::ComputeStatistics(int bApproxOK, double *pdfMin, ? static_cast(dfNoDataValue + 1e-10) : nMaxValueType + 1; - for (int iSampleBlock = 0; - iSampleBlock < nBlocksPerRow * nBlocksPerColumn; + for (GIntBig iSampleBlock = 0; + iSampleBlock < + static_cast(nBlocksPerRow) * nBlocksPerColumn; iSampleBlock += nSampleRate) { - const int iYBlock = iSampleBlock / nBlocksPerRow; - const int iXBlock = iSampleBlock - nBlocksPerRow * iYBlock; + const int iYBlock = + static_cast(iSampleBlock / nBlocksPerRow); + const int iXBlock = + static_cast(iSampleBlock % nBlocksPerRow); GDALRasterBlock *const poBlock = GetLockedBlockRef(iXBlock, iYBlock); @@ -5989,9 +5994,9 @@ CPLErr GDALRasterBand::ComputeStatistics(int bApproxOK, double *pdfMin, poBlock->DropLock(); - if (!pfnProgress(iSampleBlock / - static_cast(nBlocksPerRow * - nBlocksPerColumn), + if (!pfnProgress(static_cast(iSampleBlock) / + static_cast(nBlocksPerRow) * + nBlocksPerColumn, "Compute Statistics", pProgressData)) { ReportError(CE_Failure, CPLE_UserInterrupt, @@ -6077,12 +6082,13 @@ CPLErr GDALRasterBand::ComputeStatistics(int bApproxOK, double *pdfMin, } } - for (int iSampleBlock = 0; - iSampleBlock < nBlocksPerRow * nBlocksPerColumn; + for (GIntBig iSampleBlock = 0; + iSampleBlock < + static_cast(nBlocksPerRow) * nBlocksPerColumn; iSampleBlock += nSampleRate) { - const int iYBlock = iSampleBlock / nBlocksPerRow; - const int iXBlock = iSampleBlock - nBlocksPerRow * iYBlock; + const int iYBlock = static_cast(iSampleBlock / nBlocksPerRow); + const int iXBlock = static_cast(iSampleBlock % nBlocksPerRow); GDALRasterBlock *const poBlock = GetLockedBlockRef(iXBlock, iYBlock); @@ -6141,10 +6147,10 @@ CPLErr GDALRasterBand::ComputeStatistics(int bApproxOK, double *pdfMin, poBlock->DropLock(); - if (!pfnProgress( - iSampleBlock / - static_cast(nBlocksPerRow * nBlocksPerColumn), - "Compute Statistics", pProgressData)) + if (!pfnProgress(static_cast(iSampleBlock) / + static_cast(nBlocksPerRow) * + nBlocksPerColumn, + "Compute Statistics", pProgressData)) { ReportError(CE_Failure, CPLE_UserInterrupt, "User terminated"); CPLFree(pabyMaskData); @@ -6480,9 +6486,10 @@ static void ComputeMinMaxGeneric(const void *pData, GDALDataType eDataType, static bool ComputeMinMaxGenericIterBlocks( GDALRasterBand *poBand, GDALDataType eDataType, bool bSignedByte, - int nTotalBlocks, int nSampleRate, int nBlocksPerRow, bool bGotNoDataValue, - double dfNoDataValue, bool bGotFloatNoDataValue, float fNoDataValue, - GDALRasterBand *poMaskBand, double &dfMin, double &dfMax) + GIntBig nTotalBlocks, int nSampleRate, int nBlocksPerRow, + bool bGotNoDataValue, double dfNoDataValue, bool bGotFloatNoDataValue, + float fNoDataValue, GDALRasterBand *poMaskBand, double &dfMin, + double &dfMax) { GByte *pabyMaskData = nullptr; @@ -6499,11 +6506,11 @@ static bool ComputeMinMaxGenericIterBlocks( } } - for (int iSampleBlock = 0; iSampleBlock < nTotalBlocks; + for (GIntBig iSampleBlock = 0; iSampleBlock < nTotalBlocks; iSampleBlock += nSampleRate) { - const int iYBlock = iSampleBlock / nBlocksPerRow; - const int iXBlock = iSampleBlock - nBlocksPerRow * iYBlock; + const int iYBlock = static_cast(iSampleBlock / nBlocksPerRow); + const int iXBlock = static_cast(iSampleBlock % nBlocksPerRow); GDALRasterBlock *poBlock = poBand->GetLockedBlockRef(iXBlock, iYBlock); if (poBlock == nullptr) @@ -6811,12 +6818,15 @@ CPLErr GDALRasterBand::ComputeRasterMinMax(int bApproxOK, double *adfMinMax) if (bUseOptimizedPath) { - for (int iSampleBlock = 0; - iSampleBlock < nBlocksPerRow * nBlocksPerColumn; + for (GIntBig iSampleBlock = 0; + iSampleBlock < + static_cast(nBlocksPerRow) * nBlocksPerColumn; iSampleBlock += nSampleRate) { - const int iYBlock = iSampleBlock / nBlocksPerRow; - const int iXBlock = iSampleBlock - nBlocksPerRow * iYBlock; + const int iYBlock = + static_cast(iSampleBlock / nBlocksPerRow); + const int iXBlock = + static_cast(iSampleBlock % nBlocksPerRow); GDALRasterBlock *poBlock = GetLockedBlockRef(iXBlock, iYBlock); if (poBlock == nullptr) @@ -6838,7 +6848,8 @@ CPLErr GDALRasterBand::ComputeRasterMinMax(int bApproxOK, double *adfMinMax) } else { - const int nTotalBlocks = nBlocksPerRow * nBlocksPerColumn; + const GIntBig nTotalBlocks = + static_cast(nBlocksPerRow) * nBlocksPerColumn; if (!ComputeMinMaxGenericIterBlocks( this, eDataType, bSignedByte, nTotalBlocks, nSampleRate, nBlocksPerRow, CPL_TO_BOOL(bGotNoDataValue), dfNoDataValue, From 1d6ee525ae2d9996ffeebb1985915be2d70b5f57 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 15 Jun 2024 02:00:26 +0200 Subject: [PATCH 0177/1119] Add OGRGeometry::hasEmptyParts()/removeEmptyParts(), and use it in exportToGEOS() in the Centroid() context Fixes #10217 --- autotest/cpp/test_ogr.cpp | 128 ++++++++++++++++++++++++++++++++++ autotest/ogr/ogr_geos.py | 13 ++++ ogr/ogr_geometry.h | 23 +++++- ogr/ogrcompoundcurve.cpp | 18 +++++ ogr/ogrcurvecollection.cpp | 42 +++++++++++ ogr/ogrcurvepolygon.cpp | 22 ++++++ ogr/ogrgeometry.cpp | 49 ++++++++++++- ogr/ogrgeometrycollection.cpp | 28 ++++++++ ogr/ogrpolyhedralsurface.cpp | 18 +++++ 9 files changed, 337 insertions(+), 4 deletions(-) diff --git a/autotest/cpp/test_ogr.cpp b/autotest/cpp/test_ogr.cpp index a1d6e77b4f44..90806d2e0da7 100644 --- a/autotest/cpp/test_ogr.cpp +++ b/autotest/cpp/test_ogr.cpp @@ -4047,4 +4047,132 @@ TEST_F(test_ogr, OGRGeometry_IsRectangle) } } +// Test OGRGeometry::removeEmptyParts() +TEST_F(test_ogr, OGRGeometry_removeEmptyParts) +{ + { + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkt("POINT EMPTY", nullptr, &poGeom); + ASSERT_NE(poGeom, nullptr); + EXPECT_FALSE(poGeom->hasEmptyParts()); + poGeom->removeEmptyParts(); + EXPECT_TRUE(poGeom->IsEmpty()); + delete poGeom; + } + { + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkt("POLYGON ((0 0,0 1,1 0,0 0))", + nullptr, &poGeom); + ASSERT_NE(poGeom, nullptr); + EXPECT_FALSE(poGeom->hasEmptyParts()); + poGeom->removeEmptyParts(); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing(), nullptr); + delete poGeom; + } + { + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkt("POLYGON ((0 0,0 1,1 0,0 0))", + nullptr, &poGeom); + ASSERT_NE(poGeom, nullptr); + poGeom->toPolygon()->addRingDirectly(new OGRLinearRing()); + EXPECT_EQ(poGeom->toPolygon()->getNumInteriorRings(), 1); + EXPECT_TRUE(poGeom->hasEmptyParts()); + poGeom->removeEmptyParts(); + EXPECT_NE(poGeom->toPolygon()->getExteriorRing(), nullptr); + EXPECT_EQ(poGeom->toPolygon()->getNumInteriorRings(), 0); + EXPECT_FALSE(poGeom->hasEmptyParts()); + delete poGeom; + } + { + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkt("COMPOUNDCURVE ((0 0,1 1))", nullptr, + &poGeom); + ASSERT_NE(poGeom, nullptr); + EXPECT_FALSE(poGeom->hasEmptyParts()); + poGeom->removeEmptyParts(); + EXPECT_EQ(poGeom->toCompoundCurve()->getNumCurves(), 1); + delete poGeom; + } + { + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkt("COMPOUNDCURVE ((0 0,1 1),(1 1,2 2))", + nullptr, &poGeom); + ASSERT_NE(poGeom, nullptr); + poGeom->toCompoundCurve()->getCurve(1)->empty(); + EXPECT_EQ(poGeom->toCompoundCurve()->getNumCurves(), 2); + EXPECT_TRUE(poGeom->hasEmptyParts()); + poGeom->removeEmptyParts(); + EXPECT_FALSE(poGeom->hasEmptyParts()); + EXPECT_EQ(poGeom->toCompoundCurve()->getNumCurves(), 1); + delete poGeom; + } + { + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkt("GEOMETRYCOLLECTION (POINT(0 0))", + nullptr, &poGeom); + ASSERT_NE(poGeom, nullptr); + EXPECT_FALSE(poGeom->hasEmptyParts()); + poGeom->removeEmptyParts(); + EXPECT_EQ(poGeom->toGeometryCollection()->getNumGeometries(), 1); + delete poGeom; + } + { + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkt( + "GEOMETRYCOLLECTION (POINT EMPTY,POINT(0 0),POINT EMPTY)", nullptr, + &poGeom); + ASSERT_NE(poGeom, nullptr); + EXPECT_EQ(poGeom->toGeometryCollection()->getNumGeometries(), 3); + EXPECT_TRUE(poGeom->hasEmptyParts()); + poGeom->removeEmptyParts(); + EXPECT_FALSE(poGeom->hasEmptyParts()); + EXPECT_EQ(poGeom->toGeometryCollection()->getNumGeometries(), 1); + delete poGeom; + } + { + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkt("GEOMETRYCOLLECTION EMPTY", nullptr, + &poGeom); + ASSERT_NE(poGeom, nullptr); + OGRGeometry *poPoly = nullptr; + OGRGeometryFactory::createFromWkt("POLYGON ((0 0,0 1,1 0,0 0))", + nullptr, &poPoly); + EXPECT_NE(poPoly, nullptr); + if (poPoly) + { + poPoly->toPolygon()->addRingDirectly(new OGRLinearRing()); + poGeom->toGeometryCollection()->addGeometryDirectly(poPoly); + EXPECT_EQ(poGeom->toGeometryCollection()->getNumGeometries(), 1); + EXPECT_TRUE(poGeom->hasEmptyParts()); + poGeom->removeEmptyParts(); + EXPECT_FALSE(poGeom->hasEmptyParts()); + EXPECT_EQ(poGeom->toGeometryCollection()->getNumGeometries(), 1); + } + delete poGeom; + } + { + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkt( + "POLYHEDRALSURFACE (((0 0,0 1,1 1,0 0)))", nullptr, &poGeom); + ASSERT_NE(poGeom, nullptr); + EXPECT_FALSE(poGeom->hasEmptyParts()); + poGeom->removeEmptyParts(); + EXPECT_EQ(poGeom->toPolyhedralSurface()->getNumGeometries(), 1); + delete poGeom; + } + { + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkt( + "POLYHEDRALSURFACE (((0 0,0 1,1 1,0 0)))", nullptr, &poGeom); + ASSERT_NE(poGeom, nullptr); + poGeom->toPolyhedralSurface()->addGeometryDirectly(new OGRPolygon()); + EXPECT_EQ(poGeom->toPolyhedralSurface()->getNumGeometries(), 2); + EXPECT_TRUE(poGeom->hasEmptyParts()); + poGeom->removeEmptyParts(); + EXPECT_FALSE(poGeom->hasEmptyParts()); + EXPECT_EQ(poGeom->toPolyhedralSurface()->getNumGeometries(), 1); + delete poGeom; + } +} + } // namespace diff --git a/autotest/ogr/ogr_geos.py b/autotest/ogr/ogr_geos.py index 343d28bf7967..fee81566d24f 100755 --- a/autotest/ogr/ogr_geos.py +++ b/autotest/ogr/ogr_geos.py @@ -309,6 +309,19 @@ def test_ogr_geos_centroid_point_empty(): ############################################################################### +def test_ogr_geos_centroid_polygon_with_empty_interior_ring(): + + g = ogr.CreateGeometryFromWkt("POLYGON((0 0,0 1,1 1,1 0,0 0))") + g.AddGeometry(ogr.Geometry(ogr.wkbLinearRing)) + + centroid = g.Centroid() + + assert centroid.ExportToWkt() == "POINT (0.5 0.5)" + + +############################################################################### + + @pytest.mark.require_geos(3, 12) def test_ogr_geos_pointzm_empty(): diff --git a/ogr/ogr_geometry.h b/ogr/ogr_geometry.h index 73bc05f71678..0c46c2fa6a89 100644 --- a/ogr/ogr_geometry.h +++ b/ogr/ogr_geometry.h @@ -523,8 +523,9 @@ class CPL_DLL OGRGeometry static GEOSContextHandle_t createGEOSContext(); static void freeGEOSContext(GEOSContextHandle_t hGEOSCtxt); - virtual GEOSGeom - exportToGEOS(GEOSContextHandle_t hGEOSCtxt) const CPL_WARN_UNUSED_RESULT; + GEOSGeom + exportToGEOS(GEOSContextHandle_t hGEOSCtxt, + bool bRemoveEmptyParts = false) const CPL_WARN_UNUSED_RESULT; virtual OGRBoolean hasCurveGeometry(int bLookForNonLinear = FALSE) const; virtual OGRGeometry *getCurveGeometry( const char *const *papszOptions = nullptr) const CPL_WARN_UNUSED_RESULT; @@ -608,6 +609,9 @@ class CPL_DLL OGRGeometry OGRGeometry *SetPrecision(double dfGridSize, int nFlags) const; + virtual bool hasEmptyParts() const; + virtual void removeEmptyParts(); + //! @cond Doxygen_Suppress // backward compatibility to non-standard method names. OGRBoolean Intersect(OGRGeometry *) const @@ -2146,6 +2150,9 @@ class CPL_DLL OGRCurveCollection OGRErr removeCurve(int iIndex, bool bDelete = true); + bool hasEmptyParts() const; + void removeEmptyParts(); + OGRErr transform(OGRGeometry *poGeom, OGRCoordinateTransformation *poCT); void flattenTo2D(OGRGeometry *poGeom); void segmentize(double dfMaxLength); @@ -2335,6 +2342,9 @@ class CPL_DLL OGRCompoundCurve : public OGRCurve virtual void swapXY() override; + bool hasEmptyParts() const override; + void removeEmptyParts() override; + OGR_ALLOW_UPCAST_TO(Curve) OGR_ALLOW_CAST_TO_THIS(CompoundCurve) }; @@ -2584,6 +2594,9 @@ class CPL_DLL OGRCurvePolygon : public OGRSurface virtual void swapXY() override; + bool hasEmptyParts() const override; + void removeEmptyParts() override; + OGR_ALLOW_UPCAST_TO(Surface) OGR_ALLOW_CAST_TO_THIS(CurvePolygon) }; @@ -3009,6 +3022,9 @@ class CPL_DLL OGRGeometryCollection : public OGRGeometry OGRErr addGeometry(std::unique_ptr geom); virtual OGRErr removeGeometry(int iIndex, int bDelete = TRUE); + bool hasEmptyParts() const override; + void removeEmptyParts() override; + virtual void assignSpatialReference(const OGRSpatialReference *poSR) override; @@ -3506,6 +3522,9 @@ class CPL_DLL OGRPolyhedralSurface : public OGRSurface virtual void swapXY() override; OGRErr removeGeometry(int iIndex, int bDelete = TRUE); + bool hasEmptyParts() const override; + void removeEmptyParts() override; + virtual void accept(IOGRGeometryVisitor *visitor) override { visitor->visit(this); diff --git a/ogr/ogrcompoundcurve.cpp b/ogr/ogrcompoundcurve.cpp index 00a63bca6f05..c3728dc7dd1e 100644 --- a/ogr/ogrcompoundcurve.cpp +++ b/ogr/ogrcompoundcurve.cpp @@ -970,3 +970,21 @@ double OGRCompoundCurve::get_AreaOfCurveSegments() const } return dfArea; } + +/************************************************************************/ +/* hasEmptyParts() */ +/************************************************************************/ + +bool OGRCompoundCurve::hasEmptyParts() const +{ + return oCC.hasEmptyParts(); +} + +/************************************************************************/ +/* removeEmptyParts() */ +/************************************************************************/ + +void OGRCompoundCurve::removeEmptyParts() +{ + oCC.removeEmptyParts(); +} diff --git a/ogr/ogrcurvecollection.cpp b/ogr/ogrcurvecollection.cpp index 9a3bd3f6dcc8..cde915c87a04 100644 --- a/ogr/ogrcurvecollection.cpp +++ b/ogr/ogrcurvecollection.cpp @@ -749,4 +749,46 @@ OGRErr OGRCurveCollection::removeCurve(int iIndex, bool bDelete) return OGRERR_NONE; } +/************************************************************************/ +/* hasEmptyParts() */ +/************************************************************************/ + +/** + * \brief Returns whether a geometry has empty parts/rings. + * + * Returns true if removeEmptyParts() will modify the geometry. + * + * This is different from IsEmpty(). + * + * @since GDAL 3.10 + */ +bool OGRCurveCollection::hasEmptyParts() const +{ + for (int i = 0; i < nCurveCount; ++i) + { + if (papoCurves[i]->IsEmpty() || papoCurves[i]->hasEmptyParts()) + return true; + } + return false; +} + +/************************************************************************/ +/* removeEmptyParts() */ +/************************************************************************/ + +/** + * \brief Remove empty parts/rings from this geometry. + * + * @since GDAL 3.10 + */ +void OGRCurveCollection::removeEmptyParts() +{ + for (int i = nCurveCount - 1; i >= 0; --i) + { + papoCurves[i]->removeEmptyParts(); + if (papoCurves[i]->IsEmpty()) + removeCurve(i, true); + } +} + //! @endcond diff --git a/ogr/ogrcurvepolygon.cpp b/ogr/ogrcurvepolygon.cpp index 4f67d20196c7..af8d744d4150 100644 --- a/ogr/ogrcurvepolygon.cpp +++ b/ogr/ogrcurvepolygon.cpp @@ -929,3 +929,25 @@ OGRSurfaceCasterToCurvePolygon OGRCurvePolygon::GetCasterToCurvePolygon() const } //! @endcond + +/************************************************************************/ +/* hasEmptyParts() */ +/************************************************************************/ + +bool OGRCurvePolygon::hasEmptyParts() const +{ + return oCC.hasEmptyParts(); +} + +/************************************************************************/ +/* removeEmptyParts() */ +/************************************************************************/ + +void OGRCurvePolygon::removeEmptyParts() +{ + auto poExteriorRing = getExteriorRingCurve(); + if (poExteriorRing && poExteriorRing->IsEmpty()) + empty(); + else + oCC.removeEmptyParts(); +} diff --git a/ogr/ogrgeometry.cpp b/ogr/ogrgeometry.cpp index 52d91d4d21b2..770a64b4e2f2 100644 --- a/ogr/ogrgeometry.cpp +++ b/ogr/ogrgeometry.cpp @@ -3269,10 +3269,13 @@ static GEOSGeom convertToGEOSGeom(GEOSContextHandle_t hGEOSCtxt, /** Returns a GEOSGeom object corresponding to the geometry. * * @param hGEOSCtxt GEOS context + * @param bRemoveEmptyParts Whether empty parts of the geometry should be + * removed before exporting to GEOS (GDAL >= 3.10) * @return a GEOSGeom object corresponding to the geometry. */ GEOSGeom -OGRGeometry::exportToGEOS(UNUSED_IF_NO_GEOS GEOSContextHandle_t hGEOSCtxt) const +OGRGeometry::exportToGEOS(UNUSED_IF_NO_GEOS GEOSContextHandle_t hGEOSCtxt, + UNUSED_IF_NO_GEOS bool bRemoveEmptyParts) const { #ifndef HAVE_GEOS @@ -3301,6 +3304,8 @@ OGRGeometry::exportToGEOS(UNUSED_IF_NO_GEOS GEOSContextHandle_t hGEOSCtxt) const if (hasCurveGeometry()) { poLinearGeom = getLinearGeometry(); + if (bRemoveEmptyParts) + poLinearGeom->removeEmptyParts(); #if (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 12) // GEOS < 3.12 doesn't support M dimension if (poLinearGeom->IsMeasured()) @@ -3315,9 +3320,17 @@ OGRGeometry::exportToGEOS(UNUSED_IF_NO_GEOS GEOSContextHandle_t hGEOSCtxt) const if (IsMeasured()) { poLinearGeom = clone(); + if (bRemoveEmptyParts) + poLinearGeom->removeEmptyParts(); poLinearGeom->setMeasured(FALSE); } + else #endif + if (bRemoveEmptyParts && hasEmptyParts()) + { + poLinearGeom = clone(); + poLinearGeom->removeEmptyParts(); + } } if (eType == wkbTriangle) { @@ -6046,7 +6059,8 @@ OGRErr OGRGeometry::Centroid(OGRPoint *poPoint) const #else GEOSContextHandle_t hGEOSCtxt = createGEOSContext(); - GEOSGeom hThisGeosGeom = exportToGEOS(hGEOSCtxt); + GEOSGeom hThisGeosGeom = + exportToGEOS(hGEOSCtxt, /* bRemoveEmptyParts = */ true); if (hThisGeosGeom != nullptr) { @@ -8542,3 +8556,34 @@ bool OGRGeometry::IsRectangle() const return false; } + +/************************************************************************/ +/* hasEmptyParts() */ +/************************************************************************/ + +/** + * \brief Returns whether a geometry has empty parts/rings. + * + * Returns true if removeEmptyParts() will modify the geometry. + * + * This is different from IsEmpty(). + * + * @since GDAL 3.10 + */ +bool OGRGeometry::hasEmptyParts() const +{ + return false; +} + +/************************************************************************/ +/* removeEmptyParts() */ +/************************************************************************/ + +/** + * \brief Remove empty parts/rings from this geometry. + * + * @since GDAL 3.10 + */ +void OGRGeometry::removeEmptyParts() +{ +} diff --git a/ogr/ogrgeometrycollection.cpp b/ogr/ogrgeometrycollection.cpp index 09828abdb53c..cd0ea336bad5 100644 --- a/ogr/ogrgeometrycollection.cpp +++ b/ogr/ogrgeometrycollection.cpp @@ -453,6 +453,34 @@ OGRErr OGRGeometryCollection::removeGeometry(int iGeom, int bDelete) return OGRERR_NONE; } +/************************************************************************/ +/* hasEmptyParts() */ +/************************************************************************/ + +bool OGRGeometryCollection::hasEmptyParts() const +{ + for (int i = 0; i < nGeomCount; ++i) + { + if (papoGeoms[i]->IsEmpty() || papoGeoms[i]->hasEmptyParts()) + return true; + } + return false; +} + +/************************************************************************/ +/* removeEmptyParts() */ +/************************************************************************/ + +void OGRGeometryCollection::removeEmptyParts() +{ + for (int i = nGeomCount - 1; i >= 0; --i) + { + papoGeoms[i]->removeEmptyParts(); + if (papoGeoms[i]->IsEmpty()) + removeGeometry(i, true); + } +} + /************************************************************************/ /* WkbSize() */ /* */ diff --git a/ogr/ogrpolyhedralsurface.cpp b/ogr/ogrpolyhedralsurface.cpp index 20d2a81a5fa2..1e438b0851d7 100644 --- a/ogr/ogrpolyhedralsurface.cpp +++ b/ogr/ogrpolyhedralsurface.cpp @@ -1009,6 +1009,24 @@ OGRErr OGRPolyhedralSurface::removeGeometry(int iGeom, int bDelete) return oMP.removeGeometry(iGeom, bDelete); } +/************************************************************************/ +/* hasEmptyParts() */ +/************************************************************************/ + +bool OGRPolyhedralSurface::hasEmptyParts() const +{ + return oMP.hasEmptyParts(); +} + +/************************************************************************/ +/* removeEmptyParts() */ +/************************************************************************/ + +void OGRPolyhedralSurface::removeEmptyParts() +{ + oMP.removeEmptyParts(); +} + /************************************************************************/ /* assignSpatialReference() */ /************************************************************************/ From 807f55e62ae27d133c24f35bb7fc35c42804d695 Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Tue, 18 Jun 2024 12:52:26 -0400 Subject: [PATCH 0178/1119] Doc: Move 3.8.5 to download_past.rst --- doc/source/download.rst | 9 --------- doc/source/download_past.rst | 6 ++++++ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/doc/source/download.rst b/doc/source/download.rst index 4affb7bd8c6c..d9c77ab3353a 100644 --- a/doc/source/download.rst +++ b/doc/source/download.rst @@ -24,15 +24,6 @@ Current Release .. _`gdal-3.9.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.9.0/gdal-3.9.0.tar.gz .. _`3.9.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.9.0/gdal-3.9.0.tar.gz.md5 -Additional Supported Releases -............................. - -* **2024-04-04** `gdal-3.8.5.tar.gz`_ `3.8.5 Release Notes`_ (`3.8.5 md5`_) - -.. _`3.8.5 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.5/NEWS.md -.. _`gdal-3.8.5.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.5/gdal-3.8.5.tar.gz -.. _`3.8.5 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.5/gdal-3.8.5.tar.gz.md5 - Past Releases ............. diff --git a/doc/source/download_past.rst b/doc/source/download_past.rst index c4837ab64a0e..7e42187a4de8 100644 --- a/doc/source/download_past.rst +++ b/doc/source/download_past.rst @@ -5,6 +5,12 @@ Past Releases ============= +* **2024-04-04** `gdal-3.8.5.tar.gz`_ `3.8.5 Release Notes`_ (`3.8.5 md5`_) + +.. _`3.8.5 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.5/NEWS.md +.. _`gdal-3.8.5.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.8.5/gdal-3.8.5.tar.gz +.. _`3.8.5 md5`: https://github.com/OSGeo/gdal/releases/download/v3.8.5/gdal-3.8.5.tar.gz.md5 + * **2024-02-18** `gdal-3.8.4.tar.gz`_ `3.8.4 Release Notes`_ (`3.8.4 md5`_) .. _`3.8.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.4/NEWS.md From 3beca167d8ebfa32db8cf6fb8147957219e1e3dd Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Tue, 18 Jun 2024 12:51:01 -0400 Subject: [PATCH 0179/1119] Doc: Add ancient releases to download_past.rst --- doc/source/download_past.rst | 365 +++++++++++++++++++++++++++++++++++ 1 file changed, 365 insertions(+) diff --git a/doc/source/download_past.rst b/doc/source/download_past.rst index 7e42187a4de8..1e20d7db418c 100644 --- a/doc/source/download_past.rst +++ b/doc/source/download_past.rst @@ -265,3 +265,368 @@ Past Releases .. _`2.4.2 md5`: https://download.osgeo.org/gdal/2.4.2/gdal-2.4.2.tar.gz.md5 +* **2019-05** `gdal-3.0.0.tar.gz`_ `3.0.0 Release Notes`_ (`3.0.0 md5`)_ + +.. _`gdal-3.0.0.tar.gz`: http://download.osgeo.org/gdal/3.0.0/gdal-3.0.0.tar.gz +.. _`3.0.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.0.0/gdal/NEWS +.. _`3.0.0 md5`: http://download.osgeo.org/gdal/3.0.0/gdal-3.0.0.tar.gz.md5 + + +* **2019-03** `gdal-2.4.1.tar.gz`_ `2.4.1 Release Notes`_ (`2.4.1 md5`)_ + +.. _`gdal-2.4.1.tar.gz`: http://download.osgeo.org/gdal/2.4.1/gdal-2.4.1.tar.gz +.. _`2.4.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.4.1/gdal/NEWS +.. _`2.4.1 md5`: http://download.osgeo.org/gdal/2.4.1/gdal-2.4.1.tar.gz.md5 + + +* **2018-12** `gdal-2.4.0.tar.gz`_ `2.4.0 Release Notes`_ (`2.4.0 md5`)_ + +.. _`gdal-2.4.0.tar.gz`: http://download.osgeo.org/gdal/2.4.0/gdal-2.4.0.tar.gz +.. _`2.4.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.4.0/gdal/NEWS +.. _`2.4.0 md5`: http://download.osgeo.org/gdal/2.4.0/gdal-2.4.0.tar.gz.md5 + + +* **2019-05** `gdal-3.0.0.tar.gz`_ `3.0.0 Release Notes`_ (`3.0.0 md5`)_ + +.. _`gdal-3.0.0.tar.gz`: http://download.osgeo.org/gdal/3.0.0/gdal-3.0.0.tar.gz +.. _`3.0.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.0.0/gdal/NEWS +.. _`3.0.0 md5`: http://download.osgeo.org/gdal/3.0.0/gdal-3.0.0.tar.gz.md5 + + +* **2019-03** `gdal-2.4.1.tar.gz`_ `2.4.1 Release Notes`_ (`2.4.1 md5`)_ + +.. _`gdal-2.4.1.tar.gz`: http://download.osgeo.org/gdal/2.4.1/gdal-2.4.1.tar.gz +.. _`2.4.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.4.1/gdal/NEWS +.. _`2.4.1 md5`: http://download.osgeo.org/gdal/2.4.1/gdal-2.4.1.tar.gz.md5 + + +* **2018-12** `gdal-2.4.0.tar.gz`_ `2.4.0 Release Notes`_ (`2.4.0 md5`)_ + +.. _`gdal-2.4.0.tar.gz`: http://download.osgeo.org/gdal/2.4.0/gdal-2.4.0.tar.gz +.. _`2.4.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.4.0/gdal/NEWS +.. _`2.4.0 md5`: http://download.osgeo.org/gdal/2.4.0/gdal-2.4.0.tar.gz.md5 + + +* **2018-12** `gdal-2.3.3.tar.gz`_ `2.3.3 Release Notes`_ (`2.3.3 md5`)_ + +.. _`gdal-2.3.3.tar.gz`: http://download.osgeo.org/gdal/2.3.3/gdal-2.3.3.tar.gz +.. _`2.3.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.3.3/gdal/NEWS +.. _`2.3.3 md5`: http://download.osgeo.org/gdal/2.3.3/gdal-2.3.3.tar.gz.md5 + + +* **2018-09** `gdal-2.3.2.tar.gz`_ `2.3.2 Release Notes`_ (`2.3.2 md5`)_ + +.. _`gdal-2.3.2.tar.gz`: http://download.osgeo.org/gdal/2.3.2/gdal-2.3.2.tar.gz +.. _`2.3.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.3.2/gdal/NEWS +.. _`2.3.2 md5`: http://download.osgeo.org/gdal/2.3.2/gdal-2.3.2.tar.gz.md5 + + +* **2018-06** `gdal-2.3.1.tar.gz`_ `2.3.1 Release Notes`_ (`2.3.1 md5`)_ + +.. _`gdal-2.3.1.tar.gz`: http://download.osgeo.org/gdal/2.3.1/gdal-2.3.1.tar.gz +.. _`2.3.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.3.1/gdal/NEWS +.. _`2.3.1 md5`: http://download.osgeo.org/gdal/2.3.1/gdal-2.3.1.tar.gz.md5 + + +* **2018-05** `gdal-2.3.0.tar.gz`_ `2.3.0 Release Notes`_ (`2.3.0 md5`)_ + +.. _`gdal-2.3.0.tar.gz`: http://download.osgeo.org/gdal/2.3.0/gdal-2.3.0.tar.gz +.. _`2.3.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.3.0/gdal/NEWS +.. _`2.3.0 md5`: http://download.osgeo.org/gdal/2.3.0/gdal-2.3.0.tar.gz.md5 + + +* **2018-03** `gdal-2.2.4.tar.gz`_ `2.2.4 Release Notes`_ (`2.2.4 md5`)_ + +.. _`gdal-2.2.4.tar.gz`: http://download.osgeo.org/gdal/2.2.4/gdal-2.2.4.tar.gz +.. _`2.2.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.2.4/gdal/NEWS +.. _`2.2.4 md5`: http://download.osgeo.org/gdal/2.2.4/gdal-2.2.4.tar.gz.md5 + + +* **2017-11** `gdal-2.2.3.tar.gz`_ `2.2.3 Release Notes`_ (`2.2.3 md5`)_ + +.. _`gdal-2.2.3.tar.gz`: http://download.osgeo.org/gdal/2.2.3/gdal-2.2.3.tar.gz +.. _`2.2.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.2.3/gdal/NEWS +.. _`2.2.3 md5`: http://download.osgeo.org/gdal/2.2.3/gdal-2.2.3.tar.gz.md5 + + +* **2017-09** `gdal-2.2.2.tar.gz`_ `2.2.2 Release Notes`_ (`2.2.2 md5`)_ + +.. _`gdal-2.2.2.tar.gz`: http://download.osgeo.org/gdal/2.2.2/gdal-2.2.2.tar.gz +.. _`2.2.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.2.2/gdal/NEWS +.. _`2.2.2 md5`: http://download.osgeo.org/gdal/2.2.2/gdal-2.2.2.tar.gz.md5 + + +* **2017-06** `gdal-2.2.1.tar.gz`_ `2.2.1 Release Notes`_ (`2.2.1 md5`)_ + +.. _`gdal-2.2.1.tar.gz`: http://download.osgeo.org/gdal/2.2.1/gdal-2.2.1.tar.gz +.. _`2.2.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.2.1/gdal/NEWS +.. _`2.2.1 md5`: http://download.osgeo.org/gdal/2.2.1/gdal-2.2.1.tar.gz.md5 + + +* **2017-06** `gdal-2.1.4.tar.gz`_ `2.1.4 Release Notes`_ (`2.1.4 md5`)_ + +.. _`gdal-2.1.4.tar.gz`: http://download.osgeo.org/gdal/2.1.4/gdal-2.1.4.tar.gz +.. _`2.1.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.1.4/gdal/NEWS +.. _`2.1.4 md5`: http://download.osgeo.org/gdal/2.1.4/gdal-2.1.4.tar.gz.md5 + + +* **2017-04** `gdal-2.2.0.tar.gz`_ `2.2.0 Release Notes`_ (`2.2.0 md5`)_ + +.. _`gdal-2.2.0.tar.gz`: http://download.osgeo.org/gdal/2.2.0/gdal-2.2.0.tar.gz +.. _`2.2.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.2.0/gdal/NEWS +.. _`2.2.0 md5`: http://download.osgeo.org/gdal/2.2.0/gdal-2.2.0.tar.gz.md5 + + +* **2017-01** `gdal-2.1.3.tar.gz`_ `2.1.3 Release Notes`_ (`2.1.3 md5`)_ + +.. _`gdal-2.1.3.tar.gz`: http://download.osgeo.org/gdal/2.1.3/gdal-2.1.3.tar.gz +.. _`2.1.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.1.3/gdal/NEWS +.. _`2.1.3 md5`: http://download.osgeo.org/gdal/2.1.3/gdal-2.1.3.tar.gz.md5 + + +* **2016-10** `gdal-2.1.2.tar.gz`_ `2.1.2 Release Notes`_ (`2.1.2 md5`)_ + +.. _`gdal-2.1.2.tar.gz`: http://download.osgeo.org/gdal/2.1.2/gdal-2.1.2.tar.gz +.. _`2.1.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.1.2/gdal/NEWS +.. _`2.1.2 md5`: http://download.osgeo.org/gdal/2.1.2/gdal-2.1.2.tar.gz.md5 + + +* **2016-07** `gdal-2.1.1.tar.gz`_ `2.1.1 Release Notes`_ (`2.1.1 md5`)_ + +.. _`gdal-2.1.1.tar.gz`: http://download.osgeo.org/gdal/2.1.1/gdal-2.1.1.tar.gz +.. _`2.1.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.1.1/gdal/NEWS +.. _`2.1.1 md5`: http://download.osgeo.org/gdal/2.1.1/gdal-2.1.1.tar.gz.md5 + + +* **2016-07** `gdal-2.0.3.tar.gz`_ `2.0.3 Release Notes`_ (`2.0.3 md5`)_ + +.. _`gdal-2.0.3.tar.gz`: http://download.osgeo.org/gdal/2.0.3/gdal-2.0.3.tar.gz +.. _`2.0.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.0.3/gdal/NEWS +.. _`2.0.3 md5`: http://download.osgeo.org/gdal/2.0.3/gdal-2.0.3.tar.gz.md5 + + +* **2016-07** `gdal-1.11.5.tar.gz`_ `1.11.5 Release Notes`_ (`1.11.5 md5`)_ + +.. _`gdal-1.11.5.tar.gz`: http://download.osgeo.org/gdal/1.11.5/gdal-1.11.5.tar.gz +.. _`1.11.5 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.11.5/gdal/NEWS +.. _`1.11.5 md5`: http://download.osgeo.org/gdal/1.11.5/gdal-1.11.5.tar.gz.md5 + + +* **2016-04** `gdal-2.1.0.tar.gz`_ `2.1.0 Release Notes`_ (`2.1.0 md5`)_ + +.. _`gdal-2.1.0.tar.gz`: http://download.osgeo.org/gdal/2.1.0/gdal-2.1.0.tar.gz +.. _`2.1.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.1.0/gdal/NEWS +.. _`2.1.0 md5`: http://download.osgeo.org/gdal/2.1.0/gdal-2.1.0.tar.gz.md5 + + +* **2016-01** `gdal-2.0.2.tar.gz`_ `2.0.2 Release Notes`_ (`2.0.2 md5`)_ + +.. _`gdal-2.0.2.tar.gz`: http://download.osgeo.org/gdal/2.0.2/gdal-2.0.2.tar.gz +.. _`2.0.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.0.2/gdal/NEWS +.. _`2.0.2 md5`: http://download.osgeo.org/gdal/2.0.2/gdal-2.0.2.tar.gz.md5 + + +* **2016-01** `gdal-1.11.4.tar.gz`_ `1.11.4 Release Notes`_ (`1.11.4 md5`)_ + +.. _`gdal-1.11.4.tar.gz`: http://download.osgeo.org/gdal/1.11.4/gdal-1.11.4.tar.gz +.. _`1.11.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.11.4/gdal/NEWS +.. _`1.11.4 md5`: http://download.osgeo.org/gdal/1.11.4/gdal-1.11.4.tar.gz.md5 + + +* **2015-09** `gdal-2.0.1.tar.gz`_ `2.0.1 Release Notes`_ (`2.0.1 md5`)_ + +.. _`gdal-2.0.1.tar.gz`: http://download.osgeo.org/gdal/2.0.1/gdal-2.0.1.tar.gz +.. _`2.0.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.0.1/gdal/NEWS +.. _`2.0.1 md5`: http://download.osgeo.org/gdal/2.0.1/gdal-2.0.1.tar.gz.md5 + + +* **2015-09** `gdal-1.11.3.tar.gz`_ `1.11.3 Release Notes`_ (`1.11.3 md5`)_ + +.. _`gdal-1.11.3.tar.gz`: http://download.osgeo.org/gdal/1.11.3/gdal-1.11.3.tar.gz +.. _`1.11.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.11.3/gdal/NEWS +.. _`1.11.3 md5`: http://download.osgeo.org/gdal/1.11.3/gdal-1.11.3.tar.gz.md5 + + +* **2015-06** `gdal-2.0.0.tar.gz`_ `2.0.0 Release Notes`_ (`2.0.0 md5`)_ + +.. _`gdal-2.0.0.tar.gz`: http://download.osgeo.org/gdal/2.0.0/gdal-2.0.0.tar.gz +.. _`2.0.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v2.0.0/gdal/NEWS +.. _`2.0.0 md5`: http://download.osgeo.org/gdal/2.0.0/gdal-2.0.0.tar.gz.md5 + + +* **2015-02** `gdal-1.11.2.tar.gz`_ `1.11.2 Release Notes`_ (`1.11.2 md5`)_ + +.. _`gdal-1.11.2.tar.gz`: http://download.osgeo.org/gdal/1.11.2/gdal-1.11.2.tar.gz +.. _`1.11.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.11.2/gdal/NEWS +.. _`1.11.2 md5`: http://download.osgeo.org/gdal/1.11.2/gdal-1.11.2.tar.gz.md5 + + +* **2014-09** `gdal-1.11.1.tar.gz`_ `1.11.1 Release Notes`_ (`1.11.1 md5`)_ + +.. _`gdal-1.11.1.tar.gz`: http://download.osgeo.org/gdal/1.11.1/gdal-1.11.1.tar.gz +.. _`1.11.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.11.1/gdal/NEWS +.. _`1.11.1 md5`: http://download.osgeo.org/gdal/1.11.1/gdal-1.11.1.tar.gz.md5 + + +* **2014-04** `gdal-1.11.0.tar.gz`_ `1.11.0 Release Notes`_ (`1.11.0 md5`)_ + +.. _`gdal-1.11.0.tar.gz`: http://download.osgeo.org/gdal/1.11.0/gdal-1.11.0.tar.gz +.. _`1.11.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.11.0/gdal/NEWS +.. _`1.11.0 md5`: http://download.osgeo.org/gdal/1.11.0/gdal-1.11.0.tar.gz.md5 + + +* **2013-08** `gdal-1.10.1.tar.gz`_ `1.10.1 Release Notes`_ (`1.10.1 md5`)_ + +.. _`gdal-1.10.1.tar.gz`: http://download.osgeo.org/gdal/1.10.1/gdal-1.10.1.tar.gz +.. _`1.10.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.10.1/gdal/NEWS +.. _`1.10.1 md5`: http://download.osgeo.org/gdal/1.10.1/gdal-1.10.1.tar.gz.md5 + + +* **2013-04** `gdal-1.10.0.tar.gz`_ `1.10.0 Release Notes`_ (`1.10.0 md5`)_ + +.. _`gdal-1.10.0.tar.gz`: http://download.osgeo.org/gdal/1.10.0/gdal-1.10.0.tar.gz +.. _`1.10.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.10.0/gdal/NEWS +.. _`1.10.0 md5`: http://download.osgeo.org/gdal/1.10.0/gdal-1.10.0.tar.gz.md5 + + +* **2012-10** `gdal-1.9.2.tar.gz`_ `1.9.2 Release Notes`_ (`1.9.2 md5`)_ + +.. _`gdal-1.9.2.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.9.2.tar.gz +.. _`1.9.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.9.2/gdal/NEWS +.. _`1.9.2 md5`: http://download.osgeo.org/gdal/old_releases/gdal-1.9.2.tar.gz.md5 + + +* **2012-05** `gdal-1.9.1.tar.gz`_ `1.9.1 Release Notes`_ (`1.9.1 md5`)_ + +.. _`gdal-1.9.1.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.9.1.tar.gz +.. _`1.9.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.9.1/gdal/NEWS +.. _`1.9.1 md5`: http://download.osgeo.org/gdal/old_releases/gdal-1.9.1.tar.gz.md5 + + +* **2011-12** `gdal-1.9.0.tar.gz`_ `1.9.0 Release Notes`_ (`1.9.0 md5`)_ + +.. _`gdal-1.9.0.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.9.0.tar.gz +.. _`1.9.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.9.0/gdal/NEWS +.. _`1.9.0 md5`: http://download.osgeo.org/gdal/old_releases/gdal-1.9.0.tar.gz.md5 + + +* **2011-07** `gdal-1.8.1.tar.gz`_ `1.8.1 Release Notes`_ (`1.8.1 md5`)_ + +.. _`gdal-1.8.1.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.8.1.tar.gz +.. _`1.8.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.8.1/gdal/NEWS +.. _`1.8.1 md5`: http://download.osgeo.org/gdal/old_releases/gdal-1.8.1.tar.gz.md5 + + +* **2011-01** `gdal-1.8.0.tar.gz`_ `1.8.0 Release Notes`_ (`1.8.0 md5`)_ + +.. _`gdal-1.8.0.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.8.0.tar.gz +.. _`1.8.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.8.0/gdal/NEWS +.. _`1.8.0 md5`: http://download.osgeo.org/gdal/old_releases/gdal-1.8.0.tar.gz.md5 + + +* **2010-11** `gdal-1.7.3.tar.gz`_ `1.7.3 Release Notes`_ (`1.7.3 md5`)_ + +.. _`gdal-1.7.3.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.7.3.tar.gz +.. _`1.7.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.7.3/gdal/NEWS +.. _`1.7.3 md5`: http://download.osgeo.org/gdal/old_releases/gdal-1.7.3.tar.gz.md5 + +* **2010-04** `gdal-1.7.2.tar.gz`_ `1.7.2 Release Notes`_ (`1.7.2 md5`)_ + +.. _`gdal-1.7.2.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.7.2.tar.gz +.. _`1.7.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.7.2/gdal/NEWS +.. _`1.7.2 md5`: http://download.osgeo.org/gdal/old_releases/gdal-1.7.2.tar.gz.md5 + + +* **2010-02** `gdal-1.7.1.tar.gz`_ `1.7.1 Release Notes`_ (`1.7.1 md5`)_ + +.. _`gdal-1.7.1.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.7.1.tar.gz +.. _`1.7.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.7.1/gdal/NEWS +.. _`1.7.1 md5`: http://download.osgeo.org/gdal/old_releases/gdal-1.7.1.tar.gz.md5 + + +* **2010-01** `1.7.0 (retracted) Release Notes`_ + +.. _`1.7.0 (retracted) Release Notes`: https://github.com/OSGeo/gdal/blob/v1.7.0/gdal/NEWS + + +* **2009-11** `gdal-1.6.3.tar.gz`_ `1.6.3 Release Notes`_ + +.. _`gdal-1.6.3.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.6.3.tar.gz +.. _`1.6.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.6.3/gdal/NEWS + + +* **2009-08** `gdal-1.6.2.tar.gz`_ `1.6.2 Release Notes`_ + +.. _`gdal-1.6.2.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.6.2.tar.gz +.. _`1.6.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.6.2/gdal/NEWS + + +* **2009-05** `gdal-1.6.1.tar.gz`_ `1.6.1 Release Notes`_ + +.. _`gdal-1.6.1.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.6.1.tar.gz +.. _`1.6.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.6.1/gdal/NEWS + + +* **2008-12** `gdal-1.6.0.tar.gz`_ `1.6.0 Release Notes`_ + +.. _`gdal-1.6.0.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.6.0.tar.gz +.. _`1.6.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.6.0/gdal/NEWS + + +* **2009-01** `gdal-1.5.4.tar.gz`_ `1.5.4 Release Notes`_ + +.. _`gdal-1.5.4.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.5.4.tar.gz +.. _`1.5.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.5.4/gdal/NEWS + + +* **2008-10** `gdal-1.5.3.tar.gz`_ `1.5.3 Release Notes`_ + +.. _`gdal-1.5.3.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.5.3.tar.gz +.. _`1.5.3 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.5.3/gdal/NEWS + + +* **2008-05** `gdal-1.5.2.tar.gz`_ `1.5.2 Release Notes`_ + +.. _`gdal-1.5.2.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.5.2.tar.gz +.. _`1.5.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.5.2/gdal/NEWS + + +* **2008-03** `gdal-1.5.1.tar.gz`_ `1.5.1 Release Notes`_ + +.. _`gdal-1.5.1.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.5.1.tar.gz +.. _`1.5.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.5.1/gdal/NEWS + + +* **2007-12** `gdal-1.5.0.tar.gz`_ `1.5.0 Release Notes`_ + +.. _`gdal-1.5.0.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.5.0.tar.gz +.. _`1.5.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.5.0/gdal/NEWS + + +* **2008-12** `gdal-1.4.5.tar.gz`_ `1.4.5 Release Notes`_ + +.. _`gdal-1.4.5.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.4.5.tar.gz +.. _`1.4.5 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.4.5/gdal/NEWS + + +* **2007-11** `gdal-1.4.4.tar.gz`_ `1.4.4 Release Notes`_ + +.. _`gdal-1.4.4.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.4.4.tar.gz +.. _`1.4.4 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.4.4/gdal/NEWS + + +* **2007-06** `gdal-1.4.2.tar.gz`_ `1.4.2 Release Notes`_ + +.. _`gdal-1.4.2.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.4.2.tar.gz +.. _`1.4.2 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.4.2/gdal/NEWS + + +* **2007-04** `gdal-1.4.1.tar.gz`_ `1.4.1 Release Notes`_ + +.. _`gdal-1.4.1.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.4.1.tar.gz +.. _`1.4.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v1.4.1/gdal/NEWS + + +* **2007-01** `gdal-1.4.0.tar.gz`_ + +.. _`gdal-1.4.0.tar.gz`: http://download.osgeo.org/gdal/old_releases/gdal-1.4.0.tar.gz From bbb0e2170ec85240c3b8187dfd4fd198f2d3bf8a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Jun 2024 19:57:01 +0200 Subject: [PATCH 0180/1119] typos_allowlist.txt: update [ci skip] --- scripts/typos_allowlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/typos_allowlist.txt b/scripts/typos_allowlist.txt index 8708492d5792..22e88ee3635e 100644 --- a/scripts/typos_allowlist.txt +++ b/scripts/typos_allowlist.txt @@ -343,3 +343,4 @@ either 2 or 4 comma separated values. The same rules apply for the source and de *
  • JOIN_STYLE=ROUND/MITRE/BEVEL
  • *
  • MITRE_LIMIT=double
  • DESCRIPTION=Staatliche Verwaltungs- und Sozialdienste wie öffentliche Verwaltung, Katastrophenschutz, Schulen und Krankenhäuser, die von öffentlichen oder privaten Einrichtungen erbracht werden, soweit sie in den Anwendungsbereich der Richtlinie 2007/2/EG fallen. Dieser Datensatz enthält Informationen zu Feuerwehrleitstellen. + # Disable cfchecker validation as it fails with a '(5): co-ordinate variable not monotonic' From 175f743b5cc5cb9ee2dc00577dd28b4283c82855 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Jun 2024 17:17:50 +0200 Subject: [PATCH 0181/1119] ESRIC: properly takes into account minLOD for .tpkx Fixes #10229 --- autotest/gdrivers/data/esric/Usa_lod5.tpkx | Bin 0 -> 388420 bytes autotest/gdrivers/esric.py | 15 +++++++++++++++ frmts/esric/esric_dataset.cpp | 17 ++++++++++------- 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 autotest/gdrivers/data/esric/Usa_lod5.tpkx diff --git a/autotest/gdrivers/data/esric/Usa_lod5.tpkx b/autotest/gdrivers/data/esric/Usa_lod5.tpkx new file mode 100644 index 0000000000000000000000000000000000000000..b51041605c026e800707223752b9f98583df7aa3 GIT binary patch literal 388420 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ⅈ*vcm9g|*?mRmv=^QgiQc-SaZkAjd(@^ZCQ zw4YBIEd=BZ*(0JRna*e)c^&jG_|@XmcW-WEs1w*96Dvi=DjXN(bygNPsmO~Si%PFT zA_UX!-qEz%s#QXO0;f2rC9jNmk6%no3Mvu=SRAlVuu!fQk2QThJMFb~D-DGvkaOZ4 zyKg*|p^{e>X@r7$&IOd0rHARb_CGOn>6Fa@b8Hw zTLfVU$&+WykQAsP)b*)R5>h`UE$G&H;a#Nw>L1yCHw2!K!3Bs=`|y(j!7)0&=H;GK z%b_Ocsu~4E(%!4C(~A+rhC$gy4Ng`&+S0N$y{yC%FMhjet!>NhDkKNw`3-| zp^e)ceJ5|oTOlQ-elE%nC^upg16ln>{6}Yk7Njg}K_KFPc1VATbwZ48v?9fj2WM9` z{xdjTqaHXRVgW0V`QzByN+vi{wK$_V0)=>3HpnfJyC0eJh10s!8BfByUP z-=F{e&y!AtzTJp4Vvyed`XrGY=70Z5BJ}kCc#=57ZpfA`@1$v>E>* zOnj(aH_+Gel1_8EvHn4eNRwzrR<|=S$k#+4owkFFa-O`y8?qFW5bH7i_ww9F!Mzln zG@3onJlM#OHB(RkuLMB$^}mn*KK`F^m}@{)R8*Y5I-ZX8o-EcJ-Ts&@FDq;P`@MSF z{C34E*8-^>H{I*urenw|ZKdp`ZgO&XbKdKyUnZi`Qkp%)pz3-tQC;oL`1uKx%K2xf zqz0iEx??Rg0@aOfJB11EYb#J&v&C%@NrYAS=TWf5nI(+;|jHHX< z6t&Z``EB?CENLX*j^x8n^AGGgVVJlABoQo^SB-2=ddHp|AGz=QnhZkO^4?r7AB|6* zhTzu18iw*`-Hj7gAM`YNxj9L`3TV!Sj#cfAHe4({cU&ALMbz4_?b$r{U4i#8kEE;L zEk2yJr7rV1x-*jgSvh}jx+_0(D>u@BWcApKlQp&^;eX1~%)=D)`cRg_#o3EzP{qf) z%RF{=F1B>kk@ocO>kmuI=W_Rx`3McJi4dc;Nu8}lHyX~rjt}F<^KNgi@!Fds_oUyr z?4XH`2`B$olx&3lzkrhF{{toG`9m~~@VqzRQuY1CM+n%qkw$tyPn%BBsTSl0-s3_q zJ>zwJc}9322TvX!4TD4|>`Th_9^pesq$xt#3F12~M`}5Tk9(JE**L1&`=#zb0N~m?R8h(*pva{P8y!ga#xc6Ao z&|qhDJDR33bkpZ>IC`?M+QQa!*7Bx0nw;cvbTzj7RJWE2r=ek%^!AvbGrYQBXLmF+ zH~Ybh_-sHgd1&WYv+dKG=lRBD^V^Q!5?qBmDf+<+P+5Xx$$_-U`DOG@0jc2CCX12qHb~*I&L3{;%jI z`F};@J^l~$S{w_G|4!6?!TxCa^Mm2vsz@R;iogANTJqxpBI;j?HO0?ov7KSqaOLjM zSA<}h-UT4|1><}Z_R6S`|4LyDYk_$}-7Nc-^c$Qj&?1VtK|o$+pvq2xIH5cRUz= zPLJ3{V_{6^cR%bju1*eiqXNvAtcpXn`U{b)BG^__Zy3$P0HH*ng2K}MVsdNh;2LEQ za0~g*sZV7jgmqTLE;gYXXu4<^fMa*>jKS9E?sl%)SjQ|p!29DR-~Zvj-gR*&qeT>$ zXFF;mN*DZFVe1Z9{jfl|Lwtld+`4Y9=xLd?<-T(ivRf@qn5eGo`?5Eo0vkDImQ-|fpdGeoMOjj=9z zcLU$THLIul8xbnXk+E zDEAHT|0`CyP&Y%~9n;qPSI;fbKt)jS)SpqSt)D^+|Gu0Epql%Gan$>>yM5PsLWmp1 zqyt+&c=}Ng6KMY&|Agq3>U!L%<1fh9oGal|=bQAO|M)uPKE)b(XT{H9C-7WsEV|S} zLhEXX_LVYceT)RZhpp62ff-j(|E&%l|-)3n0Y7ctovxI9TDUje}1o`=O701yxCp)meah`|TjAH4sM@=U9pg9Dw+n%wX^u4m_P zZh7Act|Hf?Hmc)xO>=n=o_vOyV*vU|+siSsJup$PlrlR_Xw`g?rH9ux z{~-88t)7a44hYGE^~)U<*CAXU3i}*C<*Fdot;a2Kt0nM`;42CyAS8~R|8eF1$_(?O z`nMjD9Sd~tk&Y+EC;(8kyv`Pnr4xnaqjYi6lVjY8=(?|s2|R7ifes7*))O-n&QUII zY5yi9#C}8g>&c5GEZ6g{@8KpK^dzfMY*Z>C1>x;5a@#j4zVDwQA*-4K#IHg=>3o|k z$Mp-Bc%bd(W98}bk6~^0T<^)rd8?p`4Isc6(1QIYOLRv(deTVpvfZK7D!sj&#hKs) z8rSx`n*RHF0j!RKjNr$GXZ`(<1&Bib0rjStU8 zj^8F2|9SVQQ7)kK_Vh1{7m1v75lf~J z0st-h@0aA8R_;Ln;3|20y%xC{SHlAKGg{P+M#O!x&Og_Ra<$uzUSsc}l4j;ijW{6y z`|%Zx!MQ<8wdTi#Z$bf1X#l{ZnuLr+I!2RS>;@TI4FwI1iziI|afD$vgN4HQFL)Ri z8v!c<3$LC2KOOnU+SyJ5O7?Bt-Qy$Kzb@@y4eh_AY05tBc>8m?N)XEIevK^c1JOte zR?p=?_SUKg?$yWQfR$emsrAhb%?ycA%Wz)9=0X6&|G_t)56913I$u%j(13Qt|8cI~ zOo~;7RTKb-InEHb5r1@(CXoZ)WvUqPJSIl;_G514=FtV z1AvZfMp$E_kNcR-eY}v!n{Dpp{}-Cc5s%kieuzWx+IleJ&KHLj|E`iNp7A~c&s8XM z0)(K`HZ#F7U}TEo-3>}DUuXUk207K5>AKT{Hh2gic6OyHac$oN?9dW@Zm)@EDrXAt z-@`j2Hn2d($NyN5Xr_%=f)5tP=l_799^nWy7MRA#q8*aYkdQp;Nr*&?=Xn3Q_^{E8 zx8esF>sXo;5Z;PD5kUXFYN%7gA(BgFNK|`i>f&vCyt8!_awU-j*ZQs%-ea8dE0hb0 z0iz&prT9#(0ix121p*i>ia$LXpvfjA5&h4u^2uS4|8|XD#D}2;;kfjFI5YiO!{?u4 z-!K^JUU>iqaBDo!)~)lA5MwJ5F)$c8H`I_Wod`Ao%mb6hmU+;f=q?itH);c@Xml`1aS0&K?s6J^Y0L^+20w_+o;HH%(@?SLrBJff? z6>H_kQ{=~=FR$KMiVS!EltCnTdwkP9)0=AokaoVv3c{#pneOi}VIZyq8F1xjoIq&@ zWA=DU-HqQb$xTQFc*_hfK+qXySAoG5_mc*M@Mof}%=LD4H7EdaWo9UFQJ{-8d_{(6 z;rzNyUH~84%P<%K9F6fl8(wTdZS^q7*1WWN@neCk+3?T!^Nq|ACy1ZFK&GD^O??Xk zbwuyisjsaZ1Y;qROZjhMpiD4GWqh3o{UzkxdnpAA2~n$DJfpq8z2L?EkD4tWTA92a z!0OdCixVGNf*X@&;Hp>VKn6uZJ5y81F#2?$UZ62T+bW~z`=(RoTc@ArO z3l6LoO)!!${IdJ@GPd~04=#`tMh!5zJ4@30XE=8T&BS?eg4L3|V1B&@W`7fbW-%D# z3G=*K_qE0$&pC%1#@d@E&}jCR7JsBw2``?i^9lB&a7bI{s!3B{Ij8+-`#VOdT)fy< z<2G3UXacF>l10Bs^kcEHdyC?;`T`T`+4CQZcFm4Z=S~E*KOQ1Did~aC>L6?p-yAox zeW$x^llzKN$A_evMi|}7t8>c(u8F1CRa{<4&#J2kYEqDQTioqTvS!r0YtUmY=dwVb zy0M1?yAtAdl10sY#_JLq0B;LW$~{b)=r+%&34!&xbKHK2qdR0Ls?f;&_py~G1)ZTU zSjfASySZU1%{psGtv$c(xywOtu8y-qQb-75AT5=2FQjlI+!0z|2N1#@wUsD_yyJ5g zkKgNLAbn|jxUWdgf+S;)zUI0KZ8k=}u& zv8aWWV`U|R^ZHawPHsj5^Y0vw8rI*75FH6nnjJYZS-C=;w?e&5+G>0MLmK6_0~Myb zQkQ2H@c;e4Bfcnr^13Sl8w|!hg<`bqXM8xIAYJYH-x&IG zL2nFrpL`b|b?FRZKVgJoCf`If%u#>`8~$G|?Og00WCRt}beVw+Xi$sj5TV?Q=ag1^ z4~n#1^F^YAoItxV5_G?SE3rjxA=VD&B+h^gJtNi?+Lh!FK8rEwZAf6O;DX(pO_|nQ z_aKVo*vlN)dC{CSo5r#pd(-oz?mYXr7)dVAG^J?A(m{OgaR5+c^F7se`*3ji?J@o6 z{r|_Xg2;wUaoJ;Ef1>wKM@Z_vXRYFF0!yA}(8X@*?V0!Ou`I9~HII|@pK$R9wKP`L z9@6IxpTMu?IRB&)-NVFa(t__nlM||?7y!V43Yza_@8Vm;KdEN=k=x0J)#gX`e`#a- zxn3R!a1)hF({tBXU03IoKA(SZn)(C~GUI{rz(0<7_PRt66KO|-g-{0kj_rzsyZSJR zrhi)1wDV6$Ek_jxJbJbQ1GP=dtv`t2OMs`d_bnKC40H=-FpM? z`7XYwMi}`Y;rvJS9r8FfNXdVe%;_rv``;j~++_ojx)dw_*9o{>?|**+CIkL60Ux*g zbk^8&V1Cp$Fd!F>Buo*DB%|^skQ%HM8;mbu3pc9c&)m-W#?Dok+?#J>o36oAW3@81 zY|!*=amsB|tYOc2e_%T_EQUIMdxga6CgIe!?xG4Z5>@f&MxIWs+>iUV5^r73bd3Ez8i%0 z--Bj$kg(R9Batd+fL+pl!5_l)Pdzd%c?UmzxQ~KA8!l>Mk4hHpcWpEbpSOxBr4#^LVWMV;ow0 z8n3V-YP!D+JO2#|uzX{2%nTpx;$j=(%CzGQ5ePtB5O#x$*4~PRwf=H3r)p}Ak7$>r z-9j?xP0CP0QEy}kw`C6i%fEXb7{BZSfOjzc=)$-7_?=n>XS{sFV#C7qxkjOYk1*+0 ze{b3?tF~K$Lr`Aqgtvh0C=u!+a;X24zVk8E-pDb6k8S&Vz-xv5)Z9&hxkv-hB?)}5)tU29QH0<@zn8* zjRh#;cbLYAYP+6()t*!Ra?XZ491r1q^XQY}-BNAV(ePskz{}sdwFTf%iMZg_t35nC zl6#ZO`7ofGey%)uRp`-4K>L@44q;#m@T(fhU0*oHoUjp&aJDU zC7ZFSM~1}iv>6l8`|HqUS>;ZaBIK_U5h#)Zb0Q81sYy zUJrQ3JlWUmx(Lo%5xU|;lVv?W+hYmgHcSQy`>U|9LI}e5q79!N^$>jU(sa@uXg~PP zCib0_`5Ykum9gbXTDzE2ZM1!+sV(PDQd}5Sh(un%Ztm<&?C#pYnOv2-nk8);6NSEr z3r=@n!jGZZ6)e&~S|4dohvM@Ci~@Vyosvtc>t*4edIu|{`G{!keXL+fvNTK$EJ0k~ z+*lM<)(Gu=(j7aJts#2Y_Y#C|r@ht8E`17`Exx;E{3+uRr<_85s%`qN*~6AGIK&Bac*$$ zrmD+SQ+)`cIREe%9qEU-2zl=iQyx*3GGy?*{Z-;mH)WCVgS* z!=%kNEMO4(Bf=GLk0Ruur^$%uUDcYy*2(gZCac z_Y%v~`yYE!weLbCZ#;Rd803 z*7Z_!v44qMxcw+bvomycMAkc`o&b5HG zuzz|9yJ#E%kQ7fn{pLOEMe^* zgcp_kI58ty8WE_Q>?F3VT}PBhNpEq<0z8rN!t5DTYh`^hh?% zTeLs3czOr}4G5yU`v!ugE>wvWc8SOod2Cjod`O4F8X^{Ab}3=Lz~!XH&h)zuF5-~9 zY8Co=l#%*o51Oy>?9n&WvBUp7(xM=iu!LqXPNQgn1Q*iT=es(0Dl2zc!i&CNr{g=WoJ0%em6X*&UI)4PTo9CS!SoYFm3?7YS^Mk`?~>m%%%| zlT*4zw+cg4?;i(FaS$gI4qJJ9>7K_6#y)ZEPgw6y%1rAZ-iN(aF!mN2|_c-`*2h@Da<7XrFD#UEgDb0r%2P z3C}$O?mb?sO?aEm9{v=5O0C)Iw~XI?I@w`t;Z^0nBJvhwo_XoXShPTA3KEwK3?5bJ zw#5|;8!(w(?9(M8@cV%0H((1}d&=bVC1Q5RXZE{uIrNr&`{GKqVI2m@1hc7psHMeR zzsZQ_4lo1x7S!Fg`b01$Pa(Yn2cJr(w=k1_z1goXSP8t~rOjVGDl&oLObaY%9vk^E zDzO=mmRv}91HxIOPkO85J%g6Zl!wYZFWZVPlLqXHWfi=-yMj0|2?EhPk`ttq?-pgA1fIbAG~d1)B>Sxx!Xdyz1@+0!wW#oRg=hkVh-Y2L6Ygz3Tx^T^g7W` z_{D$Op0Vtr;HiV~)$rV@Ea!Q`>^{fs7)w8v;BQQ=NUT|e85%{WFpIlI(3!E(#L zf2cM+K5Dk z;80VJj*i^IlT;tvm-<_%M&LA8=OYly1%4Va;ZPDtavB*ocKpV^ErSVvKXESw{rShK z5$1c4VYYmV9(c=sX$b=DoZQ&eJD037$QA;ArgiW2;>jcIiGTO(5*AcIDfL6t7QtN; zu_RFaeb-lzvWMC7l3SI)iHd@_!SgHEsVzsQ-2z=uN2rQkh*CVO$irDyT4!w-7nDio zE8Vra(Ojm44JyVOUYq#98vj4aOLx_5Dz?~OmH4oh>3q*1O>_4RHkgvf`;bGgYGM`> zZ6}5TJynBfpdYN;^IcJwlNKxd0J)7B=~|uga9)14`UO{rwAt-BT1QWqSgP0Lt-E6L zp65ex|I~G*k@eUS__>MOQrt&KyRn zidDC(jTesd-*Da!CYllnOY#TAQE0@3voAJYIf{VA^7;i=)d+nGMR{5#@ruXGzXh|l zhUSt-g2!5}(F>|Swv&O4UU(-dIOLBtM7IflJB+Yh2TV1ze{l~n#0fF*j4RF$!3N*^ zv;UA0FCQwQ_RO!P8VQ^eTof&l-lIktFULRh821bz zOA^t1ki@eYo%wzCz9{nCNK1>Dog^vlmX0wnGnP>Mfv&%@i&I&VqT0<(w#$R+UIs)&tlL*z53K4qBTGJ?As~c z?ZvOYu%e>qZ)sSqPp5k}W;|h*cT3`EXkFD_xM$YexLJc4Hjk!kX6M;@;Lu`os<&k;k-K}%hQ5ZMt zrk`ZYw+o>)dv}Ou-<@v~O&XDZ1ao!|5w3}Wq-cb}w_(zvyRJZbb` z)Y8#0{m@wgnmERp975b4a(JZ7TRwqGXT#3!^|u(uuN@IK*JFaoH;wtP*HgMFsAAL4 z@xC@Z(7szkCI^@{4V|u4hZ56&veaC_vDe(~#<=sVw|hGmF`oOnu%2n<{2*Ma6_ZnA z$Ddn=DwZ2onb0zUlOQ{2R!}*?dfS1(*-{;fNuU#kx{en@;t9iwGnOUrHv``Z*yT#bPo7< zkyid|*H0rcZnhQ(T;B<=%f9b(xgvKpIT`VPnjqUn`S|kXVCD#huIzY@v224D8>D?! zwbAVpfSdpYFJF{P)_lhory4TQKftE5@^Yn1F|78RV~qHJjdvrn9ebg*BV#cv2cW8_ z0o*YGrC-5SDDdyq9^KjZ{k=n%>30Qs3SQrpR5&3{g-?dv$dqWJd-{=Q{RY)sF>#yk z>=e3++-}=CWnaZK`ou~5Vl|DN*GmA`<9@o7qtlf-B%nj*gBjKnfYV z%?1Mb~q{+?~KBYU)$ z{$Pa~v3m8rFNCT{hJ8oqC-5}ArSoEQh%P5CIR&YU*+cI*z^$$_%A%w+n$-whef|z5 z+p$yA-gcziK`-7$|0KhHLXFyxK|0S;5B|!gFgUAU2qG~4#4{+n88iO@%U5I9v4l6u zD}i*&9$u>7JuxDh2bh$Drj+)<=vIl_15#onzBegruc+U$V3r?K5U*um9g8Sqh^;t3 zMaNR=6!oH=%eOz)c|of&Itd6xKmFv$pwJs|L&56sHrbkM)SSPcC4@Jl_nUKmtgbJV zj^^>ke+lb$-2C=Y(ITf3-ni~9wgAcl7f1tz=!*2iP%IkOyKoko+?F0?V)Zg#(XN~H zuHV^X@2R&28?htbfyMD}yjD_+vf)`by5?%T%gmYUxfPN;1bhf0SZ_lL-hlS~!>Kfp z-au&-Sw41_+$}w92q_iQwpo}k7Iv7Ka))2>;~UH2@Sg;I!zsq%mLahIR1Cp@I{DMB zY{&;)BUbqIR>zJb{TpOTJK&#&(EHvoc6)QeR`P=^janW&Y=#q0 z=B;ZcJgn`zs1HBM(SPWHIv=F8X!aqz<2gE=jVaTrloA%bjnM+h-8T7y|HiWfbipa>gfE__`i$nF;?tx238 zVDxHG&87njKH|;aS5XMllZA_NZQ7k`cYfc_fCw*^;y3W1mJ*d1X5Bp8a9GY~iQU)5l?hbDX?3?~Q z?ix(GI1_MZEf%6%J4k%>Y#5CB=?0N?Ijl{E0dm>a2rl3E(>#=Fgr(nKM=4aK;lc-VUCmJ;kW$T^%k++V8?VGPsECq&5=(GwGp6B^$| z4&5V!){-QKT5XtKBO_z5!-~9^1_x$bIAM$Bs&mNFo2vnJKO?F$_zrm8P7QW!e;hzgT79O6U|ex2V~{VcthmrSh{> zqI*M`sPd_Pl3$R1#zU4ai4gK}9_${h-i?gX($02?t{*gLu=7=1SJ2a&&`u~#5&Q~3 zriK+Mt1x16)JtIAVROThIsef&=FNuSsXbu7yFCyh74el9*_Iu21J-ytxDEeBG-bsa zU5EcJ3Ffop>q_0mTZe?1XRwaeGl?g=H9CP(_Uo=G-rlCyIR>$3K^qxY6sOAldDfoP zhCugyTW>S9Ii1V~)dtDPU$yxvyzL)gV6F>pH>De`*1@PaPh1&x$V)T`yTJ*G=L!2` z;-E7}Dx#4QgKRGJ-S<2FX0M=C;6dnQ@m`X>m~yfW2^I$=;xE-~ZN#b_7;f*0nSA~a z<9qg}yC{w^{@2ghSI4dKO1bkO`zGt>g@t}ldavTgA*Bgap{Tk%v}Sn*i7j)W?!+an zVZ2}>uRinFzTDb8ha7pWN08ie@qMDXeR`N#A>%~)?9e|(xCYh*<^*9PJkPmxU;0MP zMgjmlF_8A59gZ20O-Eo4e)?Nx`s>1+^VAn9fc%*adsTg(+^&UIeIdvBTmH>D$B&BdSM2v(>MZKy zjMyD{0@Je1AuEEeC<%3rftyh19vXmSwwu`SI@bOs%KiDK(xL7g!0iH}y0UZO1PHZQj;tJ%3P`Teje+Owyv6*2fc>tcQ zprgUuF&Xx$(nkJr!MIuLt#5))01<-nDBqm^(slEwMf<{u;5T*1*V75FXZe@hTaS@! zC+S^)zS&jD`NqZRDJt>}?q)B>u$JJXVBh;Y>H8#fQK!4FlSP&X-`acoJH%sz9H3 z5eC+q#}rFkp70lTk+{X?PK$Ewh|kI2@0|oBQ{;xLA8=Mxj%=A+=_)GV$--Jr&gD6r-)|nW_$s=}j@p0-0-u7;e zUlQnL`sUBC^iShr$>S}S44yZybr)HC133yL3s$_@%Ii4iUYsxRoYQ+k5=uf4UK+M7 zZFc;O+wb}ZWum=U!yo)H%Rtnfqm6|CkSu;TanQBr%}$wL)g0K z!-qcyvz2PS3rR(6s>ki$$klCJwvy$3Gl+>pb_Xg}Vr!ax>h$+N$5)D8)CfX=i0v5Ie473-mv6sa zPoQz)QF0S~q(ij_YZnm9wfmV2=^aol1pJ~)k|Qq}s<@|zZ@6M{UncO~fX<4@y4HImJ)^xi|EUn)cQxx@ z&kt|FBP1T4aUKHM@*!^T_dyETx$yt-yGMx~=Uo+t8>fpF#-u>hsdQYUi1bGoC;t|t zDU{mi^Idjv>?O}9u48JXWru7SohJ~04UL}VzEUsLr;zwZdjlt+-$llRShp#1xJWkWBe9$#gF zih(LAcx1duoq+g8xAeG>3vNZ?}}XrvS<6=B-TFs9Di`C;1dS(s?#_7q)1Y( z#>jI~TS!U*YkXum7)Uv{y^6}WupBd5U*QQu3Nt6!Ne>XV{9*bG8{=0GhE@H6FJwro zU~x#D6mwHTwPF{fa4&MqI?%?*2r(NbCrT%J7H(DYjGlJYIMDdr4Ou+*ph7piv|j;a z+3cv#n&y5-f&mWknEe3^LZ&Y(0mMkiV~Z;kp%5yt&(>vXO}cToqS&LjNo5J&lqj>I z&RJ2Bt+bu>l}5>PHl!av#`NO0PCRAr^(B`Ry`Hgnoc#MyRmX(j$aPmVUtmF>Q!UM@ zw#6&ENBz$aQXjDuH?*zp^Ig$%F9RT-T_Xj6=9WWzfQd1^dzfkmO61?9Pi(BzH8 zE_N`CEl-t|Zzvt@?@-29>9p+R>7r&KA{Er>!66+ir=a5EcWNHy3d+3pQF@yDyl3nM%hPkA%S=3o(@=Ygv!J7}G(48GVSBwrjKnfvTF*!4l z9~S>e##Qh$)tX9gkDc5EcSWNGl}jGXSb-_jmUdV;1vp9=2Nc*zz5!DENldEBGGygM z-lG=%O)|2*vT+Rf#++&uovStL+F&u~*y<(;k)wXdy+e)em~izo#Xa1SH-A*;=D*^# z9=sG+GM`a4=eih!S1uGM{15~TejiHEf5#@gD%Cz({dD>069W-@w(>+ji!2~FDcKIr z*F`pzTUZ*|rGyDhkC}FaUv@)J{oefF8}zfp@X4lPP7GSi&%fa)30RHhkb&d1K?4pr zi^#?9Oi@jU6kb7qJzs*F*I6vF;_Nm6F9O|T31wOLt5E!XuTL5wkRN@^o++}>Kww*j zklMx@G*J6pjT=%uK{^qmemRtsHUE)pSGbZPq=wgn8Ik891AfE+N0M?_JLf)y&kLNYlS z9=}AzPTiY+*;JL}N{f(>HaxKPk?_HXt0Ux6M>FDT5^J435fM{NOY@92zdo{eDMY|5 ztX5KOaudR>TC<-E+wqVg@BiuwmibPYl@Dq$k__r&jr(n4=ydcTLL@6_2kQ{mm3C5C zEs0cLWCFoK&hnXe0&d%xj+YNo({0Wirs1_w5CGqo&L>)n+AW42Y<&pNrcjl`F6E5* zm2+N>S5N)CQ7Nbhv7BVI_6|eAZG!JHGo{ZBkLLHMvAg>G@ZDWn8_{pJ@1yj%n$_H8 z`N8Q~@)Ku8EZJu1(1sc=6vhfFN2uAvdG;J@^VMm|BSdZ|6iZuV_YEm$(u7I%kDYrO z@)dhBJI(5Z(Hc^Lo2Uew<0Pr7FJHcX((2tmoU&Q8yz&X>$u5Pfz7W%?jWcz=%8cH@ z>+URfFp5)(ITY{*|Mfl#p8A_Kv$+1PBg6#GF^?yaz26z`c%!C|X5t6hmq2UCca1c+ zgu`a=c<1dyEbFE9!!82LKte~P3zrG=dGB|EETkYt1ry^$Mns?jdXL4I5z#{Q2z zwGW?nN(%gPbdG#h425G_Fu{3To*T(&#bjV9XvM*RboThWsxl?vb;87PUTEFnYFOM*lt8GWcq^S{Gdqqqw^n=3>(J}+6 zpC8zyxQ4hg0}G@Fi!@CGi_|M;ZHYhpC0A%79Bj842unu~u?0(p$m9A7490k)mI=N# z*B(1&zhTbhRLK`)v@Pgjc*aBA81!(3I&!gWvMhKontCVe+QM!fTAYpBzx`G}pmvly zAjr_M;bz%!Zv7aNlSf$Ift8tw&oL7UppZJ7(`gHToG^>*p=Fif^-(X~H&bL}2z|pb zxDO)J@Np*s+K2wEIHKwn8m=I0>_5lL_hPLw$f0ZScS}I_d1_?W9~6>MIqc zVv3a)SirWDnCd}?!uu zck*&Xp3yb@#bws4yuztBCa8zM&i0XvYv!4KB2Dqxj|?A0W~P~oG6TBbmm;$AG-tvn zWfG`$MdLDJFH+?%7^m%YaJL7iShh;jToch*FuKk^5)x?l6=0gW=goPXste*$#gG-K zSfmAx_v;DZz ze15lRkecY1?ce)VIrTYw3x0eQ>?kmmUhK{7F8#&F@jHn5LZRP~_CS9fbe1zrR6Jrk z8ZkJKr@SgnsS!qunn_DUe(#geRyhRw3Vkd@7+v22?xaAK2(B!8UcQW--0%wt+ga1k zF|A|lG<~fMA9wDETNFr5AY~C*Mjolw6e;WQxsJ1{pF>qVwZNc;88;T3L>XwpxSIOe~XBB6p#4bx-?d$DQ^J_*oUN(U<4=Do81#L>KP^uuUWx#UyL8;S%K zan1HB^^RT>U&O!}$(p1d#VG(iK*GO>M;il~tfWwt1OH-a5MqBBR&|b;r3q4=PR@iv zfxICf51rW`%QZM~V?fwYETeGvoxZ;peh4!7K!B!m}Aww^w0#nT$n?cDK7$b$e7_ z#ZSJakoyhyco%%clH~V9UGdIonb(TW21Va9C^ni$>8N5@aV>e_&|_>8Ei0I$zqRVi zOYZ4Yqf0r8i-jfkW`@pfOH}dtYr^$RaDl+(ch|z>w0LFvOYxIvFR^IoEDeIsHYQ3W zc3a9oxBKlI&}M3;WfgxOROKu4yp;>J5t<-+)QX457{!p^MWA#aFN2+tLZ%5tSJ}Cm z^d0W9#Z5>2#80h&obr+oSPv6?c=8GG={DgfEt_(EADaJu6k&W7Lhe0WXHBloO-#EB zYr_ZG_lfmY=lV*JCJOX}e%+xwNiHd{`7IMTo8lPXAxaHQ0U^-n(3Wz6=VE=Jw&BF;0lH?y-dwOzKyk zbX)#_`YYVz^6}|=6AEOx&T%`I09)c4j>XW#*al~qj`R`mUq9Kt-@n3$*P_-(Zx47o zY+zrHeZtTq*;Mm0EJl~bnY4q-;&_XH2aX1RnN=2uyca$I(r2FIX@8Pg(iUDswXzMq zm54Kiz0j)g5i+FHTIX6J5Ba_ zV&6v&c_0^}rxDr!x5>2nyGDk6+b`BDbf2(v+%F#2%YgSUaSGfCI|29lvc}t?Q@;Xv?u0N)hhR!WoIcs>=&a#xZZ&gOraeYH zGOx`c0dZz)0`Aog=iEQ^cdi~nZ(Ds`#xE|m{mEdLo<4YE%BB7>CPTi zbhzFI+&|&h8}F37&ItS4-j>eFj*HXY>G}3xyyP+@FTr%4E|8yHQkNB z*iyW7?S-Y}dSP&p5p!RBFRV%UIMdBBIDW`Mn00!#4O6u;X0$+o)2wPP`I#Liio)^5 z7In2rKooqn5%7Bm_kBdg{O#eGknFRUY%ML`9z|5FpnJDP`!KK9r%*Pjl~(1X)V=Ay&J+M z72)gg+;_?ak?z7-jQ2Bc4|>H%GDLer-i5s^%l#HY;|+QKsch{Zm%RJ0-9;q~8?jI8 zNPpEiB3GUWcKjJyIunC35-PNZx;)V&T6LF zh8B6TUymJ;`~6bi;QFm&4y4`Bxf(=flSA8e1(XC{`kq1<@PXNo63f4bW%L`dRaj2i zM;!VpD_?nraUJ(=teqlB11;MX3c9zu_!Bj4M6oC$5xIj;`!$gLj!4A&qPDmmlCFJ! z+NGr%i$;DRYxVNB;n6)gRYOmm=WWV4fFYY`-+2LR2Lc3uj)_|mtc}jc%z@XMl-M16 z$^2p+`k=F&PY&?^=J%fnInqdMIQEoE=LbKA0Opf@6V{`r=uc21TFWp; zzq4JqA@u*2h&&f$+wZZ|!b>)CBgi*!h<7yTSF16o6EX=SGG#YwVXm60r6 zCaobhlidBEP{ckKzu$rs@p`p|JmFSmg#*@A49E|Yer`s9Q1YK&&E1So$4m^~2rrJ9 zh>*$ooH}GvU|WZl>^?(~^dj(Nfw0Nqz5qYhEGIcpt<8t?ds{3;l)nD3d@J=t5SFaV z#LRIl4Iy*jdZ^+nj&GGE@|^k@i@Tz=AzNr|8XTGQLmd~b=7c{g88{E|qt?38j6JDL zGY3^^w+0p}>x~@5=*9*6y!+MMarJpD0z%pm_%Y;7hM&fqBI^&D2-+uo_F97ByM5B0 zlm%q5=vkf$%>OVXkKdxKnw_xFLX4%5KR43lW!@F#ssRzYPwfdWmpcl5jPOBz3HB(m#BTaO|`S*xw z)PTwf2$Ba_EOyWrl`uaZ`emkRw;0;NCa`PX%x#plp@wI#7qu}rYjdsy51Gm8pKdbW z%k0h>#iL~j@W-OmMr)C-w-Lq>>!dpYA=8yA^kNLNnV#3FS>*-?D`=%d{th$|*_P&5 zSVNm{XHRAmdrio&6HwC9)bJwChe?!`Rsm%#B6^%^>#UlcUq`-84|87M{gN4*HQUd^9 ze-=H-@3LY8>ruunY(=7|35Jv)nW4eb$ke0C^B2dkHZMDj!M^vY9(u z+uOzj9fa!7$=KB>Vb+9yCfpCm|5Y{*n*2LB^qYfBJ5G;`u(j`A#AT0Wcvg+U69H|o zK;YLJa*i3;v!w`1;^Kld)ldmdAc-7)q{!b6isDpT>THX65hm~TdxZlpRm-Wq2=@cU zS5AnS)KYPL!G$&`NdknlBHVv39ojgf--a`%tUaOQt;= zeHgB@^- zV(FjtO3#7PCZ6$vKX4{5BbO=vm5>V@X2yaWQGcFGCG5}yw*eVWJn{#BdLQ6O-hFts z6lIt3+Kic>e}f&4hX_p`sW-A5!$%nZ&Yd?O{av`j5ZzzX@Zwu)liE5YV;*{eLzn#5 zLn~eLMfe$WiahDWiWmayV#Yx6X(W2!pcYD@!^wr$+JzUZ#-Kp?4c&Z2Mr_8v@PP4C ztYZ24{@aC4NMr9KX5YF&V(8N|INEb?b171cKeV6;AK7+r@!>^Kqy8Yqm)Tof`pcN=U3R&QE%CLQ5D^Mm8 z?LRk>I-~gr&M3^s^o*GUT*M;%X9jpFUSxJor0-wRNrXN*-?(Znv!zQ?r+80afeEue z|NMBs+Wi!eua1#qIg$vkz$!yaSX$7qhpWqv5^2T<5dUISin@7^nD!X@H^@9ukZIUyNQHN{r3xHb$`K**O}qX$StP1{civ=KLQI8sGftp|)uq>j6>WZR z^1gxA0#&GkD|5*n4`C6&XOSWB-^RAAV25?4`I^D7Vr}tYw2Jlz1%xIWmlG*J>ZOdu zSIpN{#nNX1LhrNlDZFyxu5J%OaYoHw{hCY#4t;vtCMC^PO!@3&b9Xwc;q6L<l?lzCNJLH9~!n-JY_&f)oyI=v|cc0&rF$)$~nc{E; zJbc4UqLq(CKqkS{vBBQsav61X218O3(Q)nNF7G_Dc1x0S*BadP`z&B$7DeU7;p<#@ z2iUxb;h$@kyiHV;jfqBQ@@O>Se9dz8UHvMdfZkJ@vEvM_^Z4= z-FjOU@t{#JX1~gb$;eVZ#?%XfVTEX~4V7z=p{na$_S3m=%4>JNZdJ-JS)Cy>$R9i% zc4fGaspUf22O`d$q2tcC#2(Z6aJeZ~^7d$ap$13c+0~B7HB+T90b2=6Yr?VF8&6Ib zUkk*bKMO*G*Z~qJd~pk25wR%P=bgAt!W~@l4ZB!B-~@hK|K7V=YJAw5O>JLZhx(~9 zxD)<0oQcEocdy~EqKQ9OXDrzI7~J|8MT~dRh}Cg@tP@ma7$$cNg3$D&B1$W$;`h{` zK7>I$d;bolE)!o~{k>f1fHJum{uv zbKV+*@iJc2ncMWhXbcKB^Ixxzk84782dr43)$7>vg0?KwGXh_`4bQ0!_b8@H0SVu- zKZE-Kuqp&uYpLYW;-56-r9RGX2qoLejokP{;3!S@`ui8L7Gy&hM^hX~5v*vlu%RM) zGHQ(a-RY$Q2L<@KH5BhN(t=b-f!7GZ-kv9QL>cN}7>OyQKfbn?0Id=FAS@x@o^8M{ zJ72edRUP|**4-tD0wC)T)H^{aIFt|`;3{eTi)pJI6qLu1xb%t{I>ZG{qeA7s^~ zuyc9*1%C~5OI2j+M?b@gk8jL7j5>&^w+r$27hgoK(CD{6(O5qOO{GQNfn7N1h_J{R7uBlaRNXN}^o22g+fhZTXtcwefp zC7)m7gP|l>7UKv$E_p#J>-;LyrnNx5-%+w|Np1OGNy# zS@!$dLcGx^o8N+Z!u>m_tf!FN$R0S1f8Wob1>@iRFGy<-h*<*Ed#h}zijw|ovrMLh z8B6wDXC}WWAG`hTV#xzl#ZbOrh;fuRZNqgA(J(f<6Is)xiB}Is&t^)84c5>Imudv{ zztevCc?NJ%y%8(PkZ$)#Z}Fobjv?T|v2-Y^L=40+bZ`-a>C-h6NT@k`pQrABW2FOC z>`bUqyP{HMeD}eX7II*zArv*E)3C!FgEBI3iR_p-S2wnj9aiaYoA1J}2qV7F9b8D{ zWn#;}Qy47$2R@{)(TaZb#`CCri1-Tj=SG`3$n5xP849BEUc{O`lUgrvQ*a%H_Y($K zN01sV>;n}Pjl>1FHTrvPBptCE@C2V}Fw7s5Q|7|<9fdX^_9g3MBO(y&cnL}YX9Z|Y`-IB7qgS6|HgY!>U!EnaImoF~ics`svI~+3 z#Ve$m;j|7(`KRau0lhHjwoFf38AcPDHfVobqvz5Xh1Lz{Y{H*kh}B zT%l@_s9cYQ^}LBddq3h@0uQq-)~!@5C+PYu`qQR)h2tH(MYpA>-48hQkb>$mRImsg zhS2Hf()`XYZ~yjgkT$4X&MexMshG&9M;h7_UZN)0QQ=8_Zkq6o1fx(fy?(Y70`hx&cY}$t+Imyf-zA40k(CdA1+wW_ zmqS<`S#PL(O)Sg5GY}lXDa&iv6Xmj3-6i*bkFL4(h;M_7F zoXg4RhsfQoEY*avPVYKyfAi&mNT_m$c!WMgcRxrf3o#~_^5&@f>9O2gw$XF_4_B=& zW7aRtBb>NtdOw6c?l}fZv?1XPi0+u&93U57G8&C6x6ONl0Y4%NPC{rbb>I-7{dAY3 zS-6Kvn~&yGdKjfS&++fgXNs}7W)^R685&50?Y=e|o%(JTv9d{ZHaXVOEL-GE8}?Wf zWhE;fQIiaG6|g( z?KN_2|8hqd8DtGClnIk@A<(;x24ojMyd_A^aH-$u{d-mU;?`}E+W?V)_VtV_9yt#9 z>p=p!jif=J?2}PxTGGzWR2yFFX;-~M6R;{||t14)t?f?D}2Xh$jznLgsPO^cZ zxSHy{-3V+h`%eb!Z9i59HX1s(k$Lzf-knJ@bk{s95cET-K#Z6I=&(Vsgmw|F^w15; zj`X@r4_|!?7X4~yyp%5ebmdWn${cy+3ID7C^-y6~)Lo!66y? zUwj>z@cfEM?G>%Swod*BCS2`H&J#a}%ek{Xe}GV_MpAWQaQ4l*34cKartKT++aFCD z4-{?6f{OYmjUFGxPrj4Ky%Mi;cjrUKA&Nw!u#Js-bAjSWS5SXv{WG#DP7;Rg$~Z~L z^VT$QL&p6IDb>y%@LjI#E=`C9>nSQ-K5HlOe~mR;krJHx9-~H>6psYIMy7SCN^P83 zLOAr`5j~-okxB4=rB8f?T_Lskn_Pq!>E$|nmgxl)YYt?-IV`q-YP?(Jca8TUmH&B@ zTUwDmHxA{~aLSjp{1hax-_@2ppynFcPfJ4}4u%TUUi&SBpgm3L@Xgl@+JIkkeCe8 zUFbOt8MW&yd0#Px3G}H?OQ&q^kc7Xh2!fNdW*;fjqxwTeVv%oibObCSU4w#Y`FLCs zT74w!Wsww_uP=44>>QW>XnXe_#n&PGwJ?36q%23|DhCE60aDs;FCKIhV8g7G^QlCa z0$N|IB$0_#zMQsd9{GI5U}R{31<2WZ;;toS{K{-Z)Hr{GwCTF^-K0~I+F1CA+7A($ zZs^@hM|UWHp-*HQ#3fG<^t+BxtiDU7*)VK(OL82u3R-MO@3^AF3TQc{cdY5^f&&Qm zW#%kgY}hx47$F7HSwjg&tslfUs#LoXAl;!AM`t&?EhSnh51@~JO4?=xjU@!;l70Du zno@+XG+*f~L$(bJCi*Ek%jnt~{d}ZCbOgY`GbM^I1{IW1mtQc88drk}g&w!{n8jX@ zr+YW5?sg?!zN+n1=89~t1>m`~=pFsP|1N;(OKS?eY>mhkFRBu*qd?SlL_+>S>G%m&w0O-ELH(f+hw*x#ri7snTjo&v$?mXj0|Ic-z%yy1B=iZf z+$TmsxJcAo<+}mc%I);886x503=qsXTr;i(?yJ$7$)k1Jj3Aer{{^9ve*ZVvf34EI zWOI2e*-?A_*8;#l81OmOLY8!?NU3oz%ryNg67#-N1@(OS4Uat5R+wLFI9SG?*oSdW z-&fR-^ExXbc*~1B9{zihZsVv#Z~H$u6>!1WY4 zD{iy#U10^qD(hc=s~_|qO!drMZQkxYeW&oFZG7Uu)goXtL0YeKFkT6u zam@AKit@qg#LDR^!-Hak{beR3{=_S;l}!YhoFqg!g#*=mJV)<0IsBa6iMmR+%^HoD z`-?HU=^`KN-}70>1aTE$zX8I}><%h2PrpNX6{YslvPAHPy~N{*6k^6vE)x?^#|4o_ zPzx^tj&>Y^7LTTLYZqomwDaB8%nuYFOS1M5d1Z%&_TC8D*ur-VWFoZfh`kLa_@tdrviaIQ_Bu;LX7SV+im^RicddkB5N@iQ^UWo0t)LW zy2KQt?8#oiKq2hx$nOT6=CT@U&+j~W5e_NuY#29+^ z)X@seJ{=6oDj2&U`ycuRE5fb?1f`vl>>16u{0+1IgH5ZSb$8gKH6!M>1t|t}Pz`(DDBfcV7f>Q{SkP-+dEuT~v&S8&JDwTY;%NgDkUvT#JbG&N-zQIrj<0 zFDc-IHpS1b$1h7)>8BymyE`kwj$&KY0Q?i0knnPt zNM->s)FGby@4+3p7dp6p0l~Sesznlkp9h4{^<-RO(vb{cp?M?yh+|>!PoxF94LPnk zEw??iS5Y%;R}ic*P})9YiEaA@YQKM{c?^@t8?V(3MDcBPCt*zA1fFi>L14h zbo+#>*vI0`6ajery*(P35RQGx8yVVVt3Yb(9Unf9-vgs@F6jwQ(CDgJDC9jN$0ip& zITJX(|0GzuvMLjq!b+oZq?CKbbf-)vK2OMWfw)ZX?jiTDB+ZP-}|h`F3c`%5zClbhM<` zcAr;qI3^RmKJr$B2KdbtA1z( zz^;G5!;uTRvIP`EY?!Ixe;)V(+E5vMZDu=;~2#6 z+1U2aO_7C)>tri;Ja1(_4ULO1B75o)yBr4pL#emi`kL% z+=m|F_E20;ESoP{#&nMdc|nuqwZ^2w@aF_+iDX~TlB)DYm)8ews;CHhyk*@#A)zQh z{**L;%{TuY>LA<;V=E)Esbmp2U{b<;^Qp|d2@R0*df}OE^MKlTq=_8N_(@6tlUo07 z7qJ}sV~9itllW#Sy1`A#(MS=hSZpx6mXo&qUp{og*Ew?!?8*uP^Awano|3^Nf4<{@ zy=**fkQ3XQCAuKy`|n-z_i!Q(Uc)-1{@j56G_)bwKjY3*y}I%rU$IMSB_v;sL`_MA z=_AL&o=^W;q1&#J0iHsLDBJdsjpq~vytl#Qw0~dkZ7PN(rq*O>{N|q9!%P{_1Q@rjX|@edMj#t#311eah)}X62Y!9fQQXF;@FNI z$=HLXoOq*P;6Qje!CgynLezwxE055h<@es$Vqvt#37W6ilID6jDi;EaB`m~>T1(?g zq=ErCI02%6q+rv$qjh|_rb+ye0vO86Wln_7ApB8)?lyx^YHat%WoZr*pBBG?h&9kP z-n37%MS&Xlt+~Vt&cJJ$aT2y4IrKyv{tSpl9VizN%?XvaC|jcneaZQ8O$t^Wp1>=l z{mqH9$zFoZhLabXhd(Ld*#iY~`XigY!w>V9c}^ROQ=)fcRDy(ag79ObEJ#ZM0k2i= zxijvnewn`|+UsRSPf3CNTSLC^(LTKTz#kK?3%NoK(TUybAB%Aabh(ED8;H_C-M7dU zx?tDh!p4JOOu2FmKSN~${B;7Zmy=4wGR4q+W$xIq9d+-@wAMvXmv1teRNMB1 z-$T=2`+VJVJib?u4{8&MVhM>XT}14!QAfq>J#65rRrwR;hqr=A zUz#P!b`~&A=s!rXT#;f=Ij_hsmzguBP!_*(RCGh&0_F5EQiyyrQQ#;qWTq8Zk8-Ya zB=aOb)Y~r2G(~sR+wMvb9|k3Phd+Ww&ctt0igXzNJ`)?y`@By1lX{0g2aHv~2P%ji zbzIZrRyfzqy7g&>j@gs#GmTv);ZGhA^d{JiufSx^RF?$qyGYwF2r?G&f3qV;of=V( z3?@(Cs*I9nm_!T0r{plHDuX_I+N-~ZKoAPCp?Gw8ZPp?E`N^k;3zN*SBrK#{AZ&24grj}E-Fe^f{6WE@Art#tA zc@Ij@f+QV?kHwc~auk^6@zPioiTz5uIc6#Aod+mllTWgB(uGfjDGGG6+CJ&nr8S?~1Av^>#+a)N~R^Hh3S zgTeC<3RuCzuRmytNcm8|?1?G0C|oyC!U-u0bRyCAn6TCs!GVo@=zL&9$Lphso=ijM z%qm%E57@rPkr&lIpr1#e#oP0lP<4`X70c7WsF8TD;{jDA8Qy@(_K6u{6}HwiBqOJb zE?L;0Nb1RnPT2G;r^bn!7pSB)<(SuhpX{i|vy6O`{bd!kbcu4CHeMv`)ff8YH$|jF ziMXVb&ZxnFXM*27B^;SUf4Zyerc&0SW50(e_h>zT#W9hPKu%n7Yl6+_73E(JmT%fr zR(;gBgyWOc!dmy?P_+Ep>bTQppV4z|-&WoQ{ES7yI(oJI_p5C#u?k**OF;-LB^(AI`~)^fZqcR1d|NIFuBn=2TQ*ajXy+su|@bnc7 zMDo$s8uDS1^e2+b%Y0MFlqaLA%5q3Xr8qQ3B$?Af24X%=&;z)H->t#{cG{&r|NdQkDwyTwWw zo#B2M>bcUFMn1NW>(4Jg`8$Vw%gmO0_^d3_T#Lkey!(`fClV7!qSzgP;D5b;bX zO2YBfvR-CG%{6A4|IKop(i=uO)ryv?M2r!~6ClU=R@72|=;qSlNxY6?H_8h+J4dFe zD4kqU_(*7?$_p{ppsklXH%YCRo^G;TSf0O&GMPyfttpKWyvSyaZGpeWQpYy&Dl7-V zFcw{W{H0Ma-~4mM#T6)|$nJC?^DQw4f2FW)z-4sONr?(gYx>%fZSB!%;5|(EG2z^N zC^$TnFzIYInKGDqAEloXVW$ozM)RUAaH}jr&tV7GKSww3#ThIjSj!ivPc>z~vEP0- zBtrd&A^GeMe+EY?Dm}wrZ+)dA)t7$77c>*6qq%N!N{0WZ_AYpkSFQvpBX};8?8B=3 zh-t+PZ#99L(h4d#PaM=t3qUOLl8K>zxg&JXOG7#j5>rRB7MA*wkUK00&%EJP=j7OC zp?QGs#lFI^jFKN1-7xBAcWa<0?ix=Yd%Zpm-fy7 zi?O#3ild9#2YYaLcMIss+ZP-w$JnPTRke7(C$YWL-4QVvpRFR7G z0~Nzb2#IreL^nbH_CcbQi+udgdr#_Ul3yfP!WX&!0))Rex?>(YpuD;MSQ?zw=89j! zLKroqV@lZ+zGWaI-54nzTzw+#X|{yFNi?brxt!g$e=*q?b2brW`kqGE9mZ3h~Ol-q0QOOS2#V+vtaINeXn1&Y7wQg&mRWh zFLS@jZBwB1bzN7JyH!JbiC&uKqR+=fYO1VMnSLYBVX3_kf^l@TyUnA}y$7@LFF8ZMHW)cE=a#;)qWT zCH6d17dy$J(P4Ibbh5tiGFmsoucR;r_BrZ52-PE6ehd&Gk&}(F3xmcEmK>4w=F4Ol7}F*fAx5Y>c#0fzH5Q$`q~@WQT^CEOKO3=mux+a}PLpw|c|1$vWE*lk$jn!Gpiadj z!<#SoZE-yv+deX=iA3pI`&m>24fd6&JS)m_aD^4|TGp_4Nagq^&Cw^wE!1>@;Q@VU zpSKuKm$9&#Okmc=P1~E5Ru$i67j5po@F7Zkal*`>_jN~;PAcx}PVpa$c5`E_4?Gub z$Ex|=Y%5zHJ=~pL81l2jlWOIGPu&fkC9I*{^SAsIk4RY*rV+ezyK90PfzW0{e^RrX zwu)T!UNN5{jhlr#ZhvV5HnibUj&F){rYp*uux_x%o)jW8d(@pq{muKw!|Fl4ZUStZ z|Mc!&eiHAs-9v9Lx1G4|QEx)4U4xbqA2dIj51X@)2Kv_0Rs@!Djj;&io+@ys^3%$j z#xvv}!P&oX3TFw;jIga<>vF$C33s@!GNQ>4%+~C;@A?5}ca>bG7>fD>f);W7**332 z7=zyBH6ie492P4NMcZ4=GB71kkv7vPV5_Iux{#?4Oo?>K@$}MEJ`sNnfn9}lvM*Mf z0w3e6ag(#FQdW+`$Kz#4r20HZHxsSA$X)Es4ZRSiiWJOQjI!*R*sqdX}pW+V5-j@AU)>KRg~H5CER+sCXqxJ?as@ zwEgysBsLKqcQZW)ZFVf1JVFKX=hiZvk{{x^f#jNbo>m&!lyOdzb{+RCbnPlZJ zim&mxk$aVJ>w($%#q04x-9?QaZ|!XFu&fD^3{$*?u=xEnIhWXouAZuTq^y+YzAJRHiqt38WOGA28ORe;Z2wQ^fPD+i zqJpS?G?FC<>`R-(Hs;-JdSNS3bZ&UPuL_1TGYOs}baTFbj1Zn@Ui}3mo;Dje3>Vv&; zRsfFpp7Dx0x;h&`+Yh4Htr6;?2oX`dom?}oSdeQst%R|ND}|3rQWa@ zsk6SL3B+U)!8bX`QEiVZBn*0_6lEHOiJu!0F((Eo{_5v@CA?m3qWLSqk6t(b2$!w? zhn~H+*mhTOK{a)yuyfvRK>$cy3CPM2$T29-s<-5;pILneec(VhwvW#|LtNnxLgT%AQglK|x@ zzS#MRi({|%_x;_9@shgr_Z0^eJ1Rup*)@dv!i~_O^Vc7(#3vP8iJw{s!(o%;W}?Ua z-5@r~pCqk6{jxCRaYYhsF9^@{{lYh(x*D!?RAjiju=FqD1 zsbtfz6HOpD^y2hgG8wlEa&lMV9=UoIT_AU8sY9!R(3cCdNtLyw8}_(43u)H3ouqbx z$q6d2nc;g0E2jMNk0qx^)|*EpQp0G9c((s4<78grnsz~Q*i9Mrw8Hj88e_kjkhCVL zE}9aC3JrOWap*A(3o662+UA^VDILR4$oDlkf9vk4zTpA>zn@#*LAf#%q#{yprJExh(p#e{Uk+YoQ`5YrF6!y%hVL&x@FFRNon-bnleRcF$XK>4E#>e z;uwRW-|Y0WJW#}m*&@y;9^E2L)T^U;ou*5^otpBTPwT=|L`utd+_sJ=!WqFZRGGRz z5{JG{#*f5c)S_QA$oPa? z6STrKL?z(Bhb)hITnKxCqKC#9xCBYDe#t0+#7(p;mVU?|)Cx^3I4fjv3=t_t9z@cc zlJC-*xI|P%&}8Tm7#wor~5J-a5QRs|HIg{^2}A#|JBs$!=UCEJ~lCbn{QdI zs|cEZT;Ei0jqjV^--SCktsP@G((wm<_zPx9R#6FxK=d`C_1Tu%zR_Rbz>fVwTJI}5 z=cdnWnPS00&snbrErjPH$n7)$81E^qY_zf47w?x&Xi1Hf2BWlKG;DhUui9na+;`n4 zTl};9gxA^Wz_#P4f~hqbyLViD-|P6i-Npj89<6)L@q&@ge;37fT(>!5@8^bFhc_3V zH(xz4#o6W4EX6hFd1eV^r3!(N?V(BT9L20BQs zBDbaa0J`MnQ@TjZP*9%*eNd$u$|v9Xlz0kR#6fuRlmZG_bU0wnH>F*yJDMry%)RLY z!j9JD5N3#N!?LIKiSS&hAI(old|Y<+#gI>|!HU{GIJo=^5ac?9I+CLRh=k#VruAOtax3R z2ZLwrU<72V34-g9ayI5{rw!kqc$MGe>1CumXcM-Z>nM`n8i!Ss|cr)-Ss`?{+buC8Q4x?o?6xmHa&oA_@DIT7DP*P7De%C?e0TGZAp6WSvBLVpP8d zfcbj6mafKz_u#=|dT*vc-`Q}1+FwcuewaC>zLXC6MRm}&AqEp-LwDhrD#ouuy2?Ji zZ!{Zuf#iyMFjU@jzY^y)rFWa_b#v9qeC$4l_`~9!v zK)}QD{RtyV`JIWEU_;RJT(tq(Dv8WzAD;jwr&+a9Xc`v#U3r8e0cCA~jl3)LKH#YL z^7x#v>vmt}lTn*9#!IsJDso zp^zQ+V6I7|15E-2x(tqDUzPgev$Ojy5=2)oc7IvxM7+z7Z2A=Dx5oc|FbKJnapXZL zm*ikp9feO`dAV?0J9?SYpG^xwEJAO-0qHMg4ML3RcYO`*=XIW+#G)hM-KIiAd5!qR zInW1}>N$}jvcDf~=T9EkF})R-m~-A0*xv4K^hUh+)w`*o>jg2m1^ui`jyQ(2qY?bm zi}^DX1kdLXgROXjA$HWl4gTcYQ1u47FW3HWz)x%@?TOjL*KS?bfkFF6{0qd*U_oe1 zk3qDZ#CWX4YecKv3=lBR6j;>z=QJ+ooAewa)A>uzHyB8uY&^MqI}*1#63cFHhk}rf zDRf4g(hxm1g@3a-Tlu@TG|L;KPUHbA8Me;TH0_>PD-Trd2Nvrm@92qYyNOn@$`b$2 zM>f<`{Qr9FA{6NVe(WMN6*VjcB|Hf!ysR7oF$oeOA(Dh7l9dfI0RajwE^2HXny?63 zS_ZnY2}WH#4kss0a0t%tdA!fs_&4{2--?Oo=}B|*$OeZf9UQ55_CJV=)32;C{aIq3 zUu3DNWt*MjEG^^yInL|p$u~42=;S2W+b7sJAl%g>@}o=C#zyoVv=rBvVLr= zer(*=s3^n8NTcv@;z>6l9k0U>+D~;qPw|xoi=&ZV~g>QY+K) zbEc(srlqg1rJtW==&)tPyk*3qm3N?(Pl%OYj8#yFRdBCW$e>l|s8!g6wX>hKmzTA- zw>7xU8r)$W)M*{uZ(|Z+1pGWZR4A1<6C0mS7GB{V-rws>+Wvr;bH5QVe1Wf zW!ZXX+j{5Ndgt2uWZL@V+xisR`jy)`c-uL8+Bv(~xq8~UL0;Z=?%{UsQFb1Yb{?^I z9&vV_$#!1p_SQc3wqEwO-u8AL_I953F7Ea&9`-JfH(vHG-uAA(_OAZ+u0i&0A@*)z z4i0Xmouv5xFVF8F)B4}%ckJgS6wt_N_4&H>&8owV!a1MToK$H0ZZ}ph`F!(CZYTTc zELcODAu|c(Dk=E_+BIp174~SX0C&Koy1F+KQ8NZTXnzs=@#AFFS8IVwuL@Amav-~`9-A)_fIU^ivZrpLV!<5i0A>%cM#Q7pT|}eXsAL$NmGx40jMXBm;iLd7tDX%nBW85xS@O? z$`0N8BFT_*C^$uS@CwVivEbt`3c*ep07Hw)TPj&sR*xRskCv@l;>--{yENbjz@qV8 z6_0p;&)RRrZ$Ojp*+732=)_ohIaBNR;dZ>>Q}Xw(v0xah>AW zG}qteEB5yhNNOmn z^ISftd8`rrO-i84z9dl#py;Z*s9xY6lT)k3MN=XHcCg@|=?t7ylPRkK3Ta;jtMp|N z8|cfderGRaE4V%*;9s*;61Ok7BBQ}9qO)3xD<;H{IBX~fzC8UKF`dQ{s@eM)3 zh1QMRw!CB)c8(5?Xc2>cYV)6;3ZyAOYNkrOG9SE6sai8o19WhS^&_-m3i=nT zDEN~_hmAFwEYUrMYqR>SBq(uNd)9!?(AZBb>Wc^oQAIGftzD8peNd*^AZ7K2nY``g z2y4q;bytW2KWRCQ?~_MbSn6j2$(tdTTXWjdJE#tuv-AEecvm!X*%oS)!k>g=pDf}w z=W)n3ko{l6N~(?!-Lk!F)nQ|23wJbS9T3f+m@OTiFG|3`A5Y3W0|WD$znpl%wj>ZOr@t9> z3hVTV#z@dv!5R(VSy|%eynhm`DM|Tl1nsEMIE$b>u?picI_b8y+V9dL3~a2U>kxYJ zq39Hxm!udw3^5ZNS+c@s8^WVlwUBS>$i+66g6bngi|%}MZ5j^_r_V~V8VvvV&bXD} z+C|a!u_?nn05AXrE!y2D#vG*?@9&w%7+)`g{w(u(1$mcJ)@C6Ee+k9mhRP)#CL*3@P6~ zq+JKF9@Q3@*?_`L%fP)hdi8Nd&cbr(*p#KIaP3v*O(pMtEKjlpbY`U4<}DXRqLW5| zU6~Snxkt|H5pG1W4##T0O|sc8j03EZS`e!>Z8&2MNN5yZ|1%N=AxDdpShJqN)4@Sy z{=0?bN(kMXNZM-~qbV5q(7jCoJaew#jZgCc^#h+iq&E9r{QP(S zhIG^t(71rDz`h-1LA|y?fKu}}zl1XXxv`&4rvl7~0VFtf(gJtWzJag!f;EWEzPjH_ zZ=lZc?1$0J+Pw?qVU5`O=FpEu%RrZZH_zw9)CEh1zXL{OT~uSjn`%38P$ZrD<5{jN zP_LE+#NdFOH|tl86-QX;t!cIJ>N8j+2gFJsROxmeKCz1&21sCtMz4WBloI-^vn_? zM{PX;NW8H~Fu7$_P3?%xD3Z2SV$cR+q}S0z+Qj<)P|dXdl)TT%{U)RP5svb52}JmU z!LmI}Z!e~YL9NXnF93k_dC zSW%Ra^8Jm9W0g7d+^58x?5D99v;``{(~c9O#f-LQ&TzpM*i89sf?6JEqqW8hB&yR;>s83cOZ}yuGbf8U;`@BY9K18o_5KGQDORqggjaqm3q5v%Z z^CnU#Ha7P{we&t~X?2Q{&md#-??`=MnX(%6vG(6LAHWXfpMb;90Dl6McscQ?m<52~ z2;C0^rx`VH8WqC^e!zikkFtQ8^U2X8Kff2`gexW4+hoO zEuzd^eV%@pQUgkS0d*cl89+qG=1nn50ARo@=?yc?@D#L_WUqMTe~l^_0AXhDSI#P^wQD=rajRy0*4w(?=8pXB}94Df(FG%O3d{p`s5uhK>i*u+AL4#hjA>q4k z|61DEo#isdd36z=*pRqx8L@VhsjF_WFZsPYC#|v@3mCNfm+j8JERKswbEm4S!&l$K z@{0?PxuVE}o(aVM(*rDr>;(n?Fxh}gF@uu~Wa{I9Q|%;?KVq*l{p?A@HTWf$fhjLb zk0lfgn|4yvng@P0`wRungZKFph94Q`loC39n>MYRf9}E@z8uU5l%O)B6zZ$iwYr_w^&+i;7aSsW$rcdUZX60DZD@1@1FM=q1w9oE)h+w9AoTH9-2;iRLI(-)P?M z{oj<0zU@RJ3=1SHpn}l$O=W3=U~pE!2oXRRA;c6~lBy0J_-hH6l@#ZGqL&@|Ip_vW(f*ykhMfC(TDTbXZH32% ze#CEs9vKE`wu+o;9FJ!F=l`W;lKpDW(hX%l@geIgZ7k1Tt$=c%RLI(t;zfOC4dMsgp z(V!~M60Bgf4JBM$N5)z*bXb7O+$RkX$~C1CniuGWms47QbGfJW7hI4Vf&6Ib(r<(^?m`H0azt7@)b17O=7am#Wsn@52I&phm7T z*|=;$5PnHMAAm(DQVZL6jM5EI@bFIl@fLi_B(Q=#KmnU2VQyULnn#$a^JSm%vMa$~ zhdfL30Y**9A3)-w5{3aUfL&9GpBr?XCpK^{PRY6Uv$|+yyea6a1zx-h0gzd&Mc&jl zudt%lFH~5ByF5ZkO~T?V&ivRqyax?vQB z4<5u$P)6p&Y0tcf<@QQ9ap!lZ%I6Azlev1)ToA`35|C&!H`YxABr~(Y$!U6cHYs`8 z|MM4FOst!2W9A;}ruV}IqN!h;SAf`jdDudd&y3YcCp83;6^(3S;-%`pVP9%~t()@h z$vQ23a{{Q^Mo{5TlCChSkUtUtb=vZu@kISUQ-W4}syNJ8DBaLt_Y825Z9W1=DrN-K z`(Zxru7Hs>VGBiYW^Rgw3>V6_6st5$`27G0XTdM_>sJ2O*sPL6HdxH<0{%2P}zr zN4!c_t`33tc0Y|RxdHV4)2FTN^12*3s^@gO+A3Y7uw+zmv0w+va!n&qf&SCMVGxe4 z!Sl&#z2Bbq;UTi;Bz01rU2>BBoU8#B;IIFu$c4L@>m z;M~+H68?#$GU|sTSv4f99p)F+V}SvjQ7k;d^lRx_{SFYp2!uD@-we#TM2a8d-@=DN z(|a(gAd()U=*knO!s7#j$pBE;S+|l-j`Q<_-0h5=H#AV8V&XfEdpE&OE`iSoV7ZDe z13;{^0dRA%6$6E|91-0t45kWaudb=VX;(X8iW96y0c?$e@(}3O2mpI-lr{=MTW%tXBmM-d64)0j#(-NseFB;ri$kvMLM^Ms09TYs#UauMFIW| z9IWqPDt!39E(IvHgoaz5WBt%fmaWd*BF{uD1mJV9XJQj3@T@htB$_}QGqa+5D;NOK z;Gfu5K~szVjd&d>V}_;(KonRtgq9AAo6-9N7$p=8EXoZXAc;?DgMiwiegLiy&Zimx z${(>@c=cI%P>oj~Kub*fgg|nJ1NP#cP9*&t$ph`*EpDuojQY78D4+{29aN!!;4Lw@ z7&)x?8H@{+*xhY_M+)v}gL1)SRj4%B9>RwOoGdE=EG{Qil5R<0rfUb#S;=2O{2ddb zlEhd9^l5+l2E2v$K{>9?6M$VXX&b9#o~|Y<+2vC+3gW)QflFdXHa#ot8ASCGAyasi zuI*%u!Ws9)1kyrpJlt=BCY)#Zfo-_>nYO%`$&%f5oJA!{Kys5na~(Tf3naej@rx@3 zm7KZ;jiM9*P^*?|#{iZ(gt=bm2HJvgJO3&Y{wrJrfObV;{zb`SsHJJpas&uW?cqG= z*51F2LB9>6blD_x0g0PsmfWWj}@P!CTW|lYS3Tv8qYz#W+yX)1fj#>?Wc` zhfzzZ%k8sWiF8evDK1#-X`Q%Ac;t<3AC7T)Eg2uD@#^#f`LXcB-HzWOgq3jYL0K5c@VFsO^sIx-u3@fD`=lp!lB% zpf6)@Ugw=}ccD9J?$4RMRDe(wNx<)7OI$Ok3K3^jm-@a5hIEo;d;umHI7M} zQ0yJ5)_kRyDRGZ~tM_zq4O-0Qh611Bs!yEN@y|pR)wZ-e!~akNO4pvJ>0+g!{Pk4; zhe%{b3DprSGMFzVza3iyMOra!NJ!T$aRFE-C$p+$T3{>ZhFupCAPcQ7`F+v4dTM|? z8^n+@Hjii&#L__FtOQjH1^AQIoZD75KVg6$?RawpL14DFmLd2)b*ayw!gl?#0p6ao zwx=3I9U%6C7hHb`PY#S)#3DmE@dzT8E0xYge|a&WrUh!BsDa=okQm^miP63krbq#J z^K7BB-);_}Q&=^QA_Esl_%Ilpm0Pgvd1NpE!feRKhvs%XeGKqP8agMJG6=y6<`-^> zk`quU_Y)lutNfiH-rPP5H2^#udy)V^0Xz&SmJ@310tQ5Xu@7R;(%X*!!SM6LoBCR) z=s`8-m}Cs_7(v5bg4_)jBCU2AuSV1ucPW6mj@K6spr-=8onVEE-8X@e4=6$O(^wV! zFgK$0lGdPqsfh!8hTj6VHpr6kofdZ=`h07L-ND)bFsDHTorsf@(9W~}0ouvW&J$n- z>bzJxr1pydyi+(S7)L1iD|Mig9_&IIW|=n>2Gl<%V}Uu~7;k0=TbGyTdzZsa%SW+aWK!YbC|INWzW4|n$)tK3j0%6nafHpl1V|Cd@^f>iRLIs? z)+Nz^H4-eslpIq6MTG79Fl1dGFwFJNGf2m94UktKFxhlx%82^9seB_)g=J0UPEtWH~Ii+wOXfPhtP$BFv0-%#Y+T$qrwrFN;hiEcXV)c zjNx0Y7yHvXuf+VFofPL^xEeY59Tw{8IfNakcd0-HcN19sTWPV~*NdHBY}3S=V$Lcc zo9i7dr;G4B0a5Ir+V}Zpk8|Y`SK|Ig0al%3fWkjyR1tKRb69OxH9>3xkb^Vm_WUKulOW7U<*}5 z;_w}R>=Y__{40@;#(5JwnpvM*g^&#H{o&7F=mag;c_f@+o*4=(KoOkLcPZCMPlbMt zVR%r*EKFZ&aSHaI+dOrSgD_0s%govW6#Hj9k&o)@J55J!HZ2I);#YsKi_Uky=M0(= z0)1v0q`+7thN7-j2qmNI=4rjdFWRL}4K)EeV5Se|)KK};Qa;%`_h;y=a4?gMMuk`Q#0E2;VdwN$W zv~ULB74jDs3hY3N07lmNyhuVwlRyG?XYLioVmTgSCPV)T>uV}P&jbU+g{Q-T@1WPw zkY((LUxsg@AJ%AZ0pW1FQulbEez0cE9xrD3q<(zn`2I_?=*>zm!R>0Z$_x>PyPi5* z+2)Vqs;~tRkYfuC)EgDxna-Tdw2C7uinaQ@dbHt-JT{#Lluv=w$kf-$EOW5|`tn)* z@gs&C=Qk-fFvtYe!^hxm&zh$&p?A)lr)crKW%OdZ)Dl>Fjp0!Cr|$opx?QO3&^;Sl-GnfEh!Qz7-}p{c|qwleA}zp0T&C>(5tb z-x4nZ-@kqM05D5Z0l*Y@$=4@!Ay&cQfS3TgTay(SX5dVvw;KlhtngJ8?W!y`tpNr= z4~GWzN#)`hrp_JX1tNX0f+|#~_e=tingUJWJ}yFUEer5{_L1z%`lBT@4hJI1Z|`*l zuCw=Tz@tNso>kMdU=6LL_7Y9#cY0`Sv@D~u#q5HNfUzDvBdQlta6%J)I3ae;yYpa5;)!GDX! zDC%(^y3i^fp6v$!^B@X!()8^u+yK0Up7VEDUJ z)e^EaEwk}m7&P=ujdmCSOF&oWbf*H!>nTUUc(QKtg$&7vbN32FC+zcp1+d3kEze~4 znE>-XE+eY#boS8c;w%8jHj0%Ld{hY~Zq<)9wmr5!s9gZQKv4jIi?`qdR3l#Xr}rYc z%nbZo<##R6*4bY+A@Jg<18a7}GR4QOOY@DVg*50UByeEA6#~HAsy%XlE>b+X&M7xx z#7EHg(jI%WbNxg8OJPAm8T>d!F#P1S#prmw258F>dg2f8XTsiWVV;Gru1TF99CFkU zdx^C<9m|Lg+6v3Xp~I^+T!?7_V3bn5g^)@IQaDuS+J``WHUEMD+NQ^4=$1VjL3~8; zJ%KWm*BKK97<%Pz`CO+bT_cN<5h=zDDz_111rK;?tc->Pe1ySXz;AT?S!K4VsY9QS zM+PukkPJQEzsokS(zPzMd*H6D`WJDEl%k6YlqF2Uf(=kK0aO6LLqU60OhliP!Vei% zZB5;>6$bph>o1@-9NzAkgsI_b$i|F$M0z1(YImP zzL#^DLPk*@*~28L%WNJ3091iI#s&g*vEac`nEQNLC$Irm{rZ@q@F>%$Dpk6{iU36y zoL4p2g7AvGm<|8>%rQ_m$?-Mh<;L>>weOrZ7uXQQp$Z3@*v9+8OSoHmx0SSC2uc77 zi;RS?{!SP2N$}`<<&FjpfbF{u=~yHbWPG!zg>irdG8>x&4;W~eFogcg|tw?)dv0^i5&Zv*hFS)ww=Y)3DE&4DGN zYfPX}2w@FyQxyN|Jq$V|1F2$XiXVJ+20ooV>!08liWUbl0wa^(>@YG&`EWR@WDsDhHwEYS@a3n^^i?YSyzx}Tg8+?Ae)oUzR`0@t>yxs2hZ2`WJEpu%V zSX)H4V|~;m5g%0U6Z&unr6vWR3B-nz!&q zQ|F&3ZB%dv^#}tRcyJa_TFxiLZtZ-q*2VpW3v^xgqJei!*I6?KY{0NaoSmMW1<}Aa zE`ec4E^t{=bAs|xM1(KAZe51qUiA|6)w_-U&E|*?An_5s)1c+zVTYlD5Wbkiv?JvL zU)2XdXaH%UGW?3K?60EW$;l=47$K9LxVi^2>5-*!$hzGG^Wg7KK6K=tvP z`w{&3wjMe^i=nn?IMGy>@6;_VO%m)^7)ag_0v2`FdFJrpx7IH#dW{;nsNc%C+%)vSjL)$dw##`V59UY+N zp;-cdQ6#uE3%V?Rv_o<)o*R%^!T=YxVH+TT+xq9qU5}Kzjsb2(45-5AASCo;7j{2I zWhTce{lM|B6LvpM6Z}cuV4?exSDv25up8L{)2^S>+TZ*}zB*3s^}Zttza(r{?zv|B zeSUn&y9j6#KCv}?`n^~&qiXniY$;a$Qus4xhy0&h{+|N8Ye&bf$G^mmT~`;5hu41t zE9Bnp-5h>azr$~Pus8>|mHqpF_AHQ@{eSch2O2Q21 zXZ%#xW4YM##>BLLPhkiXHL_*{yc3_Z^S=+$U z&mtM6rUvTch;v%`15q%pf3BRVn~LqJmB zt}*4~bOKjVSo*Yc0mGh6z-0Y{=45om&bKm03ho~WhQ2$xZNWPU=?O?NsvjLeLrO0l z@_k030Y^EP25Xw9-^^$X#X>Mfjk-L`5i_?tjehH$CiveX9=~P;Je+!(5!hsrWKjaY zD`zdzr&hAonAk?S2PPZ;sbI9*p7eDRMT^81?YOQ0G7nkbMXpW_0Wjod$-am`~?6|I3UJIO46AhT|2|m_T61r8}ekX^m7x^S+U|?_ttPE}(Tb|!I zSV#BbpX?Gz>SJTXzjg$kMOm&h4nEv9QpkS;K@=BX*V!0rxgpggRk-=tsLRp6Awoj~ z!&mCXvS7}l>YTs5w7R-lWZmrS_}B*Bw;J7-)xlc9yx>y6zRWA*FN4woDg5Tk(|LJk z8gBl@##AtvO7tEm+@Zs`S|Jx|b-csIJa@g~pGl^h0H6A}9OUkW!cZ8GzrGbX@&#^7de}(y50`KP6 zK9p~S54NIVGIkrxGYsB^FKUXvcoF7JGX-5vbNUU3w*A}=)|B3MTq9J0E%&2Wx%Q2Q}I*R9R;jOiRRoX zGDNU7U}j^ zpf)nxZL8ZEdo86iM7{2Ch4Yyjs)M3aR3I6`iLAIOy-F+(K1}Xv_>}K}GDhM*5`Ox-PHRvhk8XNN=Z2TvsR)Dtq zb{+r64<`#zTj(Ovpc1M-31TUIx-Q4|?U}O1N+y1o3hUO977^lx#6fLK(3l@1O^s|U z|A{%P{Sui+maUD*z6tF*ny5{if7|^g;0>JoFST*^033_D}}jdcq&m!6uV+))XPpa#SS7 zhcrlZ6SMU^?1;&Pq%pL?K#q>2$rXUu4^YC*L257q3M}(HA(fj zNOIudnG#J`aDpGr=D<%WEQy6JT$UOpm@tr9sMY(&OSk10Vg*taeKa#z(#!ip`rFsq z6x-_-*>Ll$Po7!O(p7m9us@NSf6!*z5eJY zN&A+!p;Mo-VUnRW*IwNQa0}7oN=nlsX)lT;wsfytjMfilI~k16?$*0=9J8T|uey<~ zl>H-IEkkE?ynb!6wt?qZHpG+7N;Ijae1oyc=8+kkH~rRt=jQGmzt3sh`!v)P zIM$gq9b52J!@w%3@!5AU7Z)Iz%e;5~AmMjQDVv^ZdLAyQc|3|q@FlCk!0!?rsIY~T z$O<>lt9L!Ka$uE!ac8}P4Sd@_K=)+}a3j_d`S-Bk>a3We|D_Ky)SbPMQOo_nAAbZi zz0n$eAgo!Cz~J#2Y3++c*m;}6!-o;53KUjpcL!Zn^u2l7@kyE?4mGI2tGyJ}FStm< z&&#wdV~%~@w^?PG*q`rXd9S_zAHxSaPef(1WP5IRV%YMdJT)XI6C^0GB0t!CxQh~a z{eDsJ4j~0=bFMVsv;CWtAw|YvIOiWPk5`tpBnk9+?KBT)so1t@_oTC6Bi9a_B832; zg8%>K`8zacQ>x;lgU=)gV(cM6{sK-$+dN&ZTpZwk2?cY#>MN%i8S zSJ{$;D)pbw%m(f#yQAb28(z^?^6+U%6B}w!NVw<_2xB97+bW61)@L&4JE>Onih%D; zLKz=)vXr4+WqlV>dg&W?on4j+)z@PW52=%vPj%n_u6Z+me^+$kP+CEB*ldMHC--7- zEY5~Zb*+`Pe`V273Nm3Q4wPRWqEOY{_^2xI+yP=JbsV{8elX%DD8cid*h1nx#@*C0 zcoOTEpFU1sekB@j$1!%nK>f6}ertV^VkiFi){tCS)S0J|`2bTem%ai=u{bW4ljKd- zMCkZFdw^BfK>&yMZ0)2(-Ssyc204H=Wm78W+uei^s1yTItml0+h{8*-3TU{v|0G{0 zXec)oRw*8!d$4{h8`e^1{wD&bImdjX zs7gijTk?|-LV)lsO#xHjkhzQHS7gBG3v!3KL2McF!PR9@M&aGFWhfSYNDdR8>3(B@ z0v0gK{z1{~D|gATSuacRlPVRgVvyY)Fs)lPqhfmA9~``Bc#$d8R5~`PG8xcpI9nwCmg5bJFqGm!7X<|0;-j@>*SSr;9zD z0ORQ3l?yp}p??T*U2-+u;$!~^KY7M{^Khc5UcIn`05$}6#*f&{8v@ccRSws=*%mTl zC$1|-HW$CLxx`VBa0{AVB^8W+n<43n8P`dDY`ICv^yZo=Lt(ebH3&p=dQA_SA96^&VtwMWD0<*oPcH)sN1(~OFPpEQ}LZN`m(9-L=oRNBun zy8apX-NR_~0}f+~3|V~;j8=Up_amSQv#g>^6@LgvFpq`RApoN~-cQf1bjy!clF`(V zrXsJ!zlY>5hO=_+<7Y&@u1VpQacQ3y(_5@01bwq4D)#>%_47K&V4_fw8 z?+c7UfpfoMqYE_**}#%OO_e2}VuX;?wbQ)!>U*?~lVq>sf|rRRU>nqx=X3R0ZO zJzrgQfz+hXYzv9Nn+4*IbdCvnU_b?4^W)KX@DkEE#I8c5ThC9SC%AcfGTq!WuF`I+ zW)6B8eSQ1ojXsB&w3MGQAej{s{B%FMH?5@As?(ScU*HC7gw`Vw*>^^p7SZUro)1|( z2-3S3ULlpLQ)E#T3r2kDsBdVaT+ROy14=}W-FwSilU(ULt?H>ilAj@Y#c)jj{jIT! z^WVGBt308ZEzGv$go`!MdfhrAlXgL*d_jAEeJBK1wzo?+s?;7a66ka z+_8HHfr}RXTiGoPqtxkcWQoI}rbOxnzh<>*p|8S4bzPt!SAkyZM7E|2q%zGhJp{K_ zd&#qZsa*Q|6KoRxD?q;?iBdjA32#3RiD3WX(ZXKo4}Uk<7T8-zsjt%rv(hfoe9;%M z?YeXXw3&WMnY~yJaVn4^K*z+ge51j;Ca$UgfcgBXTwPDr{%W4Ws?q|uJ*g#Al`Q{u zrK4|cOWzE=Zx|}Wdz93~?HVhYKTDi_U%Q8bC=bI!P@G&4C2bgKGw3x__>%K-adUEQ zduMpyz(_UW(hs8PmX_*S_7*lYAyR~Uf&X*>V9SB%A`V^32%^OYFC^ z6GM3aO&@+cQp5oZQ8m69^p;0j8#2K}Z84-4a&^R(%WrX^M!;6 zD2?hn@yQCg^Zgs=dTbi5hI|WVsnv9AGMAn;2V5b6fq8m_sp$og z19AjM6R(Y zjiAdu4|9xv*^$ZTJV--Axrv?GxB3~L^Zfpk#PCG{lVBUw(sBsImPYs(M66$hO^GqY zbb~T6*?W4&{n>_2;XW1I2EJ+|n64O#&n+`3?L_~oMYwP_y6&5R2w8|u3WqU=LyO+U zyaa!F%GK_ENx@z4h@y#S8$9(*^JmNagS;(qIxk5I2{t?NL!BLl2uG=d1d{TSZFt#+ z?x}I4ta4Mu*LShbz{_ICzkWSzc#Mxf<0~%43bgQSPkr@y^B$fnD-PGPDKJF;o`&R+ zLcNA$03HZB-GX9~ee5>qv&*0Mx zztYWm=}QGDysvj%_sd;P{m-TgQeTqwPwp|lQOkj5-29=JVZrlC;sV4VMVv4nEMs&L zrVFYC_ZH;?)8Ns*Z!XH{ekwV8U!q;(j-La)r50Trd?{;Qj|%_Xb-~4LvMF(9lMaZ8 z$T?Z>(yle8Z@hg`Nfl=E`F_roHM8Xwi6pC`sv;@0on!~k-o%VEUQuHCm+2}cugbzw zQz75+u=e6d`L9Py`)g=mm?I9cwCU%)AGE_?C<_T>4EWjPok~wZ_S^xv0Ruxef#>?< z0}vz4QC4SIB+O`8M>wP+bdKMxmhX97V?w7@Iee|8&=xp%^M8&@^LAt-6TM8X(M$dO zEVKNS9Alz4TTIC^zrZ|@|5y%-<9ZEk9ScU&gC~3Jf9G2@%}V*H#gvGlMPFzySdcMZ zt#*}MbGN**seIIIfek6QuhH0zo%afqXAFG{x9EiyP85{?9LIDg4=A^hU>WhgN^mnz zeN0i>*|IwIO09~=ox_l7z0r!MlA{mr{wtupM6IXGgSGTeBZ$<$G3n~NT}^3=@R4Os zJyJe;oBU_bTRb62Qx@2(qEB0IT&^hYylpNFk_Hoc8<2(QprDL6#mJtE}H)RTk>+@XrnvD7%eHZxA-= z(GiA#E7t!>`Oaho5N_5@7X)0RVsgksZ|B9t{au~MVETq^Fw#+DHPjYSB!B z(>#q-|D%LHp6lgRaQMxE2PxlqQMt4o9`+TGvK`^g_aYY6XIhpqcUq$YWu*h&Ih#WM zLlEm*FQ0p|{C@oSLAK~qU7+jt)L+IJA7pJKVdc@NT;BF##swZg0zcOLm(t_ouc~H$A-m`29r(_nV@=MT%i*_m8{ENTBOZ<)h-452v zifcZlw47J&IY+_bT~)2ZD|4mOQeSodJp1gYvSZR;t%^E^b?M5-nkNGTg9KH$p#nJa2R3V9*JnzRhMOFB3Ds@PJN&y%$*^2SPQVXu; zrd)(6%pz!Q6{mJyU}|pD<5nu2SNO^EI`7$Fd8_g3=5{&9>_0*YF%TU`AEUeBT)6Jw zH8vmp+)u}0;YEnf+JEWUBU#hFn>-$=?sc_ge0qhKMgpE&N$cIo2TnSB7#DpfaKLUh zKbZY9z)bd{(o$dArz6F*KFV})CH)uajoVeeK~1k=b#<&X)$jki7n52=~4=0WEwjVc;^XbO?lp+GkmsT`ryWnk;edSJV8`3lfMQeFEe2%)St@FSYDF!ysLDlxO`Je1od55ZW-E&y- z8lz~s5tWj8pon!218PU=TjOos_)O)VxAgBDa?x()`1Qn6EC!r1ZX_~@gR47;VE4RLf$>|y|1-XV8I@|x3p zq2T`hd3X4K7Ij=9dWCPXr!0Q%w&`-0VK>9o#BAk3e>OeFvAmgO?N^BsnL>+=XV{6lnRbdBJkJwSs5JON zD@xA6;(49|TZ<&cJ?sMY#wgNr%ln^WUxf*x>3!#Y5#LORSe6$FP;byHWJ`Y4ZjGl7 z+guRv&_r6TD)#god?(SAfXeJ`xoRkKRpYBM?aqV~9T<-fchrT5D3B#1P=E z!s$BN&aDGu-UitNCcDazYeKSnCVr-K`Tg+Yapj}nqw5q%%}$U}aF+NGllBSYLVgAT z?fENSPm?h`nztbwaeNEq^pIg->fh-ir1@vf6lBr^ zifH6xrWWXIh%xT7#Vt}WTSxVEG;%-!5B6k~(;A>dr0NeZZ;QHOdf_ujK*D~nzcAl^ z3b_Be{fN&N03BHUiof0Qdfsg`h-SBVf!7=(h8BqF*yTF3gk?SY^r4`pSJN9x%mMg-O|U91@?8;-ghadTu$ZcHZ!OphX7oijx|56sc`NK(A^bnU5;!QUHAHi%IO` z*U=?Sg?rW44a-@MnJCCrcHQPcq8vBH6k5NvUdG9;Yq&|;2P89$5Cy`v*-(KsKqQe09jE-3XK%Ig}u2&E3~|A=36P<4=Er z(#KFC_0l%*xuEF>s*cVq0|J14AzjMIF)>Sc|CO2J6iXCZywrxn0nX>rX?+v)b&Hhs z>WTKB1ar;dkk$oA|LP2$U)+5)4s~S#GKuYWTRObYZb;bPDUTG8YeL^}JqI2leVFPV z-mu9wqL_MKt@zFcAw#dB+)R3@3hrGd&vV~^7SGdel&+nP7(EC)c5~|F!Y-zB=E%{p zer}M9OTI%4E^)}Dd#S_b(0`I3nn4d}kV;`r+@xyo`^7%hr zg?i2Qmxfy_-)R9`s6KgJC)e?x(lGr~{HAG=q!`yHzWOSs`p>qSpSibxUlQpPy?yvo zcMd)%!Y-&0LZ&wF|8H8iWPJnCD#kldfc8D9en*3$&Q#Qwz_phDCQ-cs{of=Y=VL1Z zLB5-FkWct^zFu^~ey1V;)<#N{UpLKQkBxYeh`rEqCv?M@vgifJ*&t6De0yXl*Bc}* z2WV*RQ~ZH^oqJRxlP4cJ8M**{&(AY>ki-~bM6DD(i9*fNRl~piL-8vZ_b+1%KiM1tFm5vfkXRhSn^#Ft@eV|sM~-klGY%_s z2te2hdfGV}Bc47i!2P^p{Iw9KuSl>4`UF;T7kQ@92{-fZA0+u0x#@s`P(&tyijoTFSh65&^_gFdLqry$D2N6~JkN%v#L;6@&BI%h zV@cvQvVG_Z5iIR99LT3DYT9{k1$3F@H!kQl4b?r-**X04a-o2RG4hPZfO?|TxWTpPry#(+Lw)j=^Ybjc0Rz637zI$@-c;E){?02ldt z-=4Mf!Lj3Dpeav9`~myh-2njP94)C|<8B7%Z{H!WNOiG%t7_ymjx}Qv=LY8lkx8_# zv7B<8K@)pG3I#&-Gb{<-ZO!eVve)bEff1Q5_Z(c|o;No?Nm19aNs1WXh0qrh>vJV~ zZ!!phYP>x#DWfA=rkXuf`-Q0RuhvPp(F&8h$&E^!)_~Vt5c|K)r)Jn7y%x2j_#y7F z>b@i5zyz5oMW_A%gqEPCk{-gteyl-O%__WI#z1i|z6L&sQ;JU%?e`(DW1eH*z3%B! zp)^_p|1f3Er@S;0(I!fj>-AS)wAWjtLG`T%Jo<*+vYISlzs~{j)}eE)Xtyfx;Vi!c z!DduAu=sQQ#hKQ1G{{&jfmo<2a(Y|88?#U&JkBpmTJrK`+9(RxV zVaSdLd(4R%8#jj! zBx+)Iju{XJ^J~=Nbg^mwCcYKA#5O+Tv3N1dU_wTxSq?eCT3Za1VQu znHP2g&_imj;ng5?_e(T3i{%^Ew?{pE864U=%!7+Kd*~{Dxay6G{EA2@6Q=y(fG-;C zaSI#1+Wb&sL8E^63w-i&tP&!#iFl@tlQ>wRk{=|pY`KN(B@2JAl24zz!6=bJ|2|FJ zvlGL6KI){BQe(iO>*z3J_+0mZ@5N@BfW~ZY0xSDNMsK%N=d5X1ieB@2Nv$YyAW!a8 zMrU|eC4PD{sokAock%|MWoL*(->`+&K16d3HT_*9*p%qNPl0v7tQYkMud{B@N1h_P z6zy+2rpB`iVfQNC4h__Wu5|ju)t*HbI=OT^51QASd@L*pX7W3|flW17FP5pY4Hmrwe0lAt4%K(09d``#5DUy*~#P`m})pI68tfEj|rlwEQs~a$~W?BNkf7y9VZwpTLJ^+)&m1=?B2tj|)3vFMGMno9yf~@3`)B3fr zQfyd_^aDtqhRX}#>QBuava)x-NeVv1)+Jv;1J@V;gmKs0ZMal9CG8oAOOq-PPjYT@ zL76{2HZF_KbLY%mjMksh0EI0c0Fz39=x(T1MDrz~!Hh4`3FCQLgp+~EJeRgm;+3eD z=h>HY=&IU~+GoD@Pi#HOwNzL!Wkt)BYW={q_q(5XvsxtM#kiKx5J%=|TmSrqNO66{ zh6z4ac-iy%;mYaODB(vL36u#H6LkYIo6#>uB3EHuBW(CnDthB%o4uznE3D;gG>QqW zRLMIy!^-HCH9Hb4V&1~vgS@M)g)d{YF=vQFZKJJd{Cx$#D#%J~IFkJU#T#R2?WJS) zdN={m@-X)#j|rhC(eif8>gEow@vY5;PqZ<1=Bh> z{c*loe5`iKBQF3e)y`mOr!rBGRiU9u^2B^l6+1dXG}}hS}r<_*M$gjuVh6}LmwgRMS90C zmm0!Eso5#LIF;?YQd>TE7-t<|h)VXe7e8$%;x7>7bK}|xA7@LjN7GMI#~|IsYn18Z z7Y#s?WcA_7@biMTdm+_%4) z)qOPg5sHkFb}!zH2X2*Ql{)mjQ)VH49=_`NovEt^s_>MULX2sXAL$}&if}cQPPszI z3wGA&E>Hzh&bx=>3$1n9@leg#u8DRCAdE7|xcXA$%&Y5fEFwSMv%T6JGvjfdz%blU zaUEf(=%BxHHD8_({;f8nim(esC)`%4wa#hz1Qdnpah<_n74@j` zgFSJVHC7PD5X>OP4W5uBHQpGus$&E7DbIn3F(PCx8zvA}&ZwPMqLHIutf0td>eBIi ziD6jneZ3k|5WY%u+E}=-kBBlYywB{9xfz-FolK^FMv6Lx>XXXZ{&sNSePj1uvzE>^ z8}wz~danERw7H)O@qeq{82}{u&HTPDrhn#*jvmCZZB>)d;I(U9f^ zi8mrSE17!VPKTs9l1Qz3rnS2Y1or;Z08K!$zg%M(mTvmy_zG0rzl*zM+6!aiqVr8D zC|qxsMHsp9?WJkbc%wX0;>GrQfcpvtf~u{(cM^ z%=dq-q|>efnz){+Jl20BCmivDmxl0C_NYtl=!CWH%rF7j4CGrCxv%}KHofGKa=&9> z`T?VC-uwRBj`z}!(gX3t({+NP61w}$VX7jR!33@*3wU|yq!eU(;acsBjI~ks7*d|W zKV6&37&*^uq>YoU`*aDrd15biU#s6&@)!F!;t+@B_xcW9_PL!uQM%ijS}a%M>^96R z*#uhbPX8>DA=vc7(Q+)C26B@#o`1&Z&UrJ;zY&f` zXRq|v41fAaftg6ax7E>%NvBN)Yu*!-J)7S78#A(+G*J~g^CFZ%p%EPOK{-p;j0Rh- z;DJhX?%jUvXg#ZiMZ(-^>WttWA+5#1u$QOqZ5k_OqE;#W@eTY~)-wHc;d8&-f;jmibS&zHD+-WH0^ero;)g;Eg zAp)-5XQe-!k?FWZw*C90)6<=HV#|ecdJ-ajTOV|Vy5F^X`+7Y^KG+s`665oUMQX|Q z7+`=f?y^l~SQQbH8Tf~y5|%TFr%U6U~poomKA+s``N3uz9#9B(mD7iC<48U`#X#{2NlUj>&xSC+sm(!gcZLD;a>SZ z56*9(4mPH!RXo(N_9L~3RXd8P=ixGv&8Mz9wH`?3Hctv&Jvi2c8!vxUgG0-YL8pzS z#C|HJlOd$TWfZjy@ITD6*PxF%G9L z7ip6{MNdD^6QYA4^HKE;`JOiYy+^8PYvC<79?H9g+&+U3PZr=VfZ$TB5S(PCa_BDE zxF+<`XrsFM-l${kgh3%>xz7+3N>dWndel{N&`9`K1Ccgu7S{eBtWvHofpLQF43*N^ zI-x(z-i-}6t5|#~q00#_@lFFl;B`GCI;H9plMa9Ve6!J4S5$7@Ut_MMat*f6n3}?3 z#W(yEq8wIzMniddZR)q*ORb63DqeLLoDGOW5x;hn!81}vSgFNq*`LIA_dZYZ9HS_zY5$oRT-i-rcIVLlnM0uE2fRZ$Z|`F+ukr7Z>Z|eSkJ2Gu=xNOO z6xvW{T5<=e;osS2U-uN>8?hzVMH?{E3>=WCfV%HrLee+=!DYxh3!^Ljp!TvVR1i4% z0CQ}PC6nNMJ|J*BO`BQaE1C8xyUe~lzPdEEn)YU>_Z-t!`R7S}RL5a3|4oH0To6K< z$Ihr!sBV|qCqjdYo(pY+XRFx@wD{JN7lR|cUs2~$Xe+FalVf4+7&vyw7)cMKWSf5` z8!HG&e)=|M>U!TrXbrZRV?()Y^)?V~Ob~~rHT?Ys9Sl-d86ayq)ruOU`DJ;batuoHW%U3)*!}iZ4lsy}#m+0!u4e9efZdTgV|fEVlGVjD!RXILmyyxt2+KQKs0#N!(3M{l{6% z%7yA7wQk=&b;%3gXKL0@d{?c7~}hcPnKchb1Ym~x74l7@Btcj`ivcl(pW{aq7C;bP{}FoZ`o;r5;Csf;bIW|*9?VBq6BrLf#P?IyVvr=BCBlUWd-ZRfvxGQ1ar z!`aW8&ap30;=A{Qwyd>`yd}JSesc!(G79T%%cT!I_avyX&wVzX~hf%U?E09=bz6N z|1+{kng0@GicOvyvP|6wQe9FQND=;Qy8=&qhp0DO)~t6V#f>{m(Ux0T1pRYs#gKzw zb=|A3M?NFX&;NZR+y(GPXXvSH`A>7a zoE?o`B@l0dN8O00e#NKO4F>DfyOWPsy0AbvFn-C8i}ldVl<+WZR_fqM>v|1{*-?4W zNij&5sOie?&Qp&)s}3c@R>X?cqB7-syd+7e1;Z(K_)Pq^_nYs>j(>%dE*USe=s%0z zyZa#pGV3R7GA4dR282*L8+vbH$j)p>?(uW+^531;b&a2A)G=djm+Cg-2759Z40?%U z#~Jl6Dh@-jEic2$9JjttzaYN7-{+5dygIxL`C(=z8w|;`SMQjEp?P-`uG+jv#@W+m z!!oI=ehdc)nunNBc>4DjsvuMI3Gsq;i0ao|Fro#lGr689;#-W|lvP&(h$=I)H;51W|NXUwHtc+p=n`;~pF!Gz z1+w}eZsYiaY?zK>oj0*Y3;54PAogIgWi%N*pMptX0u`Rssn^7>3X-s<$~| zyd0OVpyhHFhU@S8%j2}%=LBU&;;QrKw5^XB4(1=2JbnP#nkBZ?Ke;^puQPYX$_O!n z$}2rbxT8vPk;akGtGG13oz;Qou7rW>stqNw^mSiiG@(J<^_uO9A3JYS{fP~Tp{ zcThU3e%Pz$9qs8=puQ(wU$gU7{e`*lg6c5DGvJHc$RJ;wQJ;;1qyXIJNc|*bV69cB z;qG>EW~&{E;<-ezg($@fp!gf0$CH~WWH9!Ry-A9|-a5^C-V@y>8@F-ysm?C4X<#+< zpRYlVL8jT&+r7qT!>a5&|dqviVTlG=5f zW5j~T?AuN}O(E&a2fC6cIRfu|$WdRc|BoIVFqbVW`jZwQ%0>IHH_%?uPQ0UHby*`s z@2s1^);i5^;Ifa+Fikl9F+4lI_Tj2ZEKBpM;{nN-n%&Xo!-c4Zz3y)FG$16-(7_#>$-AvYHkNvUgw>Oth3TXu-(6r?6WV&0!?q+x! z$uQ!RdS@89t?q~^Gk^Rm^s$%`D_8M91}R2ITD9ahEAHdUOWK#Vqh?^yyo0kXFc4J= zo>Pn{InvE)#yKAOnIk z%D`9G6`MX9)xi84Q(p8DZISA8blwyxB^9 zCYs+Y_B6~kJ%BMW33tx53L{lGZZyVF!%Noq8|p|E)U15)ZTE8G7e+uMi8OR)6hhYbQw zaD75}-BkX9AqBRyS4krq{X)QH{>`lap8wvVYHwylFp}|aYwR>xB_zyVkY%+)f6G;Q z`^6BxwAiSsIjn+=-yd=aJsgn1gE}e%`hIp`%tuW(3gWxI9enD^eIUo+TQ@22s<5rI z6o$h7L=u{4lZNhV6)M6#vay7F5SHOf0umz=a+q(K>dHqfn4{IX@%HSt>WoNr+4YGr zxTI_Ud;!yaN|PS#hmQN(WWje;rn!0FU_^-gaQ090n7qq1G{x6ISL;$_>y}2PFI*EU zh{-#MU@M(8-l&N2t>Azw2BnvC?4l=5<OApT03*it~ zD^lf_A2!-i!r3A4tAF8dj}seCR?MLxgind_pc$-wmCVt1fpTeqE|lB%1Y>^yMm*2O zZ~K>yJy$EB{Y%k5`?CGHi|x18;~T8K|NVQ&ZENcNW}4k@ysP(`Oe`Ed;L^XD>|wOo zUQ&=WHrI9}a|pt38xfg1^iy5K$W;sWsIh)SVJh6%kfK^!dL0cv&tmVm-bDCwzwl{T z{-va`gdcCY{;nB>y;DMNJ(4g5VR3cyC_5uX_r^co@!6eGh#c6$z0K5Dz}d+6WZ_Y^ zlU7G`vp$|yc&4{#^C;z;BR6b9>WMg8m4$$~hyWsb-WxgBj4REyuM%v0>ATORoY>G* z=>*ofA=KrVb|4x+@VE}o|N5X^e@?t9zAn<6aYst6{e8=ag2HEO%rsm1I1kONs;cJh zxoHF4+R~weQ$#LQyKWY2t$zI<+UH^||KdKTP-pE_Sk+D3HHvbPh6>B@x3Wyv>Nt;Z zB5t2ZfPg$NQy~Q@u4U_&4zh3t-_GYS_(eT2@vvbuYFzQKP5NeP>^HQj=ryDvr#UIY zf6(JuV(fQyH!F-hKRkGp44;V(|9p=9-!9FH!!;y|*74*hkJSpP5W3di&uFLc_pcX} z{OrKR=BtQ4!>_I|*GqdLHUG_5{%+TZOTWSc1X}d2Sn+Ah3g*v;_oqR}J~{P453vkdflmg$W{5*-P7d{)|QIuZL=WxPuE3W&+ZGSB$X{RcrT94lj>n+;9I9 zGoa=_bA-=BLhl*Ix%a{!$rEJy1LZIxt z|M9yFcZrM>|KFk!YzP24=`94ztquUCveL=A=4s;B-6?JApXWH%54`|YA`6A>!z_u#WXO>P=( zS7}Ak#4#8Inm1Cn74$%es_`RYzkFs4c2YK;_H<$5A(!-K*vOav-R;ZImLSm!Aep@0 zP0o$HrnI2Iv2-2t5dNrq>W_4$`i?5xEKs&`v4mFOEOyg*hfwV#bMQH~&LGbSULJOo zd%)&m_=7n6JcvU`@+JCf#|13p91FpzhVBith@9GN2X5N)*Z!Gi z%dU+Xu@%}(=gTQi+in36U!Bj-%TnS9xTZx8dbr?_q6pdl6adwiYFiVC71`u>;`!mr z(U|4Qd^*)vJvH5$0ng%zuyF=GDZd=I*E9y!tA4%`lLQ?~wbRpK4OL!AQx*aOdztm| z;$&B39PDM}5DZ)QE{;SJ<6-?s%T^X@wxmQAO%=b&A-RWP;`=?##VF4W{aL&n#Q0Vt zvq!|A#Fc6~^c3m8I}-_Acf*1gHkc4X_pn3su>(@GNadMD5)Z|xEm~+*q#8zJQ`Y|jhEMk^!$|EbtMl3?m&3j ze?7umg=P|kob487=cQG;A6FwxcDlPj8J@xg8bB=X&9}fuL}nqOf1%Zh6;dnBa%sUp zVrmL2szo}kmZA9YKgm18;Mx7)=mSVr=(D+y>HdeGyly`Bx_6-Y>8SO0T-+LZIsH*y zm3=FR*>Q#`k-88+MJfgzQNj|(AuX+1gHBe1BU$!}g!G`M!s1G3 zD`bG{uqC+2B(+7nRauyDmPi;zOA^Xs7LUV>{M_wrI`dP7S~;tnYs(x#wuEqoZP)8( z4MNV{R~Q68ad^38ye+ODPSuaXE3EdZ!$ERLgvcR4N#-yH1kA}#YtrSHY47nte6^r{#=SNO>1 z&G(7ioxe?5XyZ7+xyV<+;C5ViN4-xgtbW?OX7fN}oXDzwxBHx2K- zsZy-JS7Kt$GAizx5Ig7UdSFFjLrwg07#9y$Vu!!K2G{kYB^(l5<(6W^e>J{^Bs9=n_Zwss-9Fv0bPGZ{a#;~Hb0O?;I8w#|WVP*n@B2_uEJTy{5{+ObsRZ^y z6l}L1;k0U^tVdrmd77*sLR;0aR6?3H;2RS)o+vVFRaGFiIxQ6{?)M_jSf_W2nC`yem&u$^gbrLV(;0-r zD$FVPhRk)$-He+DLK@@>2A@(Af_8Y4v7>*=;EvMR09u1>+gw=|{uIlRh`}9N9c8h8 z@+@j!#uI6%+tB>69oOHH38wh!A9g}y2xFp5#fQIFa8JmyTH~tv{SxhP=0G_YdN`>$ zlhgn8fI7D7-3$AjaUr~D*3?!sP{r&S zO+DY=9O9%==zP7?gSv*JRHlc36OJ^luH(OzvE5j7hTUDCXrg5ub^(EBn@=`yC@ z$uId#T5T~!uxiL8{uQ1#{kC;M{;O!l$p5h{mhZWP7lLik8+bT2@90C#SX>i3b_dk3 zzH+odZF}=V!M-l6_pa88xg|T85b5j%(Rrp!cJwX(Gz^(;+#sFjF4Ho^LX7!s+`~yW z7W}OAw?Mh-9iYsaaPWJ-vik4GoQ^RI%7DgO?3kpMauY;8gf)FeePrMAf|$@|#Mcrm zGMZpcU4EMP0F((8JSF6xFbi(me&ZV2|NRcrtpjHBN%I%~m>w*IcUow?5&3Zoh5Gu$ z@1;cx5gSL3(;rgdq;c?4uDu;Qwp3a&OFqF#cG)#y`cVEvdbl#w zA!r#*#Y~eryFXCM9nP7sjPl${YULz|bqs=}AuG{9G>JUCMeSP5P;vR{kUv~WbK=?4 z1<+Gy-};Z_XQY2V2P}59?44qjMr-V<>Qs)eb?>F&5@h~*vu(LInP6&I0WE5O@1JE0 zAO!wvTH#jufX?$n--#n`>3Te2%Y?v6gewhIfCIYFEggDn_r; z+ps5Qj!kpKdHFAD8@JpS^&io%g2wlxzhRY*e>5t=t@~k{Jf3VRg?!#!Qgew?N`z?2WKCY9D1h74XN8IK)ykUI;2Gw~qaOH&0{6CV9E zpshhE(+H3Hm-JHx8bUE3$;VnTb+i*y(0hM*iU?32NUCLsD%uL_qd@E3Q!)$O@`8t$ zI~emLk`H8_&#Jw?{_M+P9CQ*M%L))E!&JzZTev(6?fGqfruF>FbzVNMmRtAT&Hkfr zix7fz+Kh^lc!MNpFxr$bAe$cOYv-+#&Q6A>T7K&&o@osL#6i+&x+_p>(xC~rLiy8c zybI5`m{xkfqHKx9M$(%aIFj#U2$=w7sV9>*>&*g(*rE|4-=2Wce>)>p2-Mk>wrYY^ z{Imy3gCEL4L&UG(tn-c`uB2w%!T;S$P1cCfJ?pL@B3j>W#-wWRHem4g-EcV7$L!b}tuW7D+CH(r9^?GCO(~Mg2|M8H4K3>- z905DHb0X0GPiamita&Avv=-L~7T1^iCr6x4zz#n^GsHUi!J)39=x^*XE!?W z<`g&A@PyZiO#} z`(A9MvC|HT16@uZ8vn39tOOB}|KUfj{TKSBG)MBVfw1X*`cs)Rm?g(+9Eyo&F-t=YGY^nUVd`2F+kS zr$)v4DBIjB8flU+3kO$!*~J$u046zvBBNPvY&GOL@HVFAYpD3KbOB@j=#P-zd}k5- z&cQTz(P$=gKOFMc$7wb9RzH$V=J$`+@e3uUJx;=gkUcJbx zv>8BuIdA|PiU)+aW59nxPVj{0DQnfZ-M%7$A-NMlH`axP4lk#$o1j&viS~lKiP;7Y z-lB_c!FP%|M|*@i-dfr{{}*|d+MKKHcVTjlku;oXm`aAssv4alUJO^V35%0dQkIqf zZF!(w-f*Lyq1Gaa6#)t0egDUFS@1P#Mn_R%L`zl>Vy?Ts(kHP8-*)TII(f#;FYK-)8Y{*!+eSRyrl#@gTve6w_T#Pv|5!Qpz`PHv?cRXI&qRZs z-cGDx!L$34kf1stb-^aG4Jum-$t(`#mrX?%XTXSKoZUIiOkNIpj@vNObIp>7zUk8?Bou4-Q3?+z9qLf)@XC}f301S)7i zB`lB}rsn=bHI7NYSwM-qF+qI!7H!(cjDyTl0Yoyot6hd6Td7^M(!ZjqVVC!7*Gbh| zz|*U;aj)5Kq1pnaXqb%8jAWelq06PxuYC;6V%~5)j3Q8QJSzS^WfNx*f)o71wgLBH zCw;1-(*t$To~3?iGi25sK9W)0cOBH*K=cEVoa)$uK16KzXVD+RFX1?!$$YVQ>E4(R zy6$G{+oAG@Fy+r%`)8M=;~aC&1wJ}VP^~I_G?Wd{9%l9;im$-P8b8yKM`8qUl0MQ) zQbS}_$5v+^?*kxG(q9N%hVvla0UJx8aZj|=dlHN*sj<#J;y)Bc#+(kMINOM!hbos( zJcYqhGx0q^>8`zl!&B%;_Q!PwQmECTy;p=l-_Ad1U`5RxEz|Ceph_4TaszNt;pf_* zf~;Q>I{Sou>Dpg|dNp!p$_dhYUh$~NYZ#A;@XYr<@UK8EMw^PGeUuE+5xhL1YLyLJ zhmwAYJWQ9E>JG2aNRXE?{S%dmX6}SAPJvt+2&lW){Eq6Syww%`ZU}u?PDj3f1j{pv zY<`LHFzy~{Ws)qi&QFys3vUpPYuQ%s9hI?u_-Rs<{E-OZ%#;uK-)@p{T zQmF7F|8*#&F^d$q%T+<}#?g%Zb>5(p*2Y%6NlIt-Ra98J1RhAsT9uZu5K;-#LGKSH z27xHl?mPJ1MR+mCV78frIFVq+=a3inv?Rcn3*;ddr->ygunUjKxN$+)q8t-G;VxrggHY&qRR`Wa~ zDte7;YJHmiVEgDt%l!vRd%Y#MKs&6GkO$Du;{ZgG=FngTEntdQ^t zuFl}y`Fp)x3D z5%t#^2_ea2WLE4Miucek>_}@;m$FdpYNloRTUpbEa%mYl6U}n`<4_sRM0yYNC-R`2 z3Fl%9jKPyY!~j1KBkv0ePce&iN_ckJK)^6NUjisia9I^kA!@9w7X;J@Ke6=lI$i%O z7%Yb~1pjdieV5(VmqM1|Rtoa86pdiR#bG4VB88VB%^OeaVcuX+FyKM}5Jk}-F#O@^ z5ArTyHQ`XpibV4wO7u8sVtn!}Fm5*5@Qu{|A{O75K!EyBN7<6>PpiycX+)~I+5W^$nA({9wj9kADl9i<=)sq7Z=wV=X0?}lltD!VJq zeGF)jmikf=%c`qtU6MQG^>iL$xI6J^0EsVoo=DuJ$nYpq^z_762|^;-Gi4;buaB7F zt1IlkSKtnzi|90j_Xv5WBn?smIFq|8*6R8+?r&3GTWfr6(|)mx8yYe)h9EPBR9EDK zO|HIffUAY_<&@(EsQ#9~Iq`{`5D)zLP&g`FajjFobH>N<`wx2uX`Y~UIV zy{yu(UIS!I+A17v{eNZJ7zC|m20<+nEc4t<=M_=h9clx4XHFJdk_7sb5Ywqa3Z1Uf zYwX#el>XmWo0{)SpD9;|c18)+{YUPZEP9zepk$>4D`Kzlt66v{<0ZEKa_V%)dDR>G z>{tB80;mQfsBu5Zg>#0&(shZl15-MaLW%s0rIMIe)K!y-jKbqm*aQF$4kYI^lG(~a z;<*ZnyX760tpphW2z*VoM?`v_CwbL7P9WmkXW19sRg0D@yCK;(T(IFJK_SG{kLuUY zZ0nW3Vf23Wz_Joz&_zg12k1F5m8l=b<143yez(rWl8zq_pbUk|e>+9$-*)-hCy9AC zQ#9x|NJbQm@(j@8tAX;@@cnDSisL)9;3<@;a)O7M1HgZ%lhOy6y%i*X@=o zna)FF_QI1@B9L0cp~B*#_PR8U|19Q55nOeibXLrThBo`_&HTwzAy;TM!D zEIjGuCLX|~+NJvN)7YL>sNMr7E{Do+Z_fvdlgAgn&IM{b)kE}KqL6gM>Wrd|spu<& z%R(Y4p>h4}Dm57osUm7YsT$Wj&}tr%`t-wQipI?V#oWJUqW9|q%dbzh=+4sFXjk}2 zs07OaidKFpTtEpx$5W6NA;s(r21LuoQN-A=#nCw#a0h=Y8NNyh!E1lr*t2z7J2sc3r;nm^mE zyOZn^P>7XAPh6s^6y4V89qUc^1G<~advNWe>4Zrrd%iAb zIJbg@%rPaNC#L>ir^KAavHY}wnA!!r)D zPTp+as|@}SKkv2J;5u=}b2ZBvL|D)TQ{`m1A?Wt|_kw-CS% z>N3Jv3yv{mhXAO0dJZ2bT11Ad)058aqacY5@YoXmb|!u34RNfoezc9-F|C}`!h(gX zd}FT}MIEY6(2zpOiQPF?APoBf9s-u?I;U=7e9(zAYdD(YVNK>_;OZ7;jNc>IZCH4&!k_@P;!oDFwwYBWOtNHE85SEIpF<4nUdN&4|6n;Qc3 zj7v|Z%1q5ucbv{o^$-BaG#@tfvPTzRV^%(G3IquY_=iPlTu}20hs8a@Ga}rkA>141 z9Kx{I-t829?lv@0A439w;PHcDKMMZ3W*m`B#9xcpf1TXG7`*Vyf5K+bqU`Mx39>_r zAi1_6I&JE^@96*+&|n)#(13UQ#W~1XgRx^yLu~3B80Q^G_*OL}Ho#Zd{FC0&^pKS7 zAo~p8&_lDU&+)1u7r!c9+oC`sZGDFE&&J!IV!dNtXe~re+j_T6FU46_=U;;Yrr4z@ z?TtMDN%)rhhJl!%rb*v}le2`Vl18QBfk>>!O5YyFLY0FI9)2lPZpDSfB*6`vjuc~Y zOKk`8SE<*0i~*$!L@sn_@}THQ*MF<9cyY!^(-AR`ueg~DL9EX_{&isSqNOmvZ!2(I z&CWEgEV+$n*S}f@b~h}}0jlq3w>=(!K*XRz>ZZMIp)o(dUg$i+{*UZi3^|!MXn(>R z-jg8#>V{omyEed5K#lUS!{<08f0B zH)*o-zcoj%FLn|D-OGY=L(ZfBYf*p541sR|tCO4eahL+tb*p``u(D)MJR(f}I48{=s!?Wpsh+hx{y7v0)ZnZxnRGfT=>gkceidr7j+9I2urFvIaL zJ6?B_xMH!|3#Gy|6M=su!}V;b#-@NLHv=WmCDY z0isoVWN8rO4iWO0>>(Ezu_r=Yuq9$`(q;=X{6_RKJ}F~k>OvK0Q#>rf_pp^FlZIvk zf6_k5bxr>4=Fx}jdbdC-CpOY)-m}b7IAQ?`e8K$T<-u7{>p6pS$g*6N?lrgEWP&*D zNWatV+^W2Z0;saNlILVf0n~YcivQejC5Hx|;+NuBJ#Ap#&E|x) z*4T31llLn6ne?Nq%f{3gA0x%qL=pj5YCeli3rHh-%i%^pDHf#q^TuJF-T)bM_qgl7 ze|_s(`KQj#qC1fe?2`-sRFj?sQ-eurd7zET|AtKZ-9thDNp|*{v!aT4+ND|DB{xQQ zow^`z&{KpyyiuKpqmKCe-;1O-M0~aZSXdc>xS)Kb-Ob!Dl&OE0&;%8Zv$F;X^_Cz3 z&Dz_4mvjch<6OK6`#^Bsrv6=Z#4BcYEo{AoZ_#1u<(`58-A=vMM*Au2Y>plP+)N3- zA2CPxAm4HrH5J2w?%2pY5ZCzzVb+eandu%~y7XMrVEH*uh0Xi$(zhB@qm4NGd`+6C z)UMQwh$;0wQqg*Ix4ThD1uyzLYAGwd1AYM~R?$k}&%x(yq1LexGakAnhm%q6zHg^z z3M`mGab+n6XOTZzN_*`#=bouII~ExEec!Lk0Dt0+{V=-%#koDvV=Rz9J>`u!HZe%Q zdAyVb&Tsl{AE=%{x@cwHtp3sXJGanAkqF*(_z3opE#QOOYf|kw~hz*d}jxKa8tgJ<<}6*?H;p`U^Z74Y=4h#@dFh{A_hoW_K{1?LruW3}>ANTNMNkV{Pr!pc zyoLHUHMiiu`8>aonbTF?$ej<}b#NXMpchEW(f>Xw*Nu7wu8I8V=%1h_qoxxYGetoS zq>*E6&#eY=H}1ip?tU)%ttHE&5&dcKaEYkn)ZelBBgqywd9gp7yn=abW__=AT)!F4 z&?9$l@lR)}tPfQfunUTatevBKleS4mYN>rBfJtXnd?yRD;|vXqiE`^uaywY6c@i1( zhE0ASNmeuB!9g`{mXnLTg+{FPW1$e^T_bt`iT$>h2yHJouN>c{cpVSxR?{YI+0j}bAFD29PNyMaHgmq3LTU0XBc{QNJ`vY0!bS;WxhJYqm3J;jjz__O;O-i(Cy zspQ`UlxO%2VsHI=gYJzGszys;@(A0`I%pB+_4L*=!#$MZ`7v0NbK7owj+k$Lx(ZNs zZf?dytbrN%5Uh#%kxh!7zuwT7KHG z_Q(^u091aZ&Ux-g*Y07O4?T0UWqdhI%!k|qeuU?sSnvbv%YU*31ru|-Ge%hCj3+N?-?Ks zL^K?cz@d@vwqUAECVEotA=eN2>Bt`u`2{{F68~iZ&PMnUC8;O@@9qB1fvkO$hr_sX zV^k#(`BzE-i*_<-utJvyNR9U;ezh%gBVz7%K03yRU)kKT-4KTI_ zKlXND3O#L7YgU-4D; z5>7631Ji$jO~ulh0M`I45M#Md4~x@8ookyjW1#x|aX&aF1bMGLBmTC`vcY5gIY|_C zZ`NH00V67y(*pl(9xD#MLYD#nAj&_^0tCT(icI=}&7Ewny3-S<6^U)b@*VF46V6rY zm|%q`p*yu>&)>#{?jEYG&P#vu$;b8^vPTNcSqd=<>&T8F9fVyGs^QH}SoCknYYY#U zK!SMutZyp|CDHeph*ow|z(erY%lvmx5N@@+eGPVu?Na?4m)B@AUXeSnBr3J0yd=bl zb@PoO4TSJi^n9Ky9idP6xB)&#-s|%empCxI(*N^ESb%QcnL$A}VZaiESbJ_r%^0cUx966cs)A^ec|bB(9`gupu=WUAvlIo4EF5e`oR zPZ20d5hw{v^4FUcy{c~0z=7{)it}mUsv}WJqt6to@Ud`X(OI6%9L z4xO6B@Q@lHl|Kb#xyYmo$${z|uL%KjLPX)jDy0?mP1#fm^t%A=^EIr_wKfCj|?Bw8zia+o(__LvqEbp?W@|L6Y@vMlre{(lHLZTIq4amjgq z|L!#1S}bTo%PCJ�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
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/autotest/ogr/ogr_xodr.py b/autotest/ogr/ogr_xodr.py new file mode 100644 index 000000000000..43259ad77eb1 --- /dev/null +++ b/autotest/ogr/ogr_xodr.py @@ -0,0 +1,308 @@ +#!/usr/bin/env pytest +# -*- coding: utf-8 -*- +############################################################################### +# +# Project: GDAL/OGR Test Suite +# Purpose: XODR driver testing. +# Author: Michael Scholz, German Aerospace Center (DLR) +# Gülsen Bardak, German Aerospace Center (DLR) +# +############################################################################### +# Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +############################################################################### +import gdaltest +import pytest + +from osgeo import gdal, ogr + +pytestmark = pytest.mark.require_driver("XODR") +xodr_file = "data/xodr/5g_living_lab_A39_Wolfsburg-West.xodr" + + +def test_ogr_xodr_test_ogrsf(): + + import test_cli_utilities + + if test_cli_utilities.get_test_ogrsf_path() is None: + pytest.skip() + + ret = gdaltest.runexternal( + test_cli_utilities.get_test_ogrsf_path() + " -ro " + xodr_file + ) + + assert "INFO" in ret + assert "ERROR" not in ret + assert "FAILURE" not in ret + + +def test_ogr_xodr_basics(): + """Test basic capabilities: + - Data source + - Layer count + """ + ds = gdal.OpenEx(xodr_file, gdal.OF_VECTOR) + assert ds is not None, f"Cannot open dataset for file: {xodr_file}" + assert ds.GetLayerCount() == 6, f"Bad layer count for file: {xodr_file}" + + +def test_ogr_xodr_undissolvable_layers(): + """Test all point and linestring layers for: + - Correct feature type definitions + - Spatial reference system + """ + ds = gdal.OpenEx(xodr_file, gdal.OF_VECTOR) + + layer_reference_line = ds.GetLayer("ReferenceLine") + check_feat_def_reference_line(layer_reference_line) + check_spatial_ref(layer_reference_line) + + layer_lane_border = ds.GetLayer("LaneBorder") + check_feat_def_lane_border(layer_lane_border) + check_spatial_ref(layer_lane_border) + + layer_road_object = ds.GetLayer("RoadObject") + check_feat_def_road_object(layer_road_object) + check_spatial_ref(layer_road_object) + + +@pytest.mark.parametrize("dissolve_tin", [True, False]) +def test_ogr_xodr_dissolvable_layers(dissolve_tin: bool): + """Test all TIN layers for: + - Correct feature type definitions + - Spatial reference system + + Args: + dissolve_tin (bool): True if to dissolve triangulated surfaces. + """ + options = ["DISSOLVE_TIN=" + str(dissolve_tin)] + ds = gdal.OpenEx(xodr_file, gdal.OF_VECTOR, open_options=options) + + layer_road_mark = ds.GetLayer("RoadMark") + check_feat_def_road_mark(layer_road_mark, dissolve_tin) + check_spatial_ref(layer_road_mark) + + layer_lane = ds.GetLayer("Lane") + check_feat_def_lane(layer_lane, dissolve_tin) + check_spatial_ref(layer_lane) + + layer_road_signal = ds.GetLayer("RoadSignal") + check_feat_def_road_signal(layer_road_signal, dissolve_tin) + check_spatial_ref(layer_road_signal) + + +def check_feat_def_reference_line(layer): + assert ( + layer.GetGeomType() == ogr.wkbLineString25D + ), "bad layer geometry type for ReferenceLine" + assert layer.GetFeatureCount() == 41 + assert layer.GetLayerDefn().GetFieldCount() == 3 + assert ( + layer.GetLayerDefn().GetFieldDefn(0).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTReal + and layer.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTString + ) + + +def check_feat_def_lane_border(layer): + assert ( + layer.GetGeomType() == ogr.wkbLineString25D + ), "bad layer geometry type for LaneBorder" + assert layer.GetFeatureCount() == 230 + assert layer.GetLayerDefn().GetFieldCount() == 5 + assert ( + layer.GetLayerDefn().GetFieldDefn(0).GetType() == ogr.OFTInteger + and layer.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(3).GetType() == ogr.OFTInteger + and layer.GetLayerDefn().GetFieldDefn(4).GetType() == ogr.OFTInteger + ) + + +def check_feat_def_road_mark(layer, dissolve_tin: bool): + if not dissolve_tin: + assert ( + layer.GetGeomType() == ogr.wkbTINZ + ), "bad layer geometry type for RoadMark" + else: + assert ( + layer.GetGeomType() == ogr.wkbPolygon25D + ), "bad layer geometry type for dissolved RoadMark" + assert layer.GetFeatureCount() == 424 + assert layer.GetLayerDefn().GetFieldCount() == 3 + assert ( + layer.GetLayerDefn().GetFieldDefn(0).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTInteger + and layer.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTString + ) + + +def check_feat_def_road_object(layer): + assert layer.GetGeomType() == ogr.wkbTINZ, "bad layer geometry type for RoadObject" + assert layer.GetFeatureCount() == 273 + assert layer.GetLayerDefn().GetFieldCount() == 4 + assert ( + layer.GetLayerDefn().GetFieldDefn(0).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(3).GetType() == ogr.OFTString + ) + + +def check_feat_def_lane(layer, dissolve_tin: bool): + if not dissolve_tin: + assert layer.GetGeomType() == ogr.wkbTINZ, "bad layer geometry type for Lane" + else: + assert ( + layer.GetGeomType() == ogr.wkbPolygon25D + ), "bad layer geometry type for dissolved Lane" + assert layer.GetFeatureCount() == 174 + assert layer.GetLayerDefn().GetFieldCount() == 5 + assert ( + layer.GetLayerDefn().GetFieldDefn(0).GetType() == ogr.OFTInteger + and layer.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(3).GetType() == ogr.OFTInteger + and layer.GetLayerDefn().GetFieldDefn(4).GetType() == ogr.OFTInteger + ) + + +def check_feat_def_road_signal(layer, dissolve_tin: bool): + if not dissolve_tin: + assert ( + layer.GetGeomType() == ogr.wkbTINZ + ), "bad layer geometry type for RoadSignal" + else: + assert ( + layer.GetGeomType() == ogr.wkbPoint25D + ), "bad layer geometry type for dissolved RoadSignal" + assert layer.GetFeatureCount() == 50 + assert layer.GetLayerDefn().GetFieldCount() == 10 + assert ( + layer.GetLayerDefn().GetFieldDefn(0).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTString + and layer.GetLayerDefn().GetFieldDefn(3).GetType() == ogr.OFTString + ) + + +def check_spatial_ref(layer): + srs_proj4 = layer.GetSpatialRef().ExportToProj4() + expected_proj4 = ( + "+proj=utm +zone=32 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs" + ) + assert srs_proj4 == expected_proj4, "bad spatial reference system" + + +@pytest.mark.parametrize("eps", [1.0, 0.1]) +def test_ogr_xodr_geometry_eps(eps: float): + """Test correct geometry creation for different values of open option EPS. + + Args: + eps (float): Value for linear approximation of parametric geometries. + """ + options = ["EPSILON=" + str(eps)] + ds = gdal.OpenEx(xodr_file, gdal.OF_VECTOR, open_options=options) + + lyr = ds.GetLayer("ReferenceLine") + ogr_xodr_check_reference_line_geometry_eps(lyr, eps) + + +def ogr_xodr_check_reference_line_geometry_eps(lyr, eps: float): + lyr.ResetReading() + feat = lyr.GetNextFeature() + wkt = feat.GetGeometryRef().ExportToWkt() + if eps == 1.0: + assert ( + wkt + == "LINESTRING (618251.572934302 5809506.96459625 102.378603962182,618254.944363001 5809506.95481165 102.371268481462,618258.290734177 5809506.56065761 102.363999939623)" + ), f"wrong geometry created for ReferenceLine with EPS {str(eps)}" + elif eps == 0.1: + assert ( + wkt + == "LINESTRING (618251.572934302 5809506.96459625 102.378603962182,618254.944363001 5809506.95481165 102.371268481462,618257.937110798 5809506.62607284 102.364759846201,618258.290734177 5809506.56065761 102.363999939623)" + ), f"wrong geometry created for ReferenceLine with EPS {str(eps)}" + + +@pytest.mark.parametrize("dissolve_tin", [True, False]) +def test_ogr_xodr_geometry_dissolve(dissolve_tin: bool): + """Test correct geometry creation for different values of open option DISSOLVE_TIN. + + Args: + dissolve_tin (bool): True if to dissolve triangulated surfaces. + """ + options = ["DISSOLVE_TIN=" + str(dissolve_tin)] + ds = gdal.OpenEx(xodr_file, gdal.OF_VECTOR, open_options=options) + + lyr = ds.GetLayer("Lane") + ogr_xodr_check_lane_geometry_dissolve(lyr, dissolve_tin) + + lyr = ds.GetLayer("RoadMark") + ogr_xodr_check_road_mark_geometry_dissolve(lyr, dissolve_tin) + + lyr = ds.GetLayer("RoadSignal") + ogr_xodr_check_road_signal_geometry_dissolve(lyr, dissolve_tin) + + +def ogr_xodr_check_lane_geometry_dissolve(lyr, dissolve_tin: bool): + lyr.ResetReading() + feat = lyr.GetNextFeature() + wkt = feat.GetGeometryRef().ExportToWkt() + if not dissolve_tin: + assert ( + wkt + == "TIN Z (((618251.708293914 5809503.30115552 102.206436434521,618253.406110685 5809502.59383908 102.162274831603,618253.40871869 5809503.08668632 102.186041767762,618251.708293914 5809503.30115552 102.206436434521)),((618251.708293914 5809503.30115552 102.206436434521,618251.726901715 5809502.7975446 102.182768671482,618253.406110685 5809502.59383908 102.162274831603,618251.708293914 5809503.30115552 102.206436434521)),((618253.40871869 5809503.08668632 102.186041767762,618254.710111278 5809502.39980074 102.146632509166,618254.735144074 5809502.88656198 102.170637739305,618253.40871869 5809503.08668632 102.186041767762)),((618253.40871869 5809503.08668632 102.186041767762,618253.406110685 5809502.59383908 102.162274831603,618254.710111278 5809502.39980074 102.146632509166,618253.40871869 5809503.08668632 102.186041767762)),((618254.735144074 5809502.88656198 102.170637739305,618256.354637481 5809502.1051039 102.128452978327,618256.414547031 5809502.56472816 102.151918900654,618254.735144074 5809502.88656198 102.170637739305)),((618254.735144074 5809502.88656198 102.170637739305,618254.710111278 5809502.39980074 102.146632509166,618256.354637481 5809502.1051039 102.128452978327,618254.735144074 5809502.88656198 102.170637739305)),((618256.414547031 5809502.56472816 102.151918900654,618257.381896193 5809501.87667676 102.118091279345,618257.465586929 5809502.30800315 102.140735883984,618256.414547031 5809502.56472816 102.151918900654)),((618256.414547031 5809502.56472816 102.151918900654,618256.354637481 5809502.1051039 102.128452978327,618257.381896193 5809501.87667676 102.118091279345,618256.414547031 5809502.56472816 102.151918900654)))" + ), "wrong geometry created for Lane" + else: + assert ( + wkt + == "POLYGON ((618257.381896193 5809501.87667676 102.118091279345,618256.354637481 5809502.1051039 102.128452978327,618254.710111278 5809502.39980074 102.146632509166,618253.406110685 5809502.59383908 102.162274831603,618251.726901715 5809502.7975446 102.182768671482,618251.708293914 5809503.30115552 102.206436434521,618253.40871869 5809503.08668632 102.186041767762,618254.735144074 5809502.88656198 102.170637739305,618256.414547031 5809502.56472816 102.151918900654,618257.465586929 5809502.30800315 102.140735883984,618257.381896193 5809501.87667676 102.118091279345))" + ), "wrong geometry created for dissolved Lane" + + +def ogr_xodr_check_road_mark_geometry_dissolve(lyr, dissolve_tin: bool): + lyr.ResetReading() + feat = lyr.GetNextFeature() + wkt = feat.GetGeometryRef().ExportToWkt() + if not dissolve_tin: + assert ( + wkt + == "TIN Z (((618251.72468874 5809502.85743767 102.185583413892,618252.578130818 5809502.64753279 102.169882217474,618252.576002918 5809502.76737822 102.175586986359,618251.72468874 5809502.85743767 102.185583413892)),((618251.72468874 5809502.85743767 102.185583413892,618251.72911469 5809502.73765153 102.179953929071,618252.578130818 5809502.64753279 102.169882217474,618251.72468874 5809502.85743767 102.185583413892)),((618252.576002918 5809502.76737822 102.175586986359,618253.405793556 5809502.53390956 102.159384806253,618253.406427815 5809502.6537686 102.165164856953,618252.576002918 5809502.76737822 102.175586986359)),((618252.576002918 5809502.76737822 102.175586986359,618252.578130818 5809502.64753279 102.169882217474,618253.405793556 5809502.53390956 102.159384806253,618252.576002918 5809502.76737822 102.175586986359)),((618253.406427815 5809502.6537686 102.165164856953,618253.747583384 5809502.4836466 102.15508610511,618253.749521849 5809502.6034901 102.160897877637,618253.406427815 5809502.6537686 102.165164856953)),((618253.406427815 5809502.6537686 102.165164856953,618253.405793556 5809502.53390956 102.159384806253,618253.747583384 5809502.4836466 102.15508610511,618253.406427815 5809502.6537686 102.165164856953)),((618253.749521849 5809502.6034901 102.160897877637,618254.085085834 5809502.43409623 102.150979368988,618254.088411764 5809502.55390772 102.156822862935,618253.749521849 5809502.6034901 102.160897877637)),((618253.749521849 5809502.6034901 102.160897877637,618253.747583384 5809502.4836466 102.15508610511,618254.085085834 5809502.43409623 102.150979368988,618253.749521849 5809502.6034901 102.160897877637)),((618254.088411764 5809502.55390772 102.156822862935,618254.707033446 5809502.33995247 102.143681017939,618254.713189111 5809502.45964901 102.149584000393,618254.088411764 5809502.55390772 102.156822862935)),((618254.088411764 5809502.55390772 102.156822862935,618254.085085834 5809502.43409623 102.150979368988,618254.707033446 5809502.33995247 102.143681017939,618254.088411764 5809502.55390772 102.156822862935)),((618254.713189111 5809502.45964901 102.149584000393,618255.243094449 5809502.25186128 102.137539289407,618255.251990439 5809502.3713828 102.143494733244,618254.713189111 5809502.45964901 102.149584000393)),((618254.713189111 5809502.45964901 102.149584000393,618254.707033446 5809502.33995247 102.143681017939,618255.243094449 5809502.25186128 102.137539289407,618254.713189111 5809502.45964901 102.149584000393)),((618255.251990439 5809502.3713828 102.143494733244,618256.346892323 5809502.04568328 102.125419284058,618256.362382638 5809502.16452451 102.131486672596,618255.251990439 5809502.3713828 102.143494733244)),((618255.251990439 5809502.3713828 102.143494733244,618255.243094449 5809502.25186128 102.137539289407,618256.346892323 5809502.04568328 102.125419284058,618255.251990439 5809502.3713828 102.143494733244)),((618256.362382638 5809502.16452451 102.131486672596,618256.86502563 5809501.93528991 102.120031826125,618256.884079624 5809502.05360925 102.126153745722,618256.362382638 5809502.16452451 102.131486672596)),((618256.362382638 5809502.16452451 102.131486672596,618256.346892323 5809502.04568328 102.125419284058,618256.86502563 5809501.93528991 102.120031826125,618256.362382638 5809502.16452451 102.131486672596)),((618256.884079624 5809502.05360925 102.126153745722,618257.370482622 5809501.81785335 102.11500305465,618257.393309764 5809501.93550017 102.12117950404,618256.884079624 5809502.05360925 102.126153745722)),((618256.884079624 5809502.05360925 102.126153745722,618256.86502563 5809501.93528991 102.120031826125,618257.370482622 5809501.81785335 102.11500305465,618256.884079624 5809502.05360925 102.126153745722)))" + ), "wrong geometry created for RoadMark" + else: + assert ( + wkt + == "POLYGON ((618253.747583384 5809502.4836466 102.15508610511,618253.405793556 5809502.53390956 102.159384806253,618252.578130818 5809502.64753279 102.169882217474,618251.72911469 5809502.73765153 102.179953929071,618251.72468874 5809502.85743767 102.185583413892,618252.576002918 5809502.76737822 102.175586986359,618253.406427815 5809502.6537686 102.165164856953,618253.749521849 5809502.6034901 102.160897877637,618254.088411764 5809502.55390772 102.156822862935,618254.713189111 5809502.45964901 102.149584000393,618255.251990439 5809502.3713828 102.143494733244,618256.362382638 5809502.16452451 102.131486672596,618256.884079624 5809502.05360925 102.126153745722,618257.393309764 5809501.93550017 102.12117950404,618257.370482622 5809501.81785335 102.11500305465,618256.86502563 5809501.93528991 102.120031826125,618256.346892323 5809502.04568328 102.125419284058,618255.243094449 5809502.25186128 102.137539289407,618254.707033446 5809502.33995247 102.143681017939,618254.085085834 5809502.43409623 102.150979368988,618253.747583384 5809502.4836466 102.15508610511))" + ), "wrong geometry created for dissolved RoadMark" + + +def ogr_xodr_check_road_signal_geometry_dissolve(lyr, dissolve_tin: bool): + lyr.ResetReading() + feat = lyr.GetNextFeature() + wkt = feat.GetGeometryRef().ExportToWkt() + if not dissolve_tin: + assert ( + wkt + == "TIN Z (((618366.844654328 5809540.96164437 103.568946384872,618366.840967264 5809541.48457345 103.54861591048,618367.044614501 5809540.96290705 103.56516023851,618366.844654328 5809540.96164437 103.568946384872)),((618366.840967264 5809541.48457345 103.54861591048,618367.040927437 5809541.48583613 103.544829764117,618367.044614501 5809540.96290705 103.56516023851,618366.840967264 5809541.48457345 103.54861591048)),((618366.858657359 5809540.99087441 104.318245603892,618367.058617531 5809540.99213709 104.31445945753,618366.854970294 5809541.51380349 104.297915129499,618366.858657359 5809540.99087441 104.318245603892)),((618366.854970294 5809541.51380349 104.297915129499,618367.058617531 5809540.99213709 104.31445945753,618367.054930467 5809541.51506617 104.294128983137,618366.854970294 5809541.51380349 104.297915129499)),((618366.854970294 5809541.51380349 104.297915129499,618367.054930467 5809541.51506617 104.294128983137,618366.840967264 5809541.48457345 103.54861591048,618366.854970294 5809541.51380349 104.297915129499)),((618366.840967264 5809541.48457345 103.54861591048,618367.054930467 5809541.51506617 104.294128983137,618367.040927437 5809541.48583613 103.544829764117,618366.840967264 5809541.48457345 103.54861591048)),((618367.058617531 5809540.99213709 104.31445945753,618366.858657359 5809540.99087441 104.318245603892,618367.044614501 5809540.96290705 103.56516023851,618367.058617531 5809540.99213709 104.31445945753)),((618367.044614501 5809540.96290705 103.56516023851,618366.858657359 5809540.99087441 104.318245603892,618366.844654328 5809540.96164437 103.568946384872,618367.044614501 5809540.96290705 103.56516023851)),((618366.844654328 5809540.96164437 103.568946384872,618366.858657359 5809540.99087441 104.318245603892,618366.854970294 5809541.51380349 104.297915129499,618366.844654328 5809540.96164437 103.568946384872)),((618366.854970294 5809541.51380349 104.297915129499,618366.840967264 5809541.48457345 103.54861591048,618366.844654328 5809540.96164437 103.568946384872,618366.854970294 5809541.51380349 104.297915129499)),((618367.044614501 5809540.96290705 103.56516023851,618367.054930467 5809541.51506617 104.294128983137,618367.058617531 5809540.99213709 104.31445945753,618367.044614501 5809540.96290705 103.56516023851)),((618367.044614501 5809540.96290705 103.56516023851,618367.040927437 5809541.48583613 103.544829764117,618367.054930467 5809541.51506617 104.294128983137,618367.044614501 5809540.96290705 103.56516023851)))" + ), "wrong geometry created for RoadSignal" + else: + assert ( + wkt == "POINT (618366.942790883 5809541.22374025 103.556888074495)" + ), "wrong geometry created for dissolved RoadSignal" diff --git a/cmake/helpers/CheckDependentLibraries.cmake b/cmake/helpers/CheckDependentLibraries.cmake index 6f9ee274bfb8..7335af0e9751 100644 --- a/cmake/helpers/CheckDependentLibraries.cmake +++ b/cmake/helpers/CheckDependentLibraries.cmake @@ -804,6 +804,8 @@ if (Arrow_FOUND) mark_as_advanced(ARROW_USE_STATIC_LIBRARIES) endif() +gdal_check_package(OpenDrive "Enable libOpenDRIVE" CAN_DISABLE) + # bindings # finding python in top of project because of common for autotest and bindings diff --git a/doc/source/development/building_from_source.rst b/doc/source/development/building_from_source.rst index 26bd2a714a12..aa18a4cea761 100644 --- a/doc/source/development/building_from_source.rst +++ b/doc/source/development/building_from_source.rst @@ -1157,6 +1157,20 @@ It is used by the internal libtiff library or the :ref:`raster.zarr` driver. Control whether to use LibLZMA. Defaults to ON when LibLZMA is found. +libOpenDRIVE +************ + +`libOpenDRIVE `_ is required for the :ref:`vector.xodr` driver. + +.. option:: OpenDrive_DIR + + Path to libOpenDRIVE CMake configuration directory ``/cmake/``. The :file:`cmake/` path is usually automatically created when installing libOpenDRIVE and contains the necessary configuration files for inclusion into other project builds. + +.. option:: GDAL_USE_OPENDRIVE=ON/OFF + + Control whether to use libOpenDRIVE. Defaults to ON when libOpenDRIVE is found. + + LibQB3 ****** @@ -1168,7 +1182,6 @@ by the :ref:`raster.marfa` driver. Control whether to use LibQB3. Defaults to ON when LibQB3 is found. - LibXml2 ******* @@ -1189,7 +1202,6 @@ capabilities in GMLJP2v2 generation. Control whether to use LibXml2. Defaults to ON when LibXml2 is found. - LURATECH ******** @@ -1569,7 +1581,7 @@ The Oracle Instant Client SDK (closed source/proprietary) is required for the .. option:: Oracle_ROOT - Path to the root directory of the Oracle Instant Client SDK + Path to the root directory of the Oracle Instant Client SDK. .. option:: GDAL_USE_ORACLE=ON/OFF @@ -1604,7 +1616,7 @@ Regular Expressions support. It is used for the REGEXP operator in drivers using .. option:: PCRE2_LIBRARY - Path to a shared or static library file with "pcre2-8" in its name + Path to a shared or static library file with "pcre2-8" in its name. .. option:: GDAL_USE_PCRE2=ON/OFF diff --git a/doc/source/drivers/vector/index.rst b/doc/source/drivers/vector/index.rst index 818b44db488f..6f1750b3336f 100644 --- a/doc/source/drivers/vector/index.rst +++ b/doc/source/drivers/vector/index.rst @@ -110,3 +110,4 @@ Vector drivers wfs xls xlsx + xodr diff --git a/doc/source/drivers/vector/xodr.rst b/doc/source/drivers/vector/xodr.rst new file mode 100644 index 000000000000..d1e526671767 --- /dev/null +++ b/doc/source/drivers/vector/xodr.rst @@ -0,0 +1,198 @@ +.. _vector.xodr: + +XODR -- OpenDRIVE Road Description Format +========================================= + +.. versionadded:: 3.10 + +.. shortname:: XODR + +.. build_dependencies:: libOpenDRIVE >= 0.5.0, GEOS + +This driver provides read access to road network elements of OpenDRIVE datasets. + +`OpenDRIVE `_ is an open industry format for lane-detailed description of road networks, commonly used in applications of the automotive and transportation systems domains. It bundles polynomial, continuous road geometry modelling with all necessary topological links and semantic information from traffic-regulating infrastructure (signs and traffic lights). + +Driver capabilities +------------------- + +.. supports_georeferencing:: + +Specification version +--------------------- + +The currently supported OpenDRIVE version is 1.4 and depends on what is provided by libOpenDRIVE_. + +.. _libOpenDRIVE: https://github.com/pageldev/libOpenDRIVE/ + +Supported OpenDRIVE elements +++++++++++++++++++++++++++++ + +The XODR driver exposes OpenDRIVE's different road elements as separate layers by converting geometric elements into 3-dimensional OGR geometry types. The following _`layer types` are currently implemented: + +* *ReferenceLine*: Road reference line (````) as :cpp:class:`OGRLineString`. +* *LaneBorder*: Outer road lane border as :cpp:class:`OGRLineString`. +* *Lane*: Polygonal surface (TIN) of the lane mesh as :cpp:class:`OGRTriangulatedSurface`. +* *RoadMark*: Polygonal surface (TIN) of the road mark mesh as :cpp:class:`OGRTriangulatedSurface`. +* *RoadObject*: Polygonal surface (TIN) of the road object mesh as :cpp:class:`OGRTriangulatedSurface`. +* *RoadSignal*: Polygonal surface (TIN) of the road signal mesh as :cpp:class:`OGRTriangulatedSurface`. + +Spatial reference +----------------- + +By definition, OpenDRIVE geometries are always referenced in a Cartesian coordinate system which defaults to having its origin at ``(0, 0, 0)``. If real-world coordinates are used, the OpenDRIVE header provides a PROJ.4 string with the definition of a projected spatial reference system: + +:: + +
    + +
    + +The XODR driver uses this PROJ definition as spatial reference for creation of all OGR geometry layers. + +Limitations +----------- + +The supported content encoding of OpenDRIVE XML files is limited to what pugixml is able to automatically guess (see `4.6. Encodings `_). The default fallback encoding is UTF-8. + +Open options +------------ + +The following open options can be specified +(typically with the ``-oo name=value`` parameters of :program:`ogrinfo` or :program:`ogr2ogr`): + +- .. oo:: EPSILON + :choices: + :default: 1.0 + + Epsilon value ``> 0.0`` for linear approximation of continuous OpenDRIVE geometries. A smaller value results in a finer sampling. This parameter corresponds to libOpenDRIVE's ``eps`` parameter. + +- .. oo:: DISSOLVE_TIN + :choices: YES, NO + :default: NO + + Whether to dissolve triangulated surfaces. By setting this option to YES, the TIN layers *Lane* and *RoadMark* of geometry type :cpp:class:`OGRTriangulatedSurface` will be simplified to single, simple :cpp:class:`OGRPolygon` geometries. This performs a :cpp:func:`UnaryUnion` which dissolves boundaries of all touching triangle patches and thus yields a slimmer dataset which often suffices for basic GIS usage. Be aware that this dissolving step increases processing time significantly. + Layer *RoadSignal* will be dissolved to a simple :cpp:class:`OGRPoint`. + +Examples +-------- + +- Translate OpenDRIVE road *ReferenceLine* elements (````) to :ref:`Shapefile ` using :program:`ogr2ogr`. The desired :ref:`layer type ` which is to be extracted from the dataset is specified as the last parameter of the function call. + + :: + + ogr2ogr -f "ESRI Shapefile" CulDeSac.shp CulDeSac.xodr ReferenceLine + +- Convert the whole OpenDRIVE dataset with all its different layers into a :ref:`GeoPackage ` using: + + :: + + ogr2ogr -f "GPKG" CulDeSac.gpkg CulDeSac.xodr + +- Convert the whole OpenDRIVE dataset with custom parameters :oo:`EPSILON` and :oo:`DISSOLVE_TIN` into a :ref:`GeoPackage `: + + :: + + ogr2ogr -f "GPKG" CulDeSac.gpkg CulDeSac.xodr -oo EPSILON=0.9 -oo DISSOLVE_TIN=YES + +Convenient usage through docker image +------------------------------------- + +To use the XODR driver through a docker image, first build the image from the corresponding docker directory + + :: + + cd /docker/ubuntu-full/ + docker build -t gdal/xodr -f Dockerfile . + +For general usage information refer to `GDAL Docker images `__. Usage examples: + +- Use :program:`ogrinfo` to extract detailed information about a local `xodr` file by mounting your current working directory (`$PWD`) containing the file into the Docker container: + + :: + + docker run --rm -v ${PWD}:/home -it gdal/xodr ogrinfo /home/.xodr + +- Use :program:`ogr2ogr` to convert a local `xodr` file into any other supported OGR output format. The result will be automatically available in your host machine's working directory which is mounted into the container: + + :: + + docker run --rm -v ${PWD}:/home -it gdal/xodr ogr2ogr -f "GPKG" /home/.gpkg /home/.xodr + + +Alternatively, you can run a docker container that enables using the XODR driver in an isolated workspace from within the container + + :: + + docker run --name -it gdal/xodr /bin/bash + + +General building notes +---------------------- + +Building of the driver as plugin is tested to work on + +* Ubuntu 24.04 using GCC +* Windows 10 using GCC 13.1.0 (with MCF threads) + MinGW-w64 11.0.0 (MSVCRT runtime), which is obtainable from `WinLibs `_. + +Ensure to meet the following driver dependencies: + +* PROJ +* GEOS +* libOpenDRIVE_ as shared library (built with CMake option ``-DBUILD_SHARED_LIBS=ON``) + +Then, after checking out GDAL sources with this driver extension, create the build directory: + + :: + + cd + mkdir build + cd build + +From the build directory configure CMake to activate our XODR driver as plugin: + + :: + + cmake .. -DOGR_ENABLE_DRIVER_XODR_PLUGIN=TRUE -DOpenDrive_DIR=/path/to/libOpenDRIVE/installdir/cmake/ + +.. note:: The :file:`cmake/` path is usually automatically created when installing libOpenDRIVE and contains the necessary configuration files for inclusion into other project builds. + +Now, build GDAL and install it: + + :: + + cmake --build . + cmake --build . --target install + +Afterwards you will find a new shared library file :file:`{path/to/GDAL/installdir}/lib/gdalplugins/ogr_XODR`. + +Verifying a successful build +++++++++++++++++++++++++++++ + +Check if XODR driver is found: + + :: + + cd /build/ + ./apps/ogrinfo --format XODR + +This should print basic capabilities of the driver: + + :: + + Format Details: + Short Name: XODR + Long Name: OpenDRIVE - Open Dynamic Road Information for Vehicle Environment + Supports: Vector + Supports: Open() - Open existing dataset. + + ... + + +If you are on Linux, depending on your environment, you might experience linker errors like: + + :: + + ERROR 1: libOpenDrive.so: cannot open shared object file: No such file or directory + +In such cases ensure that your environment variable ``LD_LIBRARY_PATH`` points to the corresponding install directories of libOpenDRIVE and GDAL and run ``ldconfig`` afterwards. diff --git a/docker/alpine-normal/Dockerfile b/docker/alpine-normal/Dockerfile index 781a0d392c29..d4f79ecdeb5e 100644 --- a/docker/alpine-normal/Dockerfile +++ b/docker/alpine-normal/Dockerfile @@ -113,6 +113,24 @@ RUN if test "${HDF4_VERSION}" != "" -a "$(uname -m)" = "x86_64"; then ( \ && apk del byacc flex portablexdr-dev \ ); fi +# Build libOpenDRIVE +ARG OPENDRIVE_VERSION=0.5.0-gdal +RUN if test "${OPENDRIVE_VERSION}" != ""; then ( \ + wget -q https://github.com/DLR-TS/libOpenDRIVE/archive/refs/tags/${OPENDRIVE_VERSION}.tar.gz \ + && tar xzf ${OPENDRIVE_VERSION}.tar.gz \ + && rm -f ${OPENDRIVE_VERSION}.tar.gz \ + && cd libOpenDRIVE-${OPENDRIVE_VERSION} \ + && cmake . -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/ \ + && make -j$(nproc) \ + && make install \ + && mkdir -p /build_thirdparty/usr/lib \ + && cp -P /usr/lib/libOpenDrive*.so* /build_thirdparty/usr/lib \ + && for i in /build_thirdparty/usr/lib/*; do strip -s $i 2>/dev/null || /bin/true; done \ + && cd .. \ + && rm -rf libOpenDRIVE-${OPENDRIVE_VERSION} \ + ); fi + RUN apk add --no-cache rsync ccache ARG RSYNC_REMOTE @@ -239,6 +257,8 @@ RUN if test "${GDAL_VERSION}" = "master"; then \ -DIconv_INCLUDE_DIR=/usr/include/gnu-libiconv \ -DIconv_LIBRARY=/usr/lib/libiconv.so \ -DBUILD_TESTING=OFF \ + -DOpenDrive_DIR=/usr/lib/ \ + -DOGR_ENABLE_DRIVER_XODR_PLUGIN=TRUE \ && make -j$(nproc) \ && make install DESTDIR="/build" \ && (make -j$(nproc) multireadtest && cp apps/multireadtest /build/usr/bin) \ diff --git a/docker/ubuntu-full/Dockerfile b/docker/ubuntu-full/Dockerfile index afe807ac8115..14fbb356bb5a 100644 --- a/docker/ubuntu-full/Dockerfile +++ b/docker/ubuntu-full/Dockerfile @@ -173,6 +173,25 @@ RUN . /buildscripts/bh-set-envvars.sh \ && rm -rf openjpeg-${OPENJPEG_VERSION} \ ); fi +# Build libOpenDRIVE +ARG OPENDRIVE_VERSION=0.5.0-gdal +RUN . /buildscripts/bh-set-envvars.sh \ + && if test "${OPENDRIVE_VERSION}" != ""; then ( \ + wget -q https://github.com/DLR-TS/libOpenDRIVE/archive/refs/tags/${OPENDRIVE_VERSION}.tar.gz \ + && tar xzf ${OPENDRIVE_VERSION}.tar.gz \ + && rm -f ${OPENDRIVE_VERSION}.tar.gz \ + && cd libOpenDRIVE-${OPENDRIVE_VERSION} \ + && cmake . -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/ \ + && make -j$(nproc) \ + && make install \ + && mkdir -p /build_thirdparty/usr/lib \ + && cp -P /usr/lib/libOpenDrive*.so* /build_thirdparty/usr/lib \ + && for i in /build_thirdparty/usr/lib/*; do strip -s $i 2>/dev/null || /bin/true; done \ + && cd .. \ + && rm -rf libOpenDRIVE-${OPENDRIVE_VERSION} \ + ); fi + #Build File Geodatabase ARG WITH_FILEGDB= diff --git a/docker/ubuntu-full/bh-gdal.sh b/docker/ubuntu-full/bh-gdal.sh index 0c5493445441..4524d01ad4f3 100755 --- a/docker/ubuntu-full/bh-gdal.sh +++ b/docker/ubuntu-full/bh-gdal.sh @@ -72,7 +72,9 @@ wget -q "https://github.com/${GDAL_REPOSITORY}/archive/${GDAL_VERSION}.tar.gz" \ -DGDAL_ENABLE_PLUGINS=ON \ -DGDAL_USE_TIFF_INTERNAL=ON \ -DBUILD_PYTHON_BINDINGS=ON \ - -DGDAL_USE_GEOTIFF_INTERNAL=ON ${GDAL_CMAKE_EXTRA_OPTS} + -DGDAL_USE_GEOTIFF_INTERNAL=ON ${GDAL_CMAKE_EXTRA_OPTS} \ + -DOpenDrive_DIR=/usr/lib/ \ + -DOGR_ENABLE_DRIVER_XODR_PLUGIN=TRUE \ make "-j$(nproc)" make install DESTDIR="/build" diff --git a/frmts/drivers.ini b/frmts/drivers.ini index 116fdab02b9d..4bcea36fd07d 100644 --- a/frmts/drivers.ini +++ b/frmts/drivers.ini @@ -263,6 +263,7 @@ GTFS PMTiles JSONFG MiraMonVector +XODR # Put TIGER and AVCBIN at end since they need poOpenInfo->GetSiblingFiles() Tiger diff --git a/frmts/gdalallregister.cpp b/frmts/gdalallregister.cpp index 0e1b73b85fc2..b7c37bd68bad 100644 --- a/frmts/gdalallregister.cpp +++ b/frmts/gdalallregister.cpp @@ -314,6 +314,9 @@ void CPL_STDCALL GDALAllRegister() #if defined(DEFERRED_ZARR_DRIVER) DeclareDeferredZarrPlugin(); #endif +#if defined(DEFERRED_XODR_DRIVER) + DeclareDeferredOGRXODRPlugin(); +#endif // AutoLoadDrivers is a no-op if compiled with GDAL_NO_AUTOLOAD defined. poDriverManager->AutoLoadDrivers(); diff --git a/ogr/ogrsf_frmts/CMakeLists.txt b/ogr/ogrsf_frmts/CMakeLists.txt index f11285935dcb..690e4a0ef339 100644 --- a/ogr/ogrsf_frmts/CMakeLists.txt +++ b/ogr/ogrsf_frmts/CMakeLists.txt @@ -70,6 +70,7 @@ ogr_dependent_driver(sosi "SOSI:Systematic Organization of Spatial Information" ogr_dependent_driver(wfs "OGC WFS service" "GDAL_USE_CURL;OGR_ENABLE_DRIVER_GML") ogr_dependent_driver(ngw "NextGIS Web" "GDAL_USE_CURL") ogr_dependent_driver(elastic "ElasticSearch" "GDAL_USE_CURL") +ogr_dependent_driver(xodr OpenDRIVE "GDAL_USE_OPENDRIVE;GDAL_USE_GEOS") ogr_dependent_driver(idrisi IDRISI "GDAL_ENABLE_DRIVER_IDRISI") diff --git a/ogr/ogrsf_frmts/generic/ogrregisterall.cpp b/ogr/ogrsf_frmts/generic/ogrregisterall.cpp index 8b364b141964..f759e377d59c 100644 --- a/ogr/ogrsf_frmts/generic/ogrregisterall.cpp +++ b/ogr/ogrsf_frmts/generic/ogrregisterall.cpp @@ -266,6 +266,9 @@ void OGRRegisterAllInternal() #ifdef MIRAMON_ENABLED RegisterOGRMiraMon(); #endif +#ifdef XODR_ENABLED + RegisterOGRXODR(); +#endif // NOTE: you need to generally insert your own driver before that line. diff --git a/ogr/ogrsf_frmts/ogrsf_frmts.h b/ogr/ogrsf_frmts/ogrsf_frmts.h index e3089568cf52..7878fcd81401 100644 --- a/ogr/ogrsf_frmts/ogrsf_frmts.h +++ b/ogr/ogrsf_frmts/ogrsf_frmts.h @@ -750,6 +750,8 @@ void CPL_DLL RegisterOGRGTFS(); void CPL_DLL RegisterOGRPMTiles(); void CPL_DLL RegisterOGRJSONFG(); void CPL_DLL RegisterOGRMiraMon(); +void CPL_DLL RegisterOGRXODR(); +void DeclareDeferredOGRXODRPlugin(); // @endcond CPL_C_END diff --git a/ogr/ogrsf_frmts/xodr/CMakeLists.txt b/ogr/ogrsf_frmts/xodr/CMakeLists.txt new file mode 100644 index 000000000000..6bbf60416945 --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/CMakeLists.txt @@ -0,0 +1,22 @@ +add_gdal_driver( + TARGET ogr_XODR + SOURCES ogr_xodr.h + ogrxodrdriver.cpp + ogrxodrdatasource.cpp + ogrxodrlayer.cpp + ogrxodrlayerlane.cpp + ogrxodrlayerlaneborder.cpp + ogrxodrlayerreferenceline.cpp + ogrxodrlayerroadmark.cpp + ogrxodrlayerroadobject.cpp + ogrxodrlayerroadsignal.cpp + CORE_SOURCES ogrxodrdrivercore.cpp + PLUGIN_CAPABLE + NO_SHARED_SYMBOL_WITH_CORE) + +if(NOT TARGET ogr_XODR) + return() +endif() + +gdal_standard_includes(ogr_XODR) +gdal_target_link_libraries(ogr_XODR PRIVATE OpenDrive::OpenDrive) \ No newline at end of file diff --git a/ogr/ogrsf_frmts/xodr/ogr_xodr.h b/ogr/ogrsf_frmts/xodr/ogr_xodr.h new file mode 100644 index 000000000000..4e33f2edbc21 --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogr_xodr.h @@ -0,0 +1,259 @@ +/****************************************************************************** + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Definition of OGR driver components for OpenDRIVE. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#pragma once +#include "ogrsf_frmts.h" +#include "ogr_api.h" +#include +#include +#include +#include + +struct RoadElements +{ + /* Map of road to its original OpenDRIVE ID for fast lookup. */ + std::map roads{}; + std::vector referenceLines{}; + + std::vector lanes{}; + std::vector laneSections{}; + std::vector laneRoadIDs{}; + std::vector laneMeshes{}; + + std::vector laneLinesInner{}; + std::vector laneLinesOuter{}; + + std::vector roadMarks{}; + std::vector roadMarkMeshes{}; + + std::vector roadObjects{}; + std::vector roadObjectMeshes{}; + + std::vector roadSignals{}; + std::vector roadSignalMeshes{}; +}; + +/*--------------------------------------------------------------------*/ +/*--------------------- Layer declarations -------------------------*/ +/*--------------------------------------------------------------------*/ + +class OGRXODRLayer : public OGRLayer +{ + private: + virtual OGRFeatureDefn *GetLayerDefn() override + { + return m_poFeatureDefn.get(); + } + + /** + * Initializes XODR road elements and iterators. + */ + void resetRoadElementIterators(); + + protected: + RoadElements m_roadElements{}; + bool m_bDissolveTIN{false}; + OGRSpatialReference m_poSRS{}; + /* Unique feature ID which is automatically incremented for any new road feature creation. */ + int m_nNextFID{0}; + + std::map::iterator m_roadIter{}; + std::vector::iterator m_referenceLineIter{}; + + std::vector::iterator m_laneIter{}; + std::vector::iterator m_laneSectionIter{}; + std::vector::iterator m_laneRoadIDIter{}; + std::vector::iterator m_laneMeshIter{}; + + std::vector::iterator m_laneLinesInnerIter{}; + std::vector::iterator m_laneLinesOuterIter{}; + + std::vector::iterator m_roadMarkIter{}; + std::vector::iterator m_roadMarkMeshIter{}; + + std::vector::iterator m_roadObjectIter{}; + std::vector::iterator m_roadObjectMeshesIter{}; + + std::vector::iterator m_roadSignalIter{}; + std::vector::iterator m_roadSignalMeshesIter{}; + + std::unique_ptr m_poFeatureDefn{}; + + /** + * Builds an ordinary TIN from libOpenDRIVE's mesh. + */ + std::unique_ptr + triangulateSurface(const odr::Mesh3D &mesh); + + public: + OGRXODRLayer(const RoadElements &xodrRoadElements, + const std::string &proj4Defn); + /** + * \param dissolveTriangulatedSurface True if original triangulated surface meshes from + * libOpenDRIVE are to be dissolved into simpler geometries. + * Only applicable for layer types derived from meshes. + */ + OGRXODRLayer(const RoadElements &xodrRoadElements, + const std::string &proj4Defn, + const bool dissolveTriangulatedSurface); + void ResetReading() override; +}; + +class OGRXODRLayerReferenceLine + : public OGRXODRLayer, + public OGRGetNextFeatureThroughRaw +{ + protected: + OGRFeature *GetNextRawFeature(); + + public: + const std::string FEATURE_CLASS_NAME = "ReferenceLine"; + + OGRXODRLayerReferenceLine(const RoadElements &xodrRoadElements, + const std::string &proj4Defn); + virtual int TestCapability(const char *pszCap) override; + DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGRXODRLayerReferenceLine) +}; + +class OGRXODRLayerLaneBorder + : public OGRXODRLayer, + public OGRGetNextFeatureThroughRaw +{ + protected: + OGRFeature *GetNextRawFeature(); + + public: + const std::string FEATURE_CLASS_NAME = "LaneBorder"; + + OGRXODRLayerLaneBorder(const RoadElements &xodrRoadElements, + const std::string &proj4Defn); + virtual int TestCapability(const char *pszCap) override; + DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGRXODRLayerLaneBorder) +}; + +class OGRXODRLayerRoadMark + : public OGRXODRLayer, + public OGRGetNextFeatureThroughRaw + +{ + protected: + OGRFeature *GetNextRawFeature(); + + public: + const std::string FEATURE_CLASS_NAME = "RoadMark"; + + OGRXODRLayerRoadMark(const RoadElements &xodrRoadElements, + const std::string &proj4Defn, + const bool dissolveTriangulatedSurface); + virtual int TestCapability(const char *pszCap) override; + DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGRXODRLayerRoadMark) +}; + +class OGRXODRLayerRoadObject + : public OGRXODRLayer, + public OGRGetNextFeatureThroughRaw +{ + protected: + OGRFeature *GetNextRawFeature(); + + public: + const std::string FEATURE_CLASS_NAME = "RoadObject"; + + OGRXODRLayerRoadObject(const RoadElements &xodrRoadElements, + const std::string &proj4Defn); + virtual int TestCapability(const char *pszCap) override; + DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGRXODRLayerRoadObject) +}; + +class OGRXODRLayerRoadSignal + : public OGRXODRLayer, + public OGRGetNextFeatureThroughRaw +{ + protected: + OGRFeature *GetNextRawFeature(); + + public: + const std::string FEATURE_CLASS_NAME = "RoadSignal"; + + OGRXODRLayerRoadSignal(const RoadElements &xodrRoadElements, + const std::string &proj4Defn, + const bool dissolveTriangulatedSurface); + virtual int TestCapability(const char *pszCap) override; + DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGRXODRLayerRoadSignal) +}; + +class OGRXODRLayerLane : public OGRXODRLayer, + public OGRGetNextFeatureThroughRaw +{ + protected: + OGRFeature *GetNextRawFeature(); + + public: + const std::string FEATURE_CLASS_NAME = "Lane"; + + OGRXODRLayerLane(const RoadElements &xodrRoadElements, + const std::string &proj4Defn, + const bool dissolveTriangulatedSurface); + virtual int TestCapability(const char *pszCap) override; + DEFINE_GET_NEXT_FEATURE_THROUGH_RAW(OGRXODRLayerLane) +}; + +/*--------------------------------------------------------------------*/ +/*-------------------- Data source declarations ----------------------*/ +/*--------------------------------------------------------------------*/ + +class OGRXODRDataSource : public GDALDataset +{ + private: + std::vector> m_apoLayers{}; + + /** + * Approximation factor for sampling of continuous geometry functions into discrete + * OGC Simple Feature geometries. + */ + double m_dfEpsilon{1.0}; + + /** + * Retrieves all necessary road elements from the underlying OpenDRIVE structure. + * + * \param roads Roads of the dataset. + */ + RoadElements createRoadElements(const std::vector &roads); + + public: + bool Open(const char *pszFilename, CSLConstList openOptions); + + int GetLayerCount() override + { + return m_apoLayers.size(); + } + + OGRLayer *GetLayer(int) override; + + virtual int TestCapability(const char *pszCap) override; +}; diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrdatasource.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrdatasource.cpp new file mode 100644 index 000000000000..cd1206fd89d3 --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogrxodrdatasource.cpp @@ -0,0 +1,183 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Implementation of OGRXODRDataSource. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogr_xodr.h" +using namespace odr; +using namespace pugi; +using namespace std; + +bool OGRXODRDataSource::Open(const char *pszFilename, CSLConstList openOptions) +{ + odr::OpenDriveMap xodr(pszFilename, false); + bool parsingFailed = xodr.xml_doc.child("OpenDRIVE").empty(); + if (parsingFailed) + { + CPLError(CE_Failure, CPLE_AppDefined, + "The provided file does not contain any OpenDRIVE data. Is it " + "empty?"); + return FALSE; + } + + std::vector roads = xodr.get_roads(); + if (roads.empty()) + { + CPLError(CE_Failure, CPLE_AppDefined, + "OpenDRIVE dataset does not contain any roads."); + return FALSE; + } + + const char *openOptionValue = CSLFetchNameValue(openOptions, "EPSILON"); + if (openOptionValue != nullptr) + { + double dfEpsilon = CPLAtof(openOptionValue); + if (dfEpsilon > 0.0) + { + m_dfEpsilon = dfEpsilon; + } + else + { + CPLError(CE_Warning, CPLE_AppDefined, + "Invalid value for EPSILON specified. Falling back to " + "default of 1.0."); + } + } + openOptionValue = CSLFetchNameValueDef(openOptions, "DISSOLVE_TIN", "NO"); + bool bDissolveTIN = CPLTestBool(openOptionValue); + + RoadElements roadElements = createRoadElements(roads); + std::string &proj4Defn = xodr.proj4; + + auto refLine = + std::make_unique(roadElements, proj4Defn); + auto laneBorder = + std::make_unique(roadElements, proj4Defn); + auto roadMark = std::make_unique( + roadElements, proj4Defn, bDissolveTIN); + auto roadObject = + std::make_unique(roadElements, proj4Defn); + auto lane = std::make_unique(roadElements, proj4Defn, + bDissolveTIN); + auto roadSignal = std::make_unique( + roadElements, proj4Defn, bDissolveTIN); + + m_apoLayers.push_back(std::move(refLine)); + m_apoLayers.push_back(std::move(laneBorder)); + m_apoLayers.push_back(std::move(roadMark)); + m_apoLayers.push_back(std::move(roadObject)); + m_apoLayers.push_back(std::move(lane)); + m_apoLayers.push_back(std::move(roadSignal)); + + return TRUE; +} + +OGRLayer *OGRXODRDataSource::GetLayer(int iLayer) +{ + if (iLayer < 0 || (size_t)iLayer >= m_apoLayers.size()) + return NULL; + + return m_apoLayers[iLayer].get(); +} + +int OGRXODRDataSource::TestCapability(CPL_UNUSED const char *pszCap) +{ + int result = FALSE; + + if (EQUAL(pszCap, ODsCZGeometries)) + result = TRUE; + + return result; +} + +RoadElements +OGRXODRDataSource::createRoadElements(const std::vector &roads) +{ + RoadElements elements; + + for (const odr::Road &road : roads) + { + elements.roads.insert({road.id, road}); + + odr::Line3D referenceLine = + road.ref_line.get_line(0.0, road.length, m_dfEpsilon); + elements.referenceLines.push_back(referenceLine); + + for (const odr::LaneSection &laneSection : road.get_lanesections()) + { + elements.laneSections.push_back(laneSection); + + for (const odr::Lane &lane : laneSection.get_lanes()) + { + elements.laneRoadIDs.push_back(road.id); + + elements.lanes.push_back(lane); + + odr::Mesh3D laneMesh = road.get_lane_mesh(lane, m_dfEpsilon); + elements.laneMeshes.push_back(laneMesh); + + odr::Line3D laneLineOuter = + road.get_lane_border_line(lane, m_dfEpsilon, true); + elements.laneLinesOuter.push_back(laneLineOuter); + + odr::Line3D laneLineInner = + road.get_lane_border_line(lane, m_dfEpsilon, false); + elements.laneLinesInner.push_back(laneLineInner); + + const double sectionStart = laneSection.s0; + const double sectionEnd = road.get_lanesection_end(laneSection); + for (const odr::RoadMark &roadMark : + lane.get_roadmarks(sectionStart, sectionEnd)) + { + elements.roadMarks.push_back(roadMark); + + odr::Mesh3D roadMarkMesh = + road.get_roadmark_mesh(lane, roadMark, m_dfEpsilon); + elements.roadMarkMeshes.push_back(roadMarkMesh); + } + } + } + + for (const odr::RoadObject &roadObject : road.get_road_objects()) + { + elements.roadObjects.push_back(roadObject); + + odr::Mesh3D roadObjectMesh = + road.get_road_object_mesh(roadObject, m_dfEpsilon); + elements.roadObjectMeshes.push_back(roadObjectMesh); + } + + for (const odr::RoadSignal &roadSignal : road.get_road_signals()) + { + elements.roadSignals.push_back(roadSignal); + + odr::Mesh3D roadSignalMesh = road.get_road_signal_mesh(roadSignal); + elements.roadSignalMeshes.push_back(roadSignalMesh); + } + } + return elements; +} diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrdriver.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrdriver.cpp new file mode 100644 index 000000000000..07a9575d7826 --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogrxodrdriver.cpp @@ -0,0 +1,63 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Implementation of OGRXODRDriver. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogr_xodr.h" +#include "ogrxodrdrivercore.h" +#include "cpl_conv.h" +#include "cpl_error.h" + +static GDALDataset *OGRXODRDriverOpen(GDALOpenInfo *poOpenInfo) +{ + if (poOpenInfo->eAccess == GA_Update || poOpenInfo->fpL == nullptr) + return nullptr; + + auto dataSource = std::make_unique(); + + if (!dataSource->Open(poOpenInfo->pszFilename, + poOpenInfo->papszOpenOptions)) + { + return nullptr; + } + + return dataSource.release(); +} + +void RegisterOGRXODR() +{ + if (!GDAL_CHECK_VERSION(DRIVER_NAME)) + return; + + if (GDALGetDriverByName(DRIVER_NAME) != nullptr) + return; + + GDALDriver *poDriver = new GDALDriver(); + OGRXODRDriverSetCommonMetadata(poDriver); + poDriver->pfnOpen = OGRXODRDriverOpen; + GetGDALDriverManager()->RegisterDriver(poDriver); +} diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp new file mode 100644 index 000000000000..4432ff238be0 --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp @@ -0,0 +1,87 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Implementation of OGRXODRDriverCore. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogrsf_frmts.h" +#include "ogrxodrdrivercore.h" + +/************************************************************************/ +/* OGRXODRDriverIdentify() */ +/************************************************************************/ + +int OGRXODRDriverIdentify(GDALOpenInfo *poOpenInfo) +{ + return EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "xodr"); +} + +/************************************************************************/ +/* OGRXODRDriverSetCommonMetadata() */ +/************************************************************************/ + +void OGRXODRDriverSetCommonMetadata(GDALDriver *poDriver) +{ + poDriver->SetDescription(DRIVER_NAME); + poDriver->SetMetadataItem( + GDAL_DMD_LONGNAME, + "OpenDRIVE - Open Dynamic Road Information for Vehicle Environment"); + poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "xodr"); + poDriver->SetMetadataItem( + GDAL_DMD_OPENOPTIONLIST, + "" + " "); + poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES"); + poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES"); + poDriver->SetMetadataItem(GDAL_DCAP_Z_GEOMETRIES, "YES"); + poDriver->pfnIdentify = OGRXODRDriverIdentify; +} + +/************************************************************************/ +/* DeclareDeferredOGRXODRPlugin() */ +/************************************************************************/ + +#ifdef PLUGIN_FILENAME +void DeclareDeferredOGRXODRPlugin() +{ + if (GDALGetDriverByName(DRIVER_NAME) != nullptr) + { + return; + } + auto poDriver = new GDALPluginDriverProxy(PLUGIN_FILENAME); +#ifdef PLUGIN_INSTALLATION_MESSAGE + poDriver->SetMetadataItem(GDAL_DMD_PLUGIN_INSTALLATION_MESSAGE, + PLUGIN_INSTALLATION_MESSAGE); +#endif + OGRXODRDriverSetCommonMetadata(poDriver); + GetGDALDriverManager()->DeclareDeferredPluginDriver(poDriver); +} +#endif diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.h b/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.h new file mode 100644 index 000000000000..8a36f237dff6 --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Definition of OGRXODRDriverCore. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#pragma once + +#include "gdal_priv.h" + +constexpr const char *DRIVER_NAME = "XODR"; + +#define OGRXODRDriverIdentify PLUGIN_SYMBOL_NAME(OGRXODRDriverIdentify) +#define OGRXODRDriverSetCommonMetadata \ + PLUGIN_SYMBOL_NAME(OGRXODRDriverSetCommonMetadata) + +int OGRXODRDriverIdentify(GDALOpenInfo *poOpenInfo); + +void OGRXODRDriverSetCommonMetadata(GDALDriver *poDriver); \ No newline at end of file diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayer.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayer.cpp new file mode 100644 index 000000000000..0e849627f10b --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayer.cpp @@ -0,0 +1,115 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Implementation of OGRXODRLayer. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogr_api.h" +#include "ogr_geometry.h" +#include "ogr_xodr.h" + +#include "cpl_error.h" +#include +#include +#include +#include +#include + +OGRXODRLayer::OGRXODRLayer(const RoadElements &xodrRoadElements, + const std::string &proj4Defn) + : OGRXODRLayer(xodrRoadElements, proj4Defn, false) +{ +} + +OGRXODRLayer::OGRXODRLayer(const RoadElements &xodrRoadElements, + const std::string &proj4Defn, + const bool dissolveTriangulatedSurface) + : m_roadElements(xodrRoadElements), + m_bDissolveTIN(dissolveTriangulatedSurface) +{ + m_poSRS.importFromProj4(proj4Defn.c_str()); + resetRoadElementIterators(); +} + +void OGRXODRLayer::ResetReading() +{ + m_nNextFID = 0; + resetRoadElementIterators(); +} + +void OGRXODRLayer::resetRoadElementIterators() +{ + m_roadIter = m_roadElements.roads.begin(); + m_referenceLineIter = m_roadElements.referenceLines.begin(); + + m_laneIter = m_roadElements.lanes.begin(); + m_laneSectionIter = m_roadElements.laneSections.begin(); + m_laneRoadIDIter = m_roadElements.laneRoadIDs.begin(); + m_laneMeshIter = m_roadElements.laneMeshes.begin(); + + m_laneLinesInnerIter = m_roadElements.laneLinesInner.begin(); + m_laneLinesOuterIter = m_roadElements.laneLinesOuter.begin(); + + m_roadMarkIter = m_roadElements.roadMarks.begin(); + m_roadMarkMeshIter = m_roadElements.roadMarkMeshes.begin(); + + m_roadObjectIter = m_roadElements.roadObjects.begin(); + m_roadObjectMeshesIter = m_roadElements.roadObjectMeshes.begin(); + + m_roadSignalIter = m_roadElements.roadSignals.begin(); + m_roadSignalMeshesIter = m_roadElements.roadSignalMeshes.begin(); +} + +std::unique_ptr +OGRXODRLayer::triangulateSurface(const odr::Mesh3D &mesh) +{ + std::vector meshVertices = mesh.vertices; + std::vector meshIndices = mesh.indices; + + auto tin = std::make_unique(); + const size_t numIndices = meshIndices.size(); + // Build triangles from mesh vertices. + // Each triple of mesh indices defines which vertices form a triangle. + for (std::size_t idx = 0; idx < numIndices; idx += 3) + { + uint32_t vertexIdx = meshIndices[idx]; + odr::Vec3D vertexP = meshVertices[vertexIdx]; + OGRPoint p(vertexP[0], vertexP[1], vertexP[2]); + + vertexIdx = meshIndices[idx + 1]; + odr::Vec3D vertexQ = meshVertices[vertexIdx]; + OGRPoint q(vertexQ[0], vertexQ[1], vertexQ[2]); + + vertexIdx = meshIndices[idx + 2]; + odr::Vec3D vertexR = meshVertices[vertexIdx]; + OGRPoint r(vertexR[0], vertexR[1], vertexR[2]); + + OGRTriangle triangle(p, q, r); + tin->addGeometry(&triangle); + } + + return tin; +} \ No newline at end of file diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerlane.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerlane.cpp new file mode 100644 index 000000000000..b7ab1e906ca5 --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerlane.cpp @@ -0,0 +1,153 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Implementation of Lane layer. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogr_api.h" +#include "ogr_geometry.h" +#include "ogr_xodr.h" + +OGRXODRLayerLane::OGRXODRLayerLane(const RoadElements &xodrRoadElements, + const std::string &proj4Defn, + const bool dissolveTriangulatedSurface) + : OGRXODRLayer(xodrRoadElements, proj4Defn, dissolveTriangulatedSurface) +{ + m_poFeatureDefn = + std::make_unique(FEATURE_CLASS_NAME.c_str()); + m_poFeatureDefn->Reference(); + SetDescription(FEATURE_CLASS_NAME.c_str()); + + if (m_bDissolveTIN) + { + OGRwkbGeometryType wkbPolygonWithZ = OGR_GT_SetZ(wkbPolygon); + m_poFeatureDefn->SetGeomType(wkbPolygonWithZ); + } + else + { + m_poFeatureDefn->SetGeomType(wkbTINZ); + } + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + + OGRFieldDefn oFieldLaneID("LaneID", OFTInteger); + m_poFeatureDefn->AddFieldDefn(&oFieldLaneID); + + OGRFieldDefn oFieldRoadID("RoadID", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldRoadID); + + OGRFieldDefn oFieldType("Type", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldType); + + OGRFieldDefn oFieldPred("Predecessor", OFTInteger); + m_poFeatureDefn->AddFieldDefn(&oFieldPred); + + OGRFieldDefn oFieldSuc("Successor", OFTInteger); + m_poFeatureDefn->AddFieldDefn(&oFieldSuc); +} + +int OGRXODRLayerLane::TestCapability(const char *pszCap) +{ + int result = FALSE; + + if (EQUAL(pszCap, OLCZGeometries)) + result = TRUE; + + return result; +} + +OGRFeature *OGRXODRLayerLane::GetNextRawFeature() +{ + std::unique_ptr feature; + + while (m_laneIter != m_roadElements.lanes.end() && (*m_laneIter).id == 0) + { + // Skip lane(s) with id 0 because these "center lanes" don't have any width + ++m_laneIter; + ++m_laneMeshIter; + ++m_laneRoadIDIter; + } + + if (m_laneIter != m_roadElements.lanes.end()) + { + + feature = std::make_unique(m_poFeatureDefn.get()); + + odr::Lane lane = *m_laneIter; + odr::Mesh3D laneMesh = *m_laneMeshIter; + std::string laneRoadID = *m_laneRoadIDIter; + + // Populate geometry field + std::unique_ptr tin = + triangulateSurface(laneMesh); + if (m_bDissolveTIN) + { + OGRGeometry *dissolvedPolygon = tin->UnaryUnion(); + if (dissolvedPolygon != nullptr) + { + dissolvedPolygon->assignSpatialReference(&m_poSRS); + feature->SetGeometryDirectly(dissolvedPolygon); + } + else + { + CPLError(CE_Warning, CPLE_AppDefined, + "Lane feature with FID %d has no geometry because its " + "triangulated surface could not be dissolved.", + m_nNextFID); + } + } + else + { + tin->assignSpatialReference(&m_poSRS); + feature->SetGeometryDirectly(tin.release()); + } + + // Populate other fields + feature->SetFID(m_nNextFID++); + feature->SetField(m_poFeatureDefn->GetFieldIndex("RoadID"), + laneRoadID.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("LaneID"), lane.id); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Type"), + lane.type.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Predecessor"), + lane.predecessor); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Successor"), + lane.successor); + + ++m_laneIter; + ++m_laneMeshIter; + ++m_laneRoadIDIter; + } + + if (feature) + { + return feature.release(); + } + else + { + // End of features for the given layer reached. + return nullptr; + } +} \ No newline at end of file diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerlaneborder.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerlaneborder.cpp new file mode 100644 index 000000000000..dae9272afe57 --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerlaneborder.cpp @@ -0,0 +1,122 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Implementation of LaneBorder layer. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogr_api.h" +#include "ogr_geometry.h" +#include "ogr_xodr.h" + +OGRXODRLayerLaneBorder::OGRXODRLayerLaneBorder( + const RoadElements &xodrRoadElements, const std::string &proj4Defn) + : OGRXODRLayer(xodrRoadElements, proj4Defn) +{ + m_poFeatureDefn = + std::make_unique(FEATURE_CLASS_NAME.c_str()); + m_poFeatureDefn->Reference(); + SetDescription(FEATURE_CLASS_NAME.c_str()); + + OGRwkbGeometryType wkbLineStringWithZ = OGR_GT_SetZ(wkbLineString); + m_poFeatureDefn->SetGeomType(wkbLineStringWithZ); + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + + OGRFieldDefn oFieldID("ID", OFTInteger); + m_poFeatureDefn->AddFieldDefn(&oFieldID); + + OGRFieldDefn oFieldRoadID("RoadID", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldRoadID); + + OGRFieldDefn oFieldType("Type", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldType); + + OGRFieldDefn oFieldPred("Predecessor", OFTInteger); + m_poFeatureDefn->AddFieldDefn(&oFieldPred); + + OGRFieldDefn oFieldSuc("Successor", OFTInteger); + m_poFeatureDefn->AddFieldDefn(&oFieldSuc); +} + +int OGRXODRLayerLaneBorder::TestCapability(const char *pszCap) +{ + int result = FALSE; + + if (EQUAL(pszCap, OLCZGeometries)) + result = TRUE; + + return result; +} + +OGRFeature *OGRXODRLayerLaneBorder::GetNextRawFeature() +{ + std::unique_ptr feature; + + if (m_laneIter != m_roadElements.lanes.end()) + { + feature = std::make_unique(m_poFeatureDefn.get()); + + odr::Lane lane = *m_laneIter; + odr::Line3D laneOuterBorder = *m_laneLinesOuterIter; + std::string laneRoadID = *m_laneRoadIDIter; + + // Populate geometry field + auto lineString = std::make_unique(); + for (const auto &borderVertex : laneOuterBorder) + { + lineString->addPoint(borderVertex[0], borderVertex[1], + borderVertex[2]); + } + lineString->assignSpatialReference(&m_poSRS); + feature->SetGeometryDirectly(lineString.release()); + + // Populate other fields + feature->SetField(m_poFeatureDefn->GetFieldIndex("RoadID"), + laneRoadID.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("ID"), lane.id); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Type"), + lane.type.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Predecessor"), + lane.predecessor); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Successor"), + lane.successor); + feature->SetFID(m_nNextFID++); + + ++m_laneIter; + ++m_laneLinesOuterIter; + ++m_laneLinesInnerIter; // For consistency, even though not used here + ++m_laneRoadIDIter; + } + + if (feature) + { + return feature.release(); + } + else + { + // End of features for the given layer reached. + return nullptr; + } +} \ No newline at end of file diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerreferenceline.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerreferenceline.cpp new file mode 100644 index 000000000000..862c555979d8 --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerreferenceline.cpp @@ -0,0 +1,106 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Implementation of ReferenceLine layer. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogr_api.h" +#include "ogr_geometry.h" +#include "ogr_xodr.h" + +OGRXODRLayerReferenceLine::OGRXODRLayerReferenceLine( + const RoadElements &xodrRoadElements, const std::string &proj4Defn) + : OGRXODRLayer(xodrRoadElements, proj4Defn) +{ + m_poFeatureDefn = + std::make_unique(FEATURE_CLASS_NAME.c_str()); + m_poFeatureDefn->Reference(); + SetDescription(FEATURE_CLASS_NAME.c_str()); + + OGRwkbGeometryType wkbLineStringWithZ = OGR_GT_SetZ(wkbLineString); + m_poFeatureDefn->SetGeomType(wkbLineStringWithZ); + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + + OGRFieldDefn oFieldID("ID", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldID); + + OGRFieldDefn oFieldLen("Length", OFTReal); + m_poFeatureDefn->AddFieldDefn(&oFieldLen); + + OGRFieldDefn oFieldJunction("Junction", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldJunction); +} + +int OGRXODRLayerReferenceLine::TestCapability(const char *pszCap) +{ + int result = FALSE; + + if (EQUAL(pszCap, OLCZGeometries)) + result = TRUE; + + return result; +} + +OGRFeature *OGRXODRLayerReferenceLine::GetNextRawFeature() +{ + std::unique_ptr feature; + + if (m_roadIter != m_roadElements.roads.end()) + { + feature = std::make_unique(m_poFeatureDefn.get()); + + odr::Road road = (*m_roadIter).second; + odr::Line3D refLine = *m_referenceLineIter; + + // Populate geometry field + auto lineString = std::make_unique(); + for (const auto &lineVertex : refLine) + { + lineString->addPoint(lineVertex[0], lineVertex[1], lineVertex[2]); + } + lineString->assignSpatialReference(&m_poSRS); + feature->SetGeometryDirectly(lineString.release()); + + // Populate other fields + feature->SetField("ID", road.id.c_str()); + feature->SetField("Length", road.length); + feature->SetField("Junction", road.junction.c_str()); + feature->SetFID(m_nNextFID++); + + ++m_roadIter; + ++m_referenceLineIter; + } + + if (feature) + { + return feature.release(); + } + else + { + // End of features for the given layer reached. + return nullptr; + } +} \ No newline at end of file diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadmark.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadmark.cpp new file mode 100644 index 000000000000..a6d364e92bf2 --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadmark.cpp @@ -0,0 +1,133 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Implementation of RoadMark layer. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogr_api.h" +#include "ogr_geometry.h" +#include "ogr_xodr.h" + +OGRXODRLayerRoadMark::OGRXODRLayerRoadMark( + const RoadElements &xodrRoadElements, const std::string &proj4Defn, + const bool dissolveTriangulatedSurface) + : OGRXODRLayer(xodrRoadElements, proj4Defn, dissolveTriangulatedSurface) +{ + m_poFeatureDefn = + std::make_unique(FEATURE_CLASS_NAME.c_str()); + m_poFeatureDefn->Reference(); + SetDescription(FEATURE_CLASS_NAME.c_str()); + + if (m_bDissolveTIN) + { + OGRwkbGeometryType wkbPolygonWithZ = OGR_GT_SetZ(wkbPolygon); + m_poFeatureDefn->SetGeomType(wkbPolygonWithZ); + } + else + { + m_poFeatureDefn->SetGeomType(wkbTINZ); + } + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + + OGRFieldDefn oFieldRoadID("RoadID", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldRoadID); + + OGRFieldDefn oFieldLaneID("LaneID", OFTInteger); + m_poFeatureDefn->AddFieldDefn(&oFieldLaneID); + + OGRFieldDefn oFieldType("Type", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldType); +} + +int OGRXODRLayerRoadMark::TestCapability(const char *pszCap) +{ + int result = FALSE; + + if (EQUAL(pszCap, OLCZGeometries)) + result = TRUE; + + return result; +} + +OGRFeature *OGRXODRLayerRoadMark::GetNextRawFeature() +{ + std::unique_ptr feature; + + if (m_roadMarkIter != m_roadElements.roadMarks.end()) + { + feature = std::make_unique(m_poFeatureDefn.get()); + + odr::RoadMark roadMark = *m_roadMarkIter; + odr::Mesh3D roadMarkMesh = *m_roadMarkMeshIter; + + // Populate geometry field + std::unique_ptr tin = + triangulateSurface(roadMarkMesh); + if (m_bDissolveTIN) + { + OGRGeometry *dissolvedPolygon = tin->UnaryUnion(); + if (dissolvedPolygon != nullptr) + { + dissolvedPolygon->assignSpatialReference(&m_poSRS); + feature->SetGeometryDirectly(dissolvedPolygon); + } + else + { + CPLError(CE_Warning, CPLE_AppDefined, + "RoadMark feature with FID %d has no geometry because " + "its triangulated surface could not be dissolved.", + m_nNextFID); + } + } + else + { + tin->assignSpatialReference(&m_poSRS); + feature->SetGeometryDirectly(tin.release()); + } + + // Populate other fields + feature->SetField(m_poFeatureDefn->GetFieldIndex("RoadID"), + roadMark.road_id.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("LaneID"), + roadMark.lane_id); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Type"), + roadMark.type.c_str()); + feature->SetFID(m_nNextFID++); + + ++m_roadMarkIter; + ++m_roadMarkMeshIter; + } + + if (feature) + { + return feature.release(); + } + else + { + // End of features for the given layer reached. + return nullptr; + } +} \ No newline at end of file diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadobject.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadobject.cpp new file mode 100644 index 000000000000..d266db82610a --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadobject.cpp @@ -0,0 +1,111 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Implementation of RoadObject layer. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogr_api.h" +#include "ogr_geometry.h" +#include "ogr_xodr.h" + +OGRXODRLayerRoadObject::OGRXODRLayerRoadObject( + const RoadElements &xodrRoadElements, const std::string &proj4Defn) + : OGRXODRLayer(xodrRoadElements, proj4Defn) +{ + m_poFeatureDefn = + std::make_unique(FEATURE_CLASS_NAME.c_str()); + m_poFeatureDefn->Reference(); + SetDescription(FEATURE_CLASS_NAME.c_str()); + + m_poFeatureDefn->SetGeomType(wkbTINZ); + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + + OGRFieldDefn oFieldObjectID("ObjectID", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldObjectID); + + OGRFieldDefn oFieldRoadID("RoadID", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldRoadID); + + OGRFieldDefn oFieldType("Type", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldType); + + OGRFieldDefn oFieldObjectName("Name", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldObjectName); +} + +int OGRXODRLayerRoadObject::TestCapability(const char *pszCap) +{ + int result = FALSE; + + if (EQUAL(pszCap, OLCZGeometries)) + result = TRUE; + + return result; +} + +OGRFeature *OGRXODRLayerRoadObject::GetNextRawFeature() +{ + std::unique_ptr feature; + + if (m_roadObjectIter != m_roadElements.roadObjects.end()) + { + feature = std::make_unique(m_poFeatureDefn.get()); + + odr::RoadObject roadObject = *m_roadObjectIter; + odr::Mesh3D roadObjectMesh = *m_roadObjectMeshesIter; + + // Populate geometry field + // In contrast to other XODR layers, dissolving of RoadObject TINs is not an option, because faces of "true" 3D objects might collapse. + std::unique_ptr tin = + triangulateSurface(roadObjectMesh); + tin->assignSpatialReference(&m_poSRS); + feature->SetGeometryDirectly(tin.release()); + + // Populate other fields + feature->SetField(m_poFeatureDefn->GetFieldIndex("ObjectID"), + roadObject.id.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("RoadID"), + roadObject.road_id.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Type"), + roadObject.type.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Name"), + roadObject.name.c_str()); + feature->SetFID(m_nNextFID++); + + ++m_roadObjectIter; + ++m_roadObjectMeshesIter; + } + + if (feature) + { + return feature.release(); + } + else + { + // End of features for the given layer reached. + return nullptr; + } +} \ No newline at end of file diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadsignal.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadsignal.cpp new file mode 100644 index 000000000000..d3956020eb56 --- /dev/null +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadsignal.cpp @@ -0,0 +1,168 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features for OpenDRIVE + * Purpose: Implementation of RoadSignal layer. + * Author: Michael Scholz, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) + * + ****************************************************************************** + * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include "ogr_api.h" +#include "ogr_geometry.h" +#include "ogr_xodr.h" + +OGRXODRLayerRoadSignal::OGRXODRLayerRoadSignal( + const RoadElements &xodrRoadElements, const std::string &proj4Defn, + const bool dissolveTriangulatedSurface) + : OGRXODRLayer(xodrRoadElements, proj4Defn, dissolveTriangulatedSurface) +{ + m_poFeatureDefn = + std::make_unique(FEATURE_CLASS_NAME.c_str()); + m_poFeatureDefn->Reference(); + SetDescription(FEATURE_CLASS_NAME.c_str()); + + if (m_bDissolveTIN) + { + OGRwkbGeometryType wkbPointWithZ = OGR_GT_SetZ(wkbPoint); + m_poFeatureDefn->SetGeomType(wkbPointWithZ); + } + else + { + m_poFeatureDefn->SetGeomType(wkbTINZ); + } + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + + OGRFieldDefn oFieldSignalID("SignalID", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldSignalID); + + OGRFieldDefn oFieldRoadID("RoadID", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldRoadID); + + OGRFieldDefn oFieldType("Type", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldType); + + OGRFieldDefn oFieldSubType("SubType", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldSubType); + + OGRFieldDefn oFieldHOffset("HOffset", OFTReal); + m_poFeatureDefn->AddFieldDefn(&oFieldHOffset); + + OGRFieldDefn oFieldPitch("Pitch", OFTReal); + m_poFeatureDefn->AddFieldDefn(&oFieldPitch); + + OGRFieldDefn oFieldRoll("Roll", OFTReal); + m_poFeatureDefn->AddFieldDefn(&oFieldRoll); + + OGRFieldDefn oFieldOrientation("Orientation", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldOrientation); + + OGRFieldDefn oFieldName("Name", OFTString); + m_poFeatureDefn->AddFieldDefn(&oFieldName); + + OGRFieldDefn oFieldObjectDynamic("Dynamic", OFTInteger); + oFieldObjectDynamic.SetSubType(OFSTBoolean); + m_poFeatureDefn->AddFieldDefn(&oFieldObjectDynamic); +} + +int OGRXODRLayerRoadSignal::TestCapability(const char *pszCap) +{ + int result = FALSE; + + if (EQUAL(pszCap, OLCZGeometries)) + result = TRUE; + + return result; +} + +OGRFeature *OGRXODRLayerRoadSignal::GetNextRawFeature() +{ + std::unique_ptr feature; + + if (m_roadSignalIter != m_roadElements.roadSignals.end()) + { + feature = std::make_unique(m_poFeatureDefn.get()); + + odr::RoadSignal roadSignal = *m_roadSignalIter; + odr::Mesh3D roadSignalMesh = *m_roadSignalMeshesIter; + + // Populate geometry field + if (m_bDissolveTIN) + { + // Use simplified centroid, directly provided by libOpenDRIVE + std::string roadId = roadSignal.road_id; + odr::Road road = m_roadElements.roads.at(roadId); + + double s = roadSignal.s0; + double t = roadSignal.t0; + double h = roadSignal.zOffset; + odr::Vec3D xyz = road.get_xyz(s, t, h); + + auto point = std::make_unique(xyz[0], xyz[1], xyz[2]); + point->assignSpatialReference(&m_poSRS); + feature->SetGeometryDirectly(point.release()); + } + else + { + std::unique_ptr tin = + triangulateSurface(roadSignalMesh); + tin->assignSpatialReference(&m_poSRS); + feature->SetGeometryDirectly(tin.release()); + } + + // Populate other fields + feature->SetField(m_poFeatureDefn->GetFieldIndex("SignalID"), + roadSignal.id.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("RoadID"), + roadSignal.road_id.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Type"), + roadSignal.type.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("SubType"), + roadSignal.subtype.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("HOffset"), + roadSignal.hOffset); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Pitch"), + roadSignal.pitch); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Roll"), + roadSignal.roll); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Orientation"), + roadSignal.orientation.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Name"), + roadSignal.name.c_str()); + feature->SetField(m_poFeatureDefn->GetFieldIndex("Dynamic"), + roadSignal.is_dynamic); + feature->SetFID(m_nNextFID++); + + ++m_roadSignalIter; + ++m_roadSignalMeshesIter; + } + + if (feature) + { + return feature.release(); + } + else + { + // End of features for the given layer reached. + return nullptr; + } +} \ No newline at end of file From 1701ed2b185f6dfca271b5d41b6607a4911106a7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 19 Jun 2024 18:27:58 +0200 Subject: [PATCH 0191/1119] XODR: fix to build with STRONG_CXX_WFLAGS --- ogr/ogrsf_frmts/xodr/CMakeLists.txt | 13 +++++++------ ogr/ogrsf_frmts/xodr/ogrxodrdatasource.cpp | 14 +++++++------- ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp | 8 ++++---- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/ogr/ogrsf_frmts/xodr/CMakeLists.txt b/ogr/ogrsf_frmts/xodr/CMakeLists.txt index 6bbf60416945..c4fce7c7ae5f 100644 --- a/ogr/ogrsf_frmts/xodr/CMakeLists.txt +++ b/ogr/ogrsf_frmts/xodr/CMakeLists.txt @@ -1,22 +1,23 @@ add_gdal_driver( TARGET ogr_XODR - SOURCES ogr_xodr.h + SOURCES ogr_xodr.h ogrxodrdriver.cpp - ogrxodrdatasource.cpp + ogrxodrdatasource.cpp ogrxodrlayer.cpp - ogrxodrlayerlane.cpp - ogrxodrlayerlaneborder.cpp + ogrxodrlayerlane.cpp + ogrxodrlayerlaneborder.cpp ogrxodrlayerreferenceline.cpp ogrxodrlayerroadmark.cpp ogrxodrlayerroadobject.cpp ogrxodrlayerroadsignal.cpp CORE_SOURCES ogrxodrdrivercore.cpp PLUGIN_CAPABLE - NO_SHARED_SYMBOL_WITH_CORE) + NO_SHARED_SYMBOL_WITH_CORE + STRONG_CXX_WFLAGS) if(NOT TARGET ogr_XODR) return() endif() gdal_standard_includes(ogr_XODR) -gdal_target_link_libraries(ogr_XODR PRIVATE OpenDrive::OpenDrive) \ No newline at end of file +gdal_target_link_libraries(ogr_XODR PRIVATE OpenDrive::OpenDrive) diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrdatasource.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrdatasource.cpp index cd1206fd89d3..bf2f78e32779 100644 --- a/ogr/ogrsf_frmts/xodr/ogrxodrdatasource.cpp +++ b/ogr/ogrsf_frmts/xodr/ogrxodrdatasource.cpp @@ -3,7 +3,7 @@ * Project: OpenGIS Simple Features for OpenDRIVE * Purpose: Implementation of OGRXODRDataSource. * Author: Michael Scholz, German Aerospace Center (DLR) - * Gülsen Bardak, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) * ****************************************************************************** * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems @@ -41,7 +41,7 @@ bool OGRXODRDataSource::Open(const char *pszFilename, CSLConstList openOptions) CPLError(CE_Failure, CPLE_AppDefined, "The provided file does not contain any OpenDRIVE data. Is it " "empty?"); - return FALSE; + return false; } std::vector roads = xodr.get_roads(); @@ -49,7 +49,7 @@ bool OGRXODRDataSource::Open(const char *pszFilename, CSLConstList openOptions) { CPLError(CE_Failure, CPLE_AppDefined, "OpenDRIVE dataset does not contain any roads."); - return FALSE; + return false; } const char *openOptionValue = CSLFetchNameValue(openOptions, "EPSILON"); @@ -93,18 +93,18 @@ bool OGRXODRDataSource::Open(const char *pszFilename, CSLConstList openOptions) m_apoLayers.push_back(std::move(lane)); m_apoLayers.push_back(std::move(roadSignal)); - return TRUE; + return true; } OGRLayer *OGRXODRDataSource::GetLayer(int iLayer) { - if (iLayer < 0 || (size_t)iLayer >= m_apoLayers.size()) - return NULL; + if (iLayer < 0 || static_cast(iLayer) >= m_apoLayers.size()) + return nullptr; return m_apoLayers[iLayer].get(); } -int OGRXODRDataSource::TestCapability(CPL_UNUSED const char *pszCap) +int OGRXODRDataSource::TestCapability(const char *pszCap) { int result = FALSE; diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp index 4432ff238be0..8bd7c86cdf75 100644 --- a/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp +++ b/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp @@ -3,7 +3,7 @@ * Project: OpenGIS Simple Features for OpenDRIVE * Purpose: Implementation of OGRXODRDriverCore. * Author: Michael Scholz, German Aerospace Center (DLR) - * Gülsen Bardak, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) * ****************************************************************************** * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems @@ -31,7 +31,7 @@ #include "ogrxodrdrivercore.h" /************************************************************************/ -/* OGRXODRDriverIdentify() */ +/* OGRXODRDriverIdentify() */ /************************************************************************/ int OGRXODRDriverIdentify(GDALOpenInfo *poOpenInfo) @@ -40,7 +40,7 @@ int OGRXODRDriverIdentify(GDALOpenInfo *poOpenInfo) } /************************************************************************/ -/* OGRXODRDriverSetCommonMetadata() */ +/* OGRXODRDriverSetCommonMetadata() */ /************************************************************************/ void OGRXODRDriverSetCommonMetadata(GDALDriver *poDriver) @@ -66,7 +66,7 @@ void OGRXODRDriverSetCommonMetadata(GDALDriver *poDriver) } /************************************************************************/ -/* DeclareDeferredOGRXODRPlugin() */ +/* DeclareDeferredOGRXODRPlugin() */ /************************************************************************/ #ifdef PLUGIN_FILENAME From 09ca3ad38469818556509a7392435e492bed6c34 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 19 Jun 2024 21:53:49 +0200 Subject: [PATCH 0192/1119] GeoParquet: always write version=1.1.0 now it has been officially released And remove hacks in validator related to it --- .../{schema.json => schema_1_1_0.json} | 56 ++++++++++++++++++- autotest/ogr/ogr_parquet.py | 6 +- .../pyscripts/test_validate_geoparquet.py | 6 +- .../parquet/ogrparquetwriterlayer.cpp | 6 +- .../samples/validate_geoparquet.py | 16 +----- 5 files changed, 61 insertions(+), 29 deletions(-) rename autotest/ogr/data/parquet/{schema.json => schema_1_1_0.json} (52%) diff --git a/autotest/ogr/data/parquet/schema.json b/autotest/ogr/data/parquet/schema_1_1_0.json similarity index 52% rename from autotest/ogr/data/parquet/schema.json rename to autotest/ogr/data/parquet/schema_1_1_0.json index ad2fd9459437..26e0f59313aa 100644 --- a/autotest/ogr/data/parquet/schema.json +++ b/autotest/ogr/data/parquet/schema_1_1_0.json @@ -7,7 +7,7 @@ "properties": { "version": { "type": "string", - "const": "1.0.0" + "const": "1.1.0" }, "primary_column": { "type": "string", @@ -23,7 +23,7 @@ "properties": { "encoding": { "type": "string", - "const": "WKB" + "pattern": "^(WKB|point|linestring|polygon|multipoint|multilinestring|multipolygon)$" }, "geometry_types": { "type": "array", @@ -36,7 +36,7 @@ "crs": { "oneOf": [ { - "$ref": "https://proj.org/schemas/v0.5/projjson.schema.json" + "$ref": "https://proj.org/schemas/v0.7/projjson.schema.json" }, { "type": "null" @@ -71,6 +71,56 @@ }, "epoch": { "type": "number" + }, + "covering": { + "type": "object", + "required": [ + "bbox" + ], + "properties": { + "bbox": { + "type": "object", + "required": ["xmin", "xmax", "ymin", "ymax"], + "properties": { + "xmin": { + "type": "array", + "items": [ + { "type": "string", "minLength": 1 }, + { "const": "xmin" } + ], + "minItems": 2, + "maxItems": 2 + }, + "xmax": { + "type": "array", + "items": [ + { "type": "string", "minLength": 1 }, + { "const": "xmax" } + ], + "minItems": 2, + "maxItems": 2 + }, + "ymin": { + "type": "array", + "items": [ + { "type": "string", "minLength": 1 }, + { "const": "ymin" } + ], + "minItems": 2, + "maxItems": 2 + }, + "ymax": { + "type": "array", + "items": [ + { "type": "string", "minLength": 1 }, + { "const": "ymax" } + ], + "minItems": 2, + "maxItems": 2 + } + } + } + } } } } diff --git a/autotest/ogr/ogr_parquet.py b/autotest/ogr/ogr_parquet.py index 82ed7c7afb71..8d09e1c861b0 100755 --- a/autotest/ogr/ogr_parquet.py +++ b/autotest/ogr/ogr_parquet.py @@ -41,7 +41,7 @@ pytestmark = pytest.mark.require_driver("Parquet") -PARQUET_JSON_SCHEMA = "data/parquet/schema.json" +GEOPARQUET_1_1_0_JSON_SCHEMA = "data/parquet/schema_1_1_0.json" ############################################################################### @@ -81,7 +81,7 @@ def _validate(filename, check_data=False): import validate_geoparquet ret = validate_geoparquet.check( - filename, check_data=check_data, local_schema=PARQUET_JSON_SCHEMA + filename, check_data=check_data, local_schema=GEOPARQUET_1_1_0_JSON_SCHEMA ) assert not ret @@ -682,7 +682,7 @@ def test_ogr_parquet_write_from_another_dataset(use_vsi, row_group_size, fid): j = json.loads(geo) assert j is not None assert "version" in j - assert j["version"] == "1.0.0" + assert j["version"] == "1.1.0" assert "primary_column" in j assert j["primary_column"] == "geometry" assert "columns" in j diff --git a/autotest/pyscripts/test_validate_geoparquet.py b/autotest/pyscripts/test_validate_geoparquet.py index d2ee01f81cc8..25343f6abedc 100755 --- a/autotest/pyscripts/test_validate_geoparquet.py +++ b/autotest/pyscripts/test_validate_geoparquet.py @@ -37,8 +37,8 @@ from osgeo import gdal, ogr -CURRENT_VERSION = "1.0.0" -PARQUET_JSON_SCHEMA = "../ogr/data/parquet/schema.json" +CURRENT_VERSION = "1.1.0" +GEOPARQUET_1_1_0_JSON_SCHEMA = "../ogr/data/parquet/schema_1_1_0.json" pytestmark = [ @@ -62,7 +62,7 @@ def script_path(): # Validate a GeoParquet file -def _validate(filename, check_data=False, local_schema=PARQUET_JSON_SCHEMA): +def _validate(filename, check_data=False, local_schema=GEOPARQUET_1_1_0_JSON_SCHEMA): import sys from test_py_scripts import samples_path diff --git a/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp b/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp index a62bb4cb17aa..5241ac22228a 100644 --- a/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp +++ b/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp @@ -652,11 +652,7 @@ std::string OGRParquetWriterLayer::GetGeoMetadata() const CPLTestBool(CPLGetConfigOption("OGR_PARQUET_WRITE_GEO", "YES"))) { CPLJSONObject oRoot; - oRoot.Add("version", - m_eGeomEncoding == - OGRArrowGeomEncoding::GEOARROW_STRUCT_GENERIC - ? "1.1.0" - : "1.0.0"); + oRoot.Add("version", "1.1.0"); oRoot.Add("primary_column", m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef()); CPLJSONObject oColumns; diff --git a/swig/python/gdal-utils/osgeo_utils/samples/validate_geoparquet.py b/swig/python/gdal-utils/osgeo_utils/samples/validate_geoparquet.py index f03e6df16df5..81141cd0cf64 100644 --- a/swig/python/gdal-utils/osgeo_utils/samples/validate_geoparquet.py +++ b/swig/python/gdal-utils/osgeo_utils/samples/validate_geoparquet.py @@ -192,11 +192,7 @@ def _check(self): if self.local_schema: schema_j = json.loads(open(self.local_schema, "rb").read()) else: - # FIXME: Remove that temporary hack once GeoParquet 1.1 schema is released - if version == "1.1.0": - schema_url = "https://github.com/opengeospatial/geoparquet/releases/download/v1.0.0/schema.json" - else: - schema_url = f"https://github.com/opengeospatial/geoparquet/releases/download/v{version}/schema.json" + schema_url = f"https://github.com/opengeospatial/geoparquet/releases/download/v{version}/schema.json" if schema_url not in geoparquet_schemas: import urllib @@ -217,16 +213,6 @@ def _check(self): schema_j = geoparquet_schemas[schema_url] - # FIXME: Remove that temporary hack once GeoParquet 1.1 schema is released - if version == "1.1.0": - schema_j["properties"]["version"] = {"const": "1.1.0", "type": "string"} - schema_j["properties"]["columns"]["patternProperties"][".+"]["properties"][ - "encoding" - ] = { - "type": "string", - "pattern": "^(WKB|point|linestring|polygon|multipoint|multilinestring|multipolygon)$", - } - try: self._validate(schema_j, geo_j) except Exception as e: From a6ffbcb0637088be65aac60b7eb98c4b770f1dfd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 20 Jun 2024 00:01:13 +0200 Subject: [PATCH 0193/1119] GDALSieveFilter(): avoid assert() when all pixels are masked (3.9.0 regression) Fixes https://github.com/rasterio/rasterio/issues/3101 --- alg/gdalsievefilter.cpp | 90 ++++++++++++++++++++++++----------------- autotest/alg/sieve.py | 28 +++++++++++++ 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/alg/gdalsievefilter.cpp b/alg/gdalsievefilter.cpp index a3c60a0cdb6a..cf20d13d4d15 100644 --- a/alg/gdalsievefilter.cpp +++ b/alg/gdalsievefilter.cpp @@ -33,7 +33,6 @@ #include #include -#include #include #include #include @@ -211,30 +210,36 @@ CPLErr CPL_STDCALL GDALSieveFilter(GDALRasterBandH hSrcBand, /* -------------------------------------------------------------------- */ int nXSize = GDALGetRasterBandXSize(hSrcBand); int nYSize = GDALGetRasterBandYSize(hSrcBand); - auto *panLastLineVal = static_cast( - VSI_MALLOC2_VERBOSE(sizeof(std::int64_t), nXSize)); - auto *panThisLineVal = static_cast( - VSI_MALLOC2_VERBOSE(sizeof(std::int64_t), nXSize)); - auto *panLastLineId = - static_cast(VSI_MALLOC2_VERBOSE(sizeof(GInt32), nXSize)); - auto *panThisLineId = - static_cast(VSI_MALLOC2_VERBOSE(sizeof(GInt32), nXSize)); - auto *panThisLineWriteVal = static_cast( - VSI_MALLOC2_VERBOSE(sizeof(std::int64_t), nXSize)); - GByte *pabyMaskLine = hMaskBand != nullptr - ? static_cast(VSI_MALLOC_VERBOSE(nXSize)) - : nullptr; + auto panLastLineValKeeper = std::unique_ptr( + static_cast( + VSI_MALLOC2_VERBOSE(sizeof(std::int64_t), nXSize))); + auto panThisLineValKeeper = std::unique_ptr( + static_cast( + VSI_MALLOC2_VERBOSE(sizeof(std::int64_t), nXSize))); + auto panLastLineIdKeeper = std::unique_ptr( + static_cast(VSI_MALLOC2_VERBOSE(sizeof(GInt32), nXSize))); + auto panThisLineIdKeeper = std::unique_ptr( + static_cast(VSI_MALLOC2_VERBOSE(sizeof(GInt32), nXSize))); + auto panThisLineWriteValKeeper = + std::unique_ptr( + static_cast( + VSI_MALLOC2_VERBOSE(sizeof(std::int64_t), nXSize))); + auto pabyMaskLineKeeper = std::unique_ptr( + hMaskBand != nullptr ? static_cast(VSI_MALLOC_VERBOSE(nXSize)) + : nullptr); + + auto panLastLineVal = panLastLineValKeeper.get(); + auto panThisLineVal = panThisLineValKeeper.get(); + auto panLastLineId = panLastLineIdKeeper.get(); + auto panThisLineId = panThisLineIdKeeper.get(); + auto panThisLineWriteVal = panThisLineWriteValKeeper.get(); + auto pabyMaskLine = pabyMaskLineKeeper.get(); + if (panLastLineVal == nullptr || panThisLineVal == nullptr || panLastLineId == nullptr || panThisLineId == nullptr || panThisLineWriteVal == nullptr || (hMaskBand != nullptr && pabyMaskLine == nullptr)) { - CPLFree(panThisLineId); - CPLFree(panLastLineId); - CPLFree(panThisLineVal); - CPLFree(panLastLineVal); - CPLFree(panThisLineWriteVal); - CPLFree(pabyMaskLine); return CE_Failure; } @@ -316,12 +321,28 @@ CPLErr CPL_STDCALL GDALSieveFilter(GDALRasterBandH hSrcBand, if (eErr == CE_None) oFirstEnum.CompleteMerges(); + /* -------------------------------------------------------------------- */ + /* Check if there are polygons */ + /* -------------------------------------------------------------------- */ + if (!oFirstEnum.panPolyIdMap || !oFirstEnum.panPolyValue) + { + // Can happen if all pixels are masked + if (hSrcBand == hDstBand) + { + pfnProgress(1.0, "", pProgressArg); + return CE_None; + } + else + { + return GDALRasterBandCopyWholeRaster(hSrcBand, hDstBand, nullptr, + pfnProgress, pProgressArg); + } + } + /* -------------------------------------------------------------------- */ /* Push the sizes of merged polygon fragments into the */ /* merged polygon id's count. */ /* -------------------------------------------------------------------- */ - assert(oFirstEnum.panPolyIdMap != nullptr); // for Coverity - assert(oFirstEnum.panPolyValue != nullptr); // for Coverity for (int iPoly = 0; iPoly < oFirstEnum.nNextPolygonId; iPoly++) { if (oFirstEnum.panPolyIdMap[iPoly] != iPoly) @@ -346,7 +367,16 @@ CPLErr CPL_STDCALL GDALSieveFilter(GDALRasterBandH hSrcBand, GDALRasterPolygonEnumerator oSecondEnum(nConnectedness); std::vector anBigNeighbour; - anBigNeighbour.resize(anPolySizes.size()); + try + { + anBigNeighbour.resize(anPolySizes.size()); + } + catch (const std::exception &) + { + CPLError(CE_Failure, CPLE_OutOfMemory, "%s: Out of memory", + __FUNCTION__); + return CE_Failure; + } for (int iPoly = 0; iPoly < static_cast(anPolySizes.size()); iPoly++) anBigNeighbour[iPoly] = -1; @@ -540,9 +570,7 @@ CPLErr CPL_STDCALL GDALSieveFilter(GDALRasterBandH hSrcBand, /* ==================================================================== */ oSecondEnum.Clear(); - for (int iY = 0; oFirstEnum.panPolyIdMap != nullptr && // for Coverity - eErr == CE_None && iY < nYSize; - iY++) + for (int iY = 0; eErr == CE_None && iY < nYSize; iY++) { /* -------------------------------------------------------------------- */ @@ -627,15 +655,5 @@ CPLErr CPL_STDCALL GDALSieveFilter(GDALRasterBandH hSrcBand, } } - /* -------------------------------------------------------------------- */ - /* Cleanup */ - /* -------------------------------------------------------------------- */ - CPLFree(panThisLineId); - CPLFree(panLastLineId); - CPLFree(panThisLineVal); - CPLFree(panLastLineVal); - CPLFree(panThisLineWriteVal); - CPLFree(pabyMaskLine); - return eErr; } diff --git a/autotest/alg/sieve.py b/autotest/alg/sieve.py index 6407f8a3d605..cf7acde9f565 100755 --- a/autotest/alg/sieve.py +++ b/autotest/alg/sieve.py @@ -328,3 +328,31 @@ def test_sieve_8(): if cs != cs_expected: print("Got: ", cs) pytest.fail("got wrong checksum") + + +############################################################################### +# Test source bands with all masked pixels + + +def test_sieve_all_masked(): + + drv = gdal.GetDriverByName("MEM") + src_ds = drv.Create("", 10, 10, gdal.GDT_Byte) + src_band = src_ds.GetRasterBand(1) + src_band.Fill(1) + + mask_ds = drv.Create("", 10, 10, gdal.GDT_Byte) + mask_band = mask_ds.GetRasterBand(1) + + dst_ds = drv.Create("", 10, 10, gdal.GDT_Byte) + dst_band = dst_ds.GetRasterBand(1) + + expected_cs = src_band.Checksum() + + gdal.SieveFilter(src_band, mask_band, dst_band, 4, 4) + + assert dst_band.Checksum() == expected_cs + + gdal.SieveFilter(src_band, mask_band, src_band, 4, 4) + + assert src_band.Checksum() == expected_cs From 96c16c94433a490040f019889f96ef58319f5547 Mon Sep 17 00:00:00 2001 From: "Peter A. Jonsson" Date: Thu, 20 Jun 2024 11:32:12 +0200 Subject: [PATCH 0194/1119] ubuntu-small/Dockerfile: use libtiff-dev The libtiff5-dev package is a transitional package, so align the Dockerfile with what is already in ubuntu-full. --- docker/ubuntu-small/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/ubuntu-small/Dockerfile b/docker/ubuntu-small/Dockerfile index 9f859eb7866c..2e57b4a1a3bf 100644 --- a/docker/ubuntu-small/Dockerfile +++ b/docker/ubuntu-small/Dockerfile @@ -45,7 +45,7 @@ RUN . /buildscripts/bh-set-envvars.sh \ build-essential ca-certificates \ git make cmake wget unzip libtool automake \ zlib1g-dev${APT_ARCH_SUFFIX} libsqlite3-dev${APT_ARCH_SUFFIX} pkg-config sqlite3 libcurl4-gnutls-dev${APT_ARCH_SUFFIX} \ - libtiff5-dev${APT_ARCH_SUFFIX} \ + libtiff-dev${APT_ARCH_SUFFIX} \ && rm -rf /var/lib/apt/lists/* # Setup build env for GDAL From f4fdda28c004ff8c5939321189705d9b381946c1 Mon Sep 17 00:00:00 2001 From: "Peter A. Jonsson" Date: Thu, 20 Jun 2024 11:36:41 +0200 Subject: [PATCH 0195/1119] ubuntu-small/Dockerfile: use libcurl4-openssl-dev This aligns the ubuntu-small Dockerfile with a change from commit dc9e272f392781. --- docker/ubuntu-small/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/ubuntu-small/Dockerfile b/docker/ubuntu-small/Dockerfile index 2e57b4a1a3bf..18cc07288604 100644 --- a/docker/ubuntu-small/Dockerfile +++ b/docker/ubuntu-small/Dockerfile @@ -44,7 +44,7 @@ RUN . /buildscripts/bh-set-envvars.sh \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --fix-missing --no-install-recommends \ build-essential ca-certificates \ git make cmake wget unzip libtool automake \ - zlib1g-dev${APT_ARCH_SUFFIX} libsqlite3-dev${APT_ARCH_SUFFIX} pkg-config sqlite3 libcurl4-gnutls-dev${APT_ARCH_SUFFIX} \ + zlib1g-dev${APT_ARCH_SUFFIX} libsqlite3-dev${APT_ARCH_SUFFIX} pkg-config sqlite3 libcurl4-openssl-dev${APT_ARCH_SUFFIX} \ libtiff-dev${APT_ARCH_SUFFIX} \ && rm -rf /var/lib/apt/lists/* From 9ef07acc4f0470dec3dc8b3420e37e13bed53995 Mon Sep 17 00:00:00 2001 From: "Peter A. Jonsson" Date: Thu, 20 Jun 2024 11:41:31 +0200 Subject: [PATCH 0196/1119] ubuntu-full/Dockerfile: whitespace changes Align whitespace and comments with the ubuntu-small Dockerfile to make it easier to spot differences. --- docker/ubuntu-full/Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker/ubuntu-full/Dockerfile b/docker/ubuntu-full/Dockerfile index 14fbb356bb5a..a11fdc7b1beb 100644 --- a/docker/ubuntu-full/Dockerfile +++ b/docker/ubuntu-full/Dockerfile @@ -36,7 +36,6 @@ RUN . /buildscripts/bh-set-envvars.sh \ && rm -rf /var/lib/apt/lists/*; \ fi - # Setup build env for PROJ USER root RUN . /buildscripts/bh-set-envvars.sh \ @@ -298,13 +297,12 @@ ARG PROJ_VERSION=master RUN . /buildscripts/bh-set-envvars.sh \ && /buildscripts/bh-proj.sh +# Build GDAL ARG GDAL_VERSION=master ARG GDAL_RELEASE_DATE ARG GDAL_BUILD_IS_RELEASE ARG GDAL_REPOSITORY=OSGeo/gdal -# Build GDAL - COPY ./bh-gdal.sh /buildscripts/bh-gdal.sh RUN . /buildscripts/bh-set-envvars.sh \ && /buildscripts/bh-gdal.sh From 366606f99f15962a0dc17974e6bcf721834441be Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 19 Jun 2024 17:54:24 +0200 Subject: [PATCH 0197/1119] Overview generation: fix multi-threaded bug, resulting in locks/crashes with GeoPackage in particular Fixes #10245 --- gcore/gdal_priv.h | 75 ++++- gcore/overview.cpp | 717 +++++++++++++++++++++++---------------------- gcore/rasterio.cpp | 114 ++++--- 3 files changed, 502 insertions(+), 404 deletions(-) diff --git a/gcore/gdal_priv.h b/gcore/gdal_priv.h index 6e6126c50f4b..d01e4de2c8c3 100644 --- a/gcore/gdal_priv.h +++ b/gcore/gdal_priv.h @@ -4207,15 +4207,72 @@ CPLErr CPL_DLL GDALRegenerateOverviewsMultiBand( const char *pszResampling, GDALProgressFunc pfnProgress, void *pProgressData, CSLConstList papszOptions); -typedef CPLErr (*GDALResampleFunction)( - double dfXRatioDstToSrc, double dfYRatioDstToSrc, double dfSrcXDelta, - double dfSrcYDelta, GDALDataType eWrkDataType, const void *pChunk, - const GByte *pabyChunkNodataMask, int nChunkXOff, int nChunkXSize, - int nChunkYOff, int nChunkYSize, int nDstXOff, int nDstXOff2, int nDstYOff, - int nDstYOff2, GDALRasterBand *poOverview, void **ppDstBuffer, - GDALDataType *peDstBufferDataType, const char *pszResampling, - bool bHasNoData, double dfNoDataValue, GDALColorTable *poColorTable, - GDALDataType eSrcDataType, bool bPropagateNoData); +/************************************************************************/ +/* GDALOverviewResampleArgs */ +/************************************************************************/ + +/** Arguments for overview resampling function. */ +// Should not contain any dataset/rasterband object, as this might be +// read in a worker thread. +struct GDALOverviewResampleArgs +{ + //! Datatype of the source band argument + GDALDataType eSrcDataType = GDT_Unknown; + //! Datatype of the destination/overview band + GDALDataType eOvrDataType = GDT_Unknown; + //! Width in pixel of the destination/overview band + int nOvrXSize = 0; + //! Height in pixel of the destination/overview band + int nOvrYSize = 0; + //! NBITS value of the destination/overview band (or 0 if not set) + int nOvrNBITS = 0; + //! Factor to convert from destination X to source X + // (source width divided by destination width) + double dfXRatioDstToSrc = 0; + //! Factor to convert from destination Y to source Y + // (source height divided by destination height) + double dfYRatioDstToSrc = 0; + //! Sub-pixel delta to add to get source X + double dfSrcXDelta = 0; + //! Sub-pixel delta to add to get source Y + double dfSrcYDelta = 0; + //! Working data type (data type of the pChunk argument) + GDALDataType eWrkDataType = GDT_Unknown; + //! Array of nChunkXSize * nChunkYSize values of mask, or nullptr + const GByte *pabyChunkNodataMask = nullptr; + //! X offset of the source chunk in the source band + int nChunkXOff = 0; + //! Width in pixel of the source chunk in the source band + int nChunkXSize = 0; + //! Y offset of the source chunk in the source band + int nChunkYOff = 0; + //! Height in pixel of the source chunk in the source band + int nChunkYSize = 0; + //! X Offset of the destination chunk in the destination band + int nDstXOff = 0; + //! X Offset of the end (not included) of the destination chunk in the destination band + int nDstXOff2 = 0; + //! Y Offset of the destination chunk in the destination band + int nDstYOff = 0; + //! Y Offset of the end (not included) of the destination chunk in the destination band + int nDstYOff2 = 0; + //! Resampling method + const char *pszResampling = nullptr; + //! Whether the source band has a nodata value + bool bHasNoData = false; + //! Source band nodata value + double dfNoDataValue = 0; + //! Source color table + const GDALColorTable *poColorTable = nullptr; + //! Whether a single contributing source pixel at nodata should result + // in the target pixel to be at nodata too (only taken into account by + // average resampling) + bool bPropagateNoData = false; +}; + +typedef CPLErr (*GDALResampleFunction)(const GDALOverviewResampleArgs &args, + const void *pChunk, void **ppDstBuffer, + GDALDataType *peDstBufferDataType); GDALResampleFunction GDALGetResampleFunction(const char *pszResampling, int *pnRadius); diff --git a/gcore/overview.cpp b/gcore/overview.cpp index 098b0eae4b21..c962aa73bfc2 100644 --- a/gcore/overview.cpp +++ b/gcore/overview.cpp @@ -83,12 +83,20 @@ /************************************************************************/ template -static CPLErr GDALResampleChunk_NearT( - double dfXRatioDstToSrc, double dfYRatioDstToSrc, GDALDataType eWrkDataType, - const T *pChunk, int nChunkXOff, int nChunkXSize, int nChunkYOff, - int nDstXOff, int nDstXOff2, int nDstYOff, int nDstYOff2, T **ppDstBuffer) +static CPLErr GDALResampleChunk_NearT(const GDALOverviewResampleArgs &args, + const T *pChunk, T **ppDstBuffer) { + const double dfXRatioDstToSrc = args.dfXRatioDstToSrc; + const double dfYRatioDstToSrc = args.dfYRatioDstToSrc; + const GDALDataType eWrkDataType = args.eWrkDataType; + const int nChunkXOff = args.nChunkXOff; + const int nChunkXSize = args.nChunkXSize; + const int nChunkYOff = args.nChunkYOff; + const int nDstXOff = args.nDstXOff; + const int nDstXOff2 = args.nDstXOff2; + const int nDstYOff = args.nDstYOff; + const int nDstYOff2 = args.nDstYOff2; const int nDstXWidth = nDstXOff2 - nDstXOff; /* -------------------------------------------------------------------- */ @@ -155,44 +163,44 @@ static CPLErr GDALResampleChunk_NearT( return CE_None; } -static CPLErr GDALResampleChunk_Near( - double dfXRatioDstToSrc, double dfYRatioDstToSrc, double /* dfSrcXDelta */, - double /* dfSrcYDelta */, GDALDataType eWrkDataType, const void *pChunk, - const GByte * /* pabyChunkNodataMask_unused */, int nChunkXOff, - int nChunkXSize, int nChunkYOff, int /* nChunkYSize */, int nDstXOff, - int nDstXOff2, int nDstYOff, int nDstYOff2, GDALRasterBand * /*poOverview*/, - void **ppDstBuffer, GDALDataType *peDstBufferDataType, - const char * /* pszResampling_unused */, bool /* bHasNoData_unused */, - double /* fNoDataValue_unused */, - GDALColorTable * /* poColorTable_unused */, GDALDataType /* eSrcDataType */, - bool /* bPropagateNoData */) +static CPLErr GDALResampleChunk_Near(const GDALOverviewResampleArgs &args, + const void *pChunk, void **ppDstBuffer, + GDALDataType *peDstBufferDataType) { - *peDstBufferDataType = eWrkDataType; - if (eWrkDataType == GDT_Byte) - return GDALResampleChunk_NearT( - dfXRatioDstToSrc, dfYRatioDstToSrc, eWrkDataType, - static_cast(pChunk), nChunkXOff, nChunkXSize, - nChunkYOff, nDstXOff, nDstXOff2, nDstYOff, nDstYOff2, - reinterpret_cast(ppDstBuffer)); - else if (eWrkDataType == GDT_UInt16) - return GDALResampleChunk_NearT( - dfXRatioDstToSrc, dfYRatioDstToSrc, eWrkDataType, - static_cast(pChunk), nChunkXOff, nChunkXSize, - nChunkYOff, nDstXOff, nDstXOff2, nDstYOff, nDstYOff2, - reinterpret_cast(ppDstBuffer)); - else if (eWrkDataType == GDT_Float32) - return GDALResampleChunk_NearT( - dfXRatioDstToSrc, dfYRatioDstToSrc, eWrkDataType, - static_cast(pChunk), nChunkXOff, nChunkXSize, - nChunkYOff, nDstXOff, nDstXOff2, nDstYOff, nDstYOff2, - reinterpret_cast(ppDstBuffer)); - else if (eWrkDataType == GDT_Float64) - return GDALResampleChunk_NearT( - dfXRatioDstToSrc, dfYRatioDstToSrc, eWrkDataType, - static_cast(pChunk), nChunkXOff, nChunkXSize, - nChunkYOff, nDstXOff, nDstXOff2, nDstYOff, nDstYOff2, - reinterpret_cast(ppDstBuffer)); + *peDstBufferDataType = args.eWrkDataType; + switch (args.eWrkDataType) + { + case GDT_Byte: + { + return GDALResampleChunk_NearT( + args, static_cast(pChunk), + reinterpret_cast(ppDstBuffer)); + } + + case GDT_UInt16: + { + return GDALResampleChunk_NearT( + args, static_cast(pChunk), + reinterpret_cast(ppDstBuffer)); + } + + case GDT_Float32: + { + return GDALResampleChunk_NearT( + args, static_cast(pChunk), + reinterpret_cast(ppDstBuffer)); + } + + case GDT_Float64: + { + return GDALResampleChunk_NearT( + args, static_cast(pChunk), + reinterpret_cast(ppDstBuffer)); + } + default: + break; + } CPLAssert(false); return CE_Failure; } @@ -1110,15 +1118,29 @@ static int AverageFloatSSE2(int nDstXWidth, int nChunkXSize, /************************************************************************/ template -static CPLErr GDALResampleChunk_AverageOrRMS_T( - double dfXRatioDstToSrc, double dfYRatioDstToSrc, double dfSrcXDelta, - double dfSrcYDelta, const T *pChunk, const GByte *pabyChunkNodataMask, - int nChunkXOff, int nChunkXSize, int nChunkYOff, int nChunkYSize, - int nDstXOff, int nDstXOff2, int nDstYOff, int nDstYOff2, - GDALRasterBand *poOverview, void **ppDstBuffer, const char *pszResampling, - bool bHasNoData, double dfNoDataValue, GDALColorTable *poColorTable, - bool bPropagateNoData) +static CPLErr +GDALResampleChunk_AverageOrRMS_T(const GDALOverviewResampleArgs &args, + const T *pChunk, void **ppDstBuffer) { + const double dfXRatioDstToSrc = args.dfXRatioDstToSrc; + const double dfYRatioDstToSrc = args.dfYRatioDstToSrc; + const double dfSrcXDelta = args.dfSrcXDelta; + const double dfSrcYDelta = args.dfSrcYDelta; + const GByte *pabyChunkNodataMask = args.pabyChunkNodataMask; + const int nChunkXOff = args.nChunkXOff; + const int nChunkYOff = args.nChunkYOff; + const int nChunkXSize = args.nChunkXSize; + const int nChunkYSize = args.nChunkYSize; + const int nDstXOff = args.nDstXOff; + const int nDstXOff2 = args.nDstXOff2; + const int nDstYOff = args.nDstYOff; + const int nDstYOff2 = args.nDstYOff2; + const char *pszResampling = args.pszResampling; + bool bHasNoData = args.bHasNoData; + const double dfNoDataValue = args.dfNoDataValue; + const GDALColorTable *poColorTable = args.poColorTable; + const bool bPropagateNoData = args.bPropagateNoData; + // AVERAGE_BIT2GRAYSCALE const bool bBit2Grayscale = CPL_TO_BOOL(STARTS_WITH_CI(pszResampling, "AVERAGE_BIT2G")); @@ -1133,7 +1155,7 @@ static CPLErr GDALResampleChunk_AverageOrRMS_T( tNoDataValue = static_cast(dfNoDataValue); const T tReplacementVal = bHasNoData ? static_cast(GDALGetNoDataReplacementValue( - poOverview->GetRasterDataType(), dfNoDataValue)) + args.eOvrDataType, dfNoDataValue)) : 0; int nChunkRightXOff = nChunkXOff + nChunkXSize; @@ -1185,7 +1207,7 @@ static CPLErr GDALResampleChunk_AverageOrRMS_T( // transparent, consider it as the nodata value else if (!bHasNoData && nTransparentIdx >= 0) { - bHasNoData = TRUE; + bHasNoData = true; tNoDataValue = static_cast(nTransparentIdx); } @@ -1708,73 +1730,52 @@ static CPLErr GDALResampleChunk_AverageOrRMS_T( return CE_None; } -static CPLErr GDALResampleChunk_AverageOrRMS( - double dfXRatioDstToSrc, double dfYRatioDstToSrc, double dfSrcXDelta, - double dfSrcYDelta, GDALDataType eWrkDataType, const void *pChunk, - const GByte *pabyChunkNodataMask, int nChunkXOff, int nChunkXSize, - int nChunkYOff, int nChunkYSize, int nDstXOff, int nDstXOff2, int nDstYOff, - int nDstYOff2, GDALRasterBand *poOverview, void **ppDstBuffer, - GDALDataType *peDstBufferDataType, const char *pszResampling, - bool bHasNoData, double dfNoDataValue, GDALColorTable *poColorTable, - GDALDataType /* eSrcDataType */, bool bPropagateNoData) +static CPLErr +GDALResampleChunk_AverageOrRMS(const GDALOverviewResampleArgs &args, + const void *pChunk, void **ppDstBuffer, + GDALDataType *peDstBufferDataType) { - if (eWrkDataType == GDT_Byte) + *peDstBufferDataType = args.eWrkDataType; + switch (args.eWrkDataType) { - *peDstBufferDataType = eWrkDataType; - return GDALResampleChunk_AverageOrRMS_T( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), pabyChunkNodataMask, nChunkXOff, - nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, nDstXOff2, nDstYOff, - nDstYOff2, poOverview, ppDstBuffer, pszResampling, bHasNoData, - dfNoDataValue, poColorTable, bPropagateNoData); - } - else if (eWrkDataType == GDT_UInt16) - { - *peDstBufferDataType = eWrkDataType; - if (EQUAL(pszResampling, "RMS")) + case GDT_Byte: { - // Use double as accumulation type, because UInt32 could overflow - return GDALResampleChunk_AverageOrRMS_T( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), pabyChunkNodataMask, - nChunkXOff, nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, - nDstXOff2, nDstYOff, nDstYOff2, poOverview, ppDstBuffer, - pszResampling, bHasNoData, dfNoDataValue, poColorTable, - bPropagateNoData); + return GDALResampleChunk_AverageOrRMS_T( + args, static_cast(pChunk), ppDstBuffer); } - else + + case GDT_UInt16: { - return GDALResampleChunk_AverageOrRMS_T( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), pabyChunkNodataMask, - nChunkXOff, nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, - nDstXOff2, nDstYOff, nDstYOff2, poOverview, ppDstBuffer, - pszResampling, bHasNoData, dfNoDataValue, poColorTable, - bPropagateNoData); + if (EQUAL(args.pszResampling, "RMS")) + { + // Use double as accumulation type, because UInt32 could overflow + return GDALResampleChunk_AverageOrRMS_T( + args, static_cast(pChunk), ppDstBuffer); + } + else + { + return GDALResampleChunk_AverageOrRMS_T( + args, static_cast(pChunk), ppDstBuffer); + } } - } - else if (eWrkDataType == GDT_Float32) - { - *peDstBufferDataType = eWrkDataType; - return GDALResampleChunk_AverageOrRMS_T( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), pabyChunkNodataMask, nChunkXOff, - nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, nDstXOff2, nDstYOff, - nDstYOff2, poOverview, ppDstBuffer, pszResampling, bHasNoData, - dfNoDataValue, poColorTable, bPropagateNoData); - } - else if (eWrkDataType == GDT_Float64) - { - *peDstBufferDataType = eWrkDataType; - return GDALResampleChunk_AverageOrRMS_T( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), pabyChunkNodataMask, - nChunkXOff, nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, - nDstXOff2, nDstYOff, nDstYOff2, poOverview, ppDstBuffer, - pszResampling, bHasNoData, dfNoDataValue, poColorTable, - bPropagateNoData); + + case GDT_Float32: + { + return GDALResampleChunk_AverageOrRMS_T( + args, static_cast(pChunk), ppDstBuffer); + } + + case GDT_Float64: + { + return GDALResampleChunk_AverageOrRMS_T( + args, static_cast(pChunk), ppDstBuffer); + } + + default: + break; } CPLAssert(false); @@ -1785,18 +1786,26 @@ static CPLErr GDALResampleChunk_AverageOrRMS( /* GDALResampleChunk_Gauss() */ /************************************************************************/ -static CPLErr GDALResampleChunk_Gauss( - double dfXRatioDstToSrc, double dfYRatioDstToSrc, double /* dfSrcXDelta */, - double /* dfSrcYDelta */, GDALDataType /* eWrkDataType */, - const void *pChunk, const GByte *pabyChunkNodataMask, int nChunkXOff, - int nChunkXSize, int nChunkYOff, int nChunkYSize, int nDstXOff, - int nDstXOff2, int nDstYOff, int nDstYOff2, GDALRasterBand *poOverview, - void **ppDstBuffer, GDALDataType *peDstBufferDataType, - const char * /* pszResampling */, bool bHasNoData, double dfNoDataValue, - GDALColorTable *poColorTable, GDALDataType /* eSrcDataType */, - bool /* bPropagateNoData */) +static CPLErr GDALResampleChunk_Gauss(const GDALOverviewResampleArgs &args, + const void *pChunk, void **ppDstBuffer, + GDALDataType *peDstBufferDataType) { + const double dfXRatioDstToSrc = args.dfXRatioDstToSrc; + const double dfYRatioDstToSrc = args.dfYRatioDstToSrc; + const GByte *pabyChunkNodataMask = args.pabyChunkNodataMask; + const int nChunkXOff = args.nChunkXOff; + const int nChunkXSize = args.nChunkXSize; + const int nChunkYOff = args.nChunkYOff; + const int nChunkYSize = args.nChunkYSize; + const int nDstXOff = args.nDstXOff; + const int nDstXOff2 = args.nDstXOff2; + const int nDstYOff = args.nDstYOff; + const int nDstYOff2 = args.nDstYOff2; + const bool bHasNoData = args.bHasNoData; + double dfNoDataValue = args.dfNoDataValue; + const GDALColorTable *poColorTable = args.poColorTable; + const double *const padfChunk = static_cast(pChunk); *ppDstBuffer = @@ -1824,8 +1833,8 @@ static CPLErr GDALResampleChunk_Gauss( 120, 20, 15, 90, 225, 300, 225, 90, 15, 6, 36, 90, 120, 90, 36, 6, 1, 6, 15, 20, 15, 6, 1}; - const int nOXSize = poOverview->GetXSize(); - const int nOYSize = poOverview->GetYSize(); + const int nOXSize = args.nOvrXSize; + const int nOYSize = args.nOvrYSize; const int nResYFactor = static_cast(0.5 + dfYRatioDstToSrc); // matrix for gauss filter @@ -2068,15 +2077,27 @@ static CPLErr GDALResampleChunk_Gauss( /************************************************************************/ template -static CPLErr GDALResampleChunk_Mode_T( - double dfXRatioDstToSrc, double dfYRatioDstToSrc, double dfSrcXDelta, - double dfSrcYDelta, const T *pChunk, const GByte *pabyChunkNodataMask, - int nChunkXOff, int nChunkXSize, int nChunkYOff, int nChunkYSize, - int nDstXOff, int nDstXOff2, int nDstYOff, int nDstYOff2, - T *const pDstBuffer, bool bHasNoData, double dfNoDataValue, - GDALColorTable *poColorTable, GDALDataType eSrcDataType) +static CPLErr GDALResampleChunk_Mode_T(const GDALOverviewResampleArgs &args, + const T *pChunk, T *const pDstBuffer) { + const double dfXRatioDstToSrc = args.dfXRatioDstToSrc; + const double dfYRatioDstToSrc = args.dfYRatioDstToSrc; + const double dfSrcXDelta = args.dfSrcXDelta; + const double dfSrcYDelta = args.dfSrcYDelta; + const GByte *pabyChunkNodataMask = args.pabyChunkNodataMask; + const int nChunkXOff = args.nChunkXOff; + const int nChunkXSize = args.nChunkXSize; + const int nChunkYOff = args.nChunkYOff; + const int nChunkYSize = args.nChunkYSize; + const int nDstXOff = args.nDstXOff; + const int nDstXOff2 = args.nDstXOff2; + const int nDstYOff = args.nDstYOff; + const int nDstYOff2 = args.nDstYOff2; + const bool bHasNoData = args.bHasNoData; + const double dfNoDataValue = args.dfNoDataValue; + const GDALColorTable *poColorTable = args.poColorTable; + const GDALDataType eSrcDataType = args.eSrcDataType; const int nDstXSize = nDstXOff2 - nDstXOff; T tNoDataValue; @@ -2310,61 +2331,51 @@ static CPLErr GDALResampleChunk_Mode_T( return CE_None; } -static CPLErr GDALResampleChunk_Mode( - double dfXRatioDstToSrc, double dfYRatioDstToSrc, double dfSrcXDelta, - double dfSrcYDelta, GDALDataType eWrkDataType, const void *pChunk, - const GByte *pabyChunkNodataMask, int nChunkXOff, int nChunkXSize, - int nChunkYOff, int nChunkYSize, int nDstXOff, int nDstXOff2, int nDstYOff, - int nDstYOff2, GDALRasterBand * /*poOverview*/, void **ppDstBuffer, - GDALDataType *peDstBufferDataType, const char * /* pszResampling */, - bool bHasNoData, double dfNoDataValue, GDALColorTable *poColorTable, - GDALDataType eSrcDataType, bool /*bPropagateNoData*/) +static CPLErr GDALResampleChunk_Mode(const GDALOverviewResampleArgs &args, + const void *pChunk, void **ppDstBuffer, + GDALDataType *peDstBufferDataType) { - *ppDstBuffer = - VSI_MALLOC3_VERBOSE(nDstXOff2 - nDstXOff, nDstYOff2 - nDstYOff, - GDALGetDataTypeSizeBytes(eWrkDataType)); + *ppDstBuffer = VSI_MALLOC3_VERBOSE( + args.nDstXOff2 - args.nDstXOff, args.nDstYOff2 - args.nDstYOff, + GDALGetDataTypeSizeBytes(args.eWrkDataType)); if (*ppDstBuffer == nullptr) { return CE_Failure; } - *peDstBufferDataType = eWrkDataType; - if (eWrkDataType == GDT_Byte) + *peDstBufferDataType = args.eWrkDataType; + switch (args.eWrkDataType) { - return GDALResampleChunk_Mode_T( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), pabyChunkNodataMask, nChunkXOff, - nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, nDstXOff2, nDstYOff, - nDstYOff2, static_cast(*ppDstBuffer), bHasNoData, - dfNoDataValue, poColorTable, eSrcDataType); - } - else if (eWrkDataType == GDT_UInt16) - { - return GDALResampleChunk_Mode_T( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), pabyChunkNodataMask, - nChunkXOff, nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, - nDstXOff2, nDstYOff, nDstYOff2, - static_cast(*ppDstBuffer), bHasNoData, dfNoDataValue, - poColorTable, eSrcDataType); - } - else if (eWrkDataType == GDT_Float32) - { - return GDALResampleChunk_Mode_T( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), pabyChunkNodataMask, nChunkXOff, - nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, nDstXOff2, nDstYOff, - nDstYOff2, static_cast(*ppDstBuffer), bHasNoData, - dfNoDataValue, poColorTable, eSrcDataType); - } - else if (eWrkDataType == GDT_Float64) - { - return GDALResampleChunk_Mode_T( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), pabyChunkNodataMask, - nChunkXOff, nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, - nDstXOff2, nDstYOff, nDstYOff2, static_cast(*ppDstBuffer), - bHasNoData, dfNoDataValue, poColorTable, eSrcDataType); + case GDT_Byte: + { + return GDALResampleChunk_Mode_T( + args, static_cast(pChunk), + static_cast(*ppDstBuffer)); + } + + case GDT_UInt16: + { + return GDALResampleChunk_Mode_T( + args, static_cast(pChunk), + static_cast(*ppDstBuffer)); + } + + case GDT_Float32: + { + return GDALResampleChunk_Mode_T( + args, static_cast(pChunk), + static_cast(*ppDstBuffer)); + } + + case GDT_Float64: + { + return GDALResampleChunk_Mode_T( + args, static_cast(pChunk), + static_cast(*ppDstBuffer)); + } + + default: + break; } CPLAssert(false); @@ -2989,19 +3000,31 @@ inline void GDALResampleConvolutionHorizontalPixelCount4_3rows( template static CPLErr GDALResampleChunk_ConvolutionT( - double dfXRatioDstToSrc, double dfYRatioDstToSrc, double dfSrcXDelta, - double dfSrcYDelta, const T *pChunk, int nBands, - const GByte *pabyChunkNodataMask, int nChunkXOff, int nChunkXSize, - int nChunkYOff, int nChunkYSize, int nDstXOff, int nDstXOff2, int nDstYOff, - int nDstYOff2, GDALRasterBand *poDstBand, void *pDstBuffer, bool bHasNoData, - double dfNoDataValue, FilterFuncType pfnFilterFunc, - FilterFunc4ValuesType pfnFilterFunc4Values, int nKernelRadius, - bool bKernelWithNegativeWeights, float fMaxVal) + const GDALOverviewResampleArgs &args, const T *pChunk, void *pDstBuffer, + FilterFuncType pfnFilterFunc, FilterFunc4ValuesType pfnFilterFunc4Values, + int nKernelRadius, bool bKernelWithNegativeWeights, float fMaxVal) { + const double dfXRatioDstToSrc = args.dfXRatioDstToSrc; + const double dfYRatioDstToSrc = args.dfYRatioDstToSrc; + const double dfSrcXDelta = args.dfSrcXDelta; + const double dfSrcYDelta = args.dfSrcYDelta; + constexpr int nBands = 1; + const GByte *pabyChunkNodataMask = args.pabyChunkNodataMask; + const int nChunkXOff = args.nChunkXOff; + const int nChunkXSize = args.nChunkXSize; + const int nChunkYOff = args.nChunkYOff; + const int nChunkYSize = args.nChunkYSize; + const int nDstXOff = args.nDstXOff; + const int nDstXOff2 = args.nDstXOff2; + const int nDstYOff = args.nDstYOff; + const int nDstYOff2 = args.nDstYOff2; + const bool bHasNoData = args.bHasNoData; + double dfNoDataValue = args.dfNoDataValue; + if (!bHasNoData) dfNoDataValue = 0.0; - const auto dstDataType = poDstBand->GetRasterDataType(); + const auto dstDataType = args.eOvrDataType; const int nDstDataTypeSize = GDALGetDataTypeSizeBytes(dstDataType); const double dfReplacementVal = bHasNoData ? GDALGetNoDataReplacementValue(dstDataType, dfNoDataValue) @@ -3578,29 +3601,23 @@ static CPLErr GDALResampleChunk_ConvolutionT( return CE_None; } -static CPLErr GDALResampleChunk_Convolution( - double dfXRatioDstToSrc, double dfYRatioDstToSrc, double dfSrcXDelta, - double dfSrcYDelta, GDALDataType eWrkDataType, const void *pChunk, - const GByte *pabyChunkNodataMask, int nChunkXOff, int nChunkXSize, - int nChunkYOff, int nChunkYSize, int nDstXOff, int nDstXOff2, int nDstYOff, - int nDstYOff2, GDALRasterBand *poOverview, void **ppDstBuffer, - GDALDataType *peDstBufferDataType, const char *pszResampling, - bool bHasNoData, double dfNoDataValue, - GDALColorTable * /* poColorTable_unused */, GDALDataType /* eSrcDataType */, - bool /* bPropagateNoData */) +static CPLErr +GDALResampleChunk_Convolution(const GDALOverviewResampleArgs &args, + const void *pChunk, void **ppDstBuffer, + GDALDataType *peDstBufferDataType) { GDALResampleAlg eResample; bool bKernelWithNegativeWeights = false; - if (EQUAL(pszResampling, "BILINEAR")) + if (EQUAL(args.pszResampling, "BILINEAR")) eResample = GRA_Bilinear; - else if (EQUAL(pszResampling, "CUBIC")) + else if (EQUAL(args.pszResampling, "CUBIC")) { eResample = GRA_Cubic; bKernelWithNegativeWeights = true; } - else if (EQUAL(pszResampling, "CUBICSPLINE")) + else if (EQUAL(args.pszResampling, "CUBICSPLINE")) eResample = GRA_CubicSpline; - else if (EQUAL(pszResampling, "LANCZOS")) + else if (EQUAL(args.pszResampling, "LANCZOS")) { eResample = GRA_Lanczos; bKernelWithNegativeWeights = true; @@ -3618,60 +3635,63 @@ static CPLErr GDALResampleChunk_Convolution( float fMaxVal = 0.f; // Cubic, etc... can have overshoots, so make sure we clamp values to the // maximum value if NBITS is set. - const char *pszNBITS = - poOverview->GetMetadataItem("NBITS", "IMAGE_STRUCTURE"); - GDALDataType eBandDT = poOverview->GetRasterDataType(); - if (eResample != GRA_Bilinear && pszNBITS != nullptr && - (eBandDT == GDT_Byte || eBandDT == GDT_UInt16 || eBandDT == GDT_UInt32)) + if (eResample != GRA_Bilinear && args.nOvrNBITS > 0 && + (args.eOvrDataType == GDT_Byte || args.eOvrDataType == GDT_UInt16 || + args.eOvrDataType == GDT_UInt32)) { - int nBits = atoi(pszNBITS); - if (nBits == GDALGetDataTypeSize(eBandDT)) + int nBits = args.nOvrNBITS; + if (nBits == GDALGetDataTypeSize(args.eOvrDataType)) nBits = 0; if (nBits > 0 && nBits < 32) fMaxVal = static_cast((1U << nBits) - 1); } - *ppDstBuffer = - VSI_MALLOC3_VERBOSE(nDstXOff2 - nDstXOff, nDstYOff2 - nDstYOff, - GDALGetDataTypeSizeBytes(eBandDT)); + *ppDstBuffer = VSI_MALLOC3_VERBOSE( + args.nDstXOff2 - args.nDstXOff, args.nDstYOff2 - args.nDstYOff, + GDALGetDataTypeSizeBytes(args.eOvrDataType)); if (*ppDstBuffer == nullptr) { return CE_Failure; } - *peDstBufferDataType = eBandDT; - - if (eWrkDataType == GDT_Byte) - return GDALResampleChunk_ConvolutionT( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), 1, pabyChunkNodataMask, - nChunkXOff, nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, - nDstXOff2, nDstYOff, nDstYOff2, poOverview, *ppDstBuffer, - bHasNoData, dfNoDataValue, pfnFilterFunc, pfnFilterFunc4Values, - nKernelRadius, bKernelWithNegativeWeights, fMaxVal); - else if (eWrkDataType == GDT_UInt16) - return GDALResampleChunk_ConvolutionT( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), 1, pabyChunkNodataMask, - nChunkXOff, nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, - nDstXOff2, nDstYOff, nDstYOff2, poOverview, *ppDstBuffer, - bHasNoData, dfNoDataValue, pfnFilterFunc, pfnFilterFunc4Values, - nKernelRadius, bKernelWithNegativeWeights, fMaxVal); - else if (eWrkDataType == GDT_Float32) - return GDALResampleChunk_ConvolutionT( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), 1, pabyChunkNodataMask, - nChunkXOff, nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, - nDstXOff2, nDstYOff, nDstYOff2, poOverview, *ppDstBuffer, - bHasNoData, dfNoDataValue, pfnFilterFunc, pfnFilterFunc4Values, - nKernelRadius, bKernelWithNegativeWeights, fMaxVal); - else if (eWrkDataType == GDT_Float64) - return GDALResampleChunk_ConvolutionT( - dfXRatioDstToSrc, dfYRatioDstToSrc, dfSrcXDelta, dfSrcYDelta, - static_cast(pChunk), 1, pabyChunkNodataMask, - nChunkXOff, nChunkXSize, nChunkYOff, nChunkYSize, nDstXOff, - nDstXOff2, nDstYOff, nDstYOff2, poOverview, *ppDstBuffer, - bHasNoData, dfNoDataValue, pfnFilterFunc, pfnFilterFunc4Values, - nKernelRadius, bKernelWithNegativeWeights, fMaxVal); + *peDstBufferDataType = args.eOvrDataType; + + switch (args.eWrkDataType) + { + case GDT_Byte: + { + return GDALResampleChunk_ConvolutionT( + args, static_cast(pChunk), *ppDstBuffer, + pfnFilterFunc, pfnFilterFunc4Values, nKernelRadius, + bKernelWithNegativeWeights, fMaxVal); + } + + case GDT_UInt16: + { + return GDALResampleChunk_ConvolutionT( + args, static_cast(pChunk), *ppDstBuffer, + pfnFilterFunc, pfnFilterFunc4Values, nKernelRadius, + bKernelWithNegativeWeights, fMaxVal); + } + + case GDT_Float32: + { + return GDALResampleChunk_ConvolutionT( + args, static_cast(pChunk), *ppDstBuffer, + pfnFilterFunc, pfnFilterFunc4Values, nKernelRadius, + bKernelWithNegativeWeights, fMaxVal); + } + + case GDT_Float64: + { + return GDALResampleChunk_ConvolutionT( + args, static_cast(pChunk), *ppDstBuffer, + pfnFilterFunc, pfnFilterFunc4Values, nKernelRadius, + bKernelWithNegativeWeights, fMaxVal); + } + + default: + break; + } CPLAssert(false); return CE_Failure; @@ -3681,11 +3701,11 @@ static CPLErr GDALResampleChunk_Convolution( /* GDALResampleChunkC32R() */ /************************************************************************/ -static CPLErr GDALResampleChunkC32R(int nSrcWidth, int nSrcHeight, - const float *pafChunk, int nChunkYOff, - int nChunkYSize, int nDstYOff, - int nDstYOff2, GDALRasterBand *poOverview, - void **ppDstBuffer, +static CPLErr GDALResampleChunkC32R(const int nSrcWidth, const int nSrcHeight, + const float *pafChunk, const int nChunkYOff, + const int nChunkYSize, const int nDstYOff, + const int nDstYOff2, const int nOvrXSize, + const int nOvrYSize, void **ppDstBuffer, GDALDataType *peDstBufferDataType, const char *pszResampling) @@ -3725,7 +3745,7 @@ static CPLErr GDALResampleChunkC32R(int nSrcWidth, int nSrcHeight, return CE_Failure; } - const int nOXSize = poOverview->GetXSize(); + const int nOXSize = nOvrXSize; *ppDstBuffer = VSI_MALLOC3_VERBOSE(nOXSize, nDstYOff2 - nDstYOff, GDALGetDataTypeSizeBytes(GDT_CFloat32)); if (*ppDstBuffer == nullptr) @@ -3735,7 +3755,7 @@ static CPLErr GDALResampleChunkC32R(int nSrcWidth, int nSrcHeight, float *const pafDstBuffer = static_cast(*ppDstBuffer); *peDstBufferDataType = GDT_CFloat32; - const int nOYSize = poOverview->GetYSize(); + const int nOYSize = nOvrYSize; const double dfXRatioDstToSrc = static_cast(nSrcWidth) / nOXSize; const double dfYRatioDstToSrc = static_cast(nSrcHeight) / nOYSize; @@ -4436,27 +4456,15 @@ CPLErr GDALRegenerateOverviewsEx(GDALRasterBandH hSrcBand, int nOverviewCount, std::shared_ptr oSrcBufferHolder{}; std::unique_ptr oDstBufferHolder{}; + GDALRasterBand *poDstBand = nullptr; + // Input parameters of pfnResampleFn GDALResampleFunction pfnResampleFn = nullptr; - double dfXRatioDstToSrc{}; - double dfYRatioDstToSrc{}; - GDALDataType eWrkDataType = GDT_Unknown; - const void *pChunk = nullptr; - const GByte *pabyChunkNodataMask = nullptr; - int nWidth = 0; - int nHeight = 0; - int nChunkYOff = 0; - int nChunkYSize = 0; + int nSrcWidth = 0; + int nSrcHeight = 0; int nDstWidth = 0; - int nDstYOff = 0; - int nDstYOff2 = 0; - GDALRasterBand *poDstBand = nullptr; - const char *pszResampling = nullptr; - bool bHasNoData = false; - double dfNoDataValue = 0.0; - GDALColorTable *poColorTable = nullptr; - GDALDataType eSrcDataType = GDT_Unknown; - bool bPropagateNoData = false; + GDALOverviewResampleArgs args{}; + const void *pChunk = nullptr; // Output values of resampling function CPLErr eErr = CE_Failure; @@ -4486,26 +4494,22 @@ CPLErr GDALRegenerateOverviewsEx(GDALRasterBandH hSrcBand, int nOverviewCount, { OvrJob *poJob = static_cast(pData); - if (poJob->eWrkDataType != GDT_CFloat32) + if (poJob->args.eWrkDataType != GDT_CFloat32) { - poJob->eErr = poJob->pfnResampleFn( - poJob->dfXRatioDstToSrc, poJob->dfYRatioDstToSrc, 0.0, 0.0, - poJob->eWrkDataType, poJob->pChunk, poJob->pabyChunkNodataMask, - 0, poJob->nWidth, poJob->nChunkYOff, poJob->nChunkYSize, 0, - poJob->nDstWidth, poJob->nDstYOff, poJob->nDstYOff2, - poJob->poDstBand, &(poJob->pDstBuffer), - &(poJob->eDstBufferDataType), poJob->pszResampling, - poJob->bHasNoData, poJob->dfNoDataValue, poJob->poColorTable, - poJob->eSrcDataType, poJob->bPropagateNoData); + poJob->eErr = poJob->pfnResampleFn(poJob->args, poJob->pChunk, + &(poJob->pDstBuffer), + &(poJob->eDstBufferDataType)); } else { poJob->eErr = GDALResampleChunkC32R( - poJob->nWidth, poJob->nHeight, - static_cast(poJob->pChunk), poJob->nChunkYOff, - poJob->nChunkYSize, poJob->nDstYOff, poJob->nDstYOff2, - poJob->poDstBand, &(poJob->pDstBuffer), - &(poJob->eDstBufferDataType), poJob->pszResampling); + poJob->nSrcWidth, poJob->nSrcHeight, + static_cast(poJob->pChunk), + poJob->args.nChunkYOff, poJob->args.nChunkYSize, + poJob->args.nDstYOff, poJob->args.nDstYOff2, + poJob->args.nOvrXSize, poJob->args.nOvrYSize, + &(poJob->pDstBuffer), &(poJob->eDstBufferDataType), + poJob->args.pszResampling); } poJob->oDstBufferHolder = @@ -4522,9 +4526,9 @@ CPLErr GDALRegenerateOverviewsEx(GDALRasterBandH hSrcBand, int nOverviewCount, const auto WriteJobData = [](const OvrJob *poJob) { return poJob->poDstBand->RasterIO( - GF_Write, 0, poJob->nDstYOff, poJob->nDstWidth, - poJob->nDstYOff2 - poJob->nDstYOff, poJob->pDstBuffer, - poJob->nDstWidth, poJob->nDstYOff2 - poJob->nDstYOff, + GF_Write, 0, poJob->args.nDstYOff, poJob->nDstWidth, + poJob->args.nDstYOff2 - poJob->args.nDstYOff, poJob->pDstBuffer, + poJob->nDstWidth, poJob->args.nDstYOff2 - poJob->args.nDstYOff, poJob->eDstBufferDataType, 0, 0, nullptr); }; @@ -4812,27 +4816,37 @@ CPLErr GDALRegenerateOverviewsEx(GDALRasterBandH hSrcBand, int nOverviewCount, nDstWidth, nDstYOff2 - nDstYOff); #endif - auto poJob = std::unique_ptr(new OvrJob()); + auto poJob = std::make_unique(); poJob->pfnResampleFn = pfnResampleFn; - poJob->dfXRatioDstToSrc = dfXRatioDstToSrc; - poJob->dfYRatioDstToSrc = dfYRatioDstToSrc; - poJob->eWrkDataType = eWrkDataType; + poJob->args.eOvrDataType = poDstBand->GetRasterDataType(); + poJob->args.nOvrXSize = poDstBand->GetXSize(); + poJob->args.nOvrYSize = poDstBand->GetYSize(); + const char *pszNBITS = + poDstBand->GetMetadataItem("NBITS", "IMAGE_STRUCTURE"); + poJob->args.nOvrNBITS = pszNBITS ? atoi(pszNBITS) : 0; + poJob->args.dfXRatioDstToSrc = dfXRatioDstToSrc; + poJob->args.dfYRatioDstToSrc = dfYRatioDstToSrc; + poJob->args.eWrkDataType = eWrkDataType; poJob->pChunk = pChunk; - poJob->pabyChunkNodataMask = pabyChunkNodataMask; - poJob->nWidth = nWidth; - poJob->nHeight = nHeight; - poJob->nChunkYOff = nChunkYOffQueried; - poJob->nChunkYSize = nChunkYSizeQueried; + poJob->args.pabyChunkNodataMask = pabyChunkNodataMask; + poJob->nSrcWidth = nWidth; + poJob->nSrcHeight = nHeight; + poJob->args.nChunkXOff = 0; + poJob->args.nChunkXSize = nWidth; + poJob->args.nChunkYOff = nChunkYOff; + poJob->args.nChunkYSize = nChunkYSizeQueried; poJob->nDstWidth = nDstWidth; - poJob->nDstYOff = nDstYOff; - poJob->nDstYOff2 = nDstYOff2; + poJob->args.nDstXOff = 0; + poJob->args.nDstXOff2 = nDstWidth; + poJob->args.nDstYOff = nDstYOff; + poJob->args.nDstYOff2 = nDstYOff2; poJob->poDstBand = poDstBand; - poJob->pszResampling = pszResampling; - poJob->bHasNoData = bHasNoData; - poJob->dfNoDataValue = dfNoDataValue; - poJob->poColorTable = poColorTable; - poJob->eSrcDataType = eSrcDataType; - poJob->bPropagateNoData = bPropagateNoData; + poJob->args.pszResampling = pszResampling; + poJob->args.bHasNoData = bHasNoData; + poJob->args.dfNoDataValue = dfNoDataValue; + poJob->args.poColorTable = poColorTable; + poJob->args.eSrcDataType = eSrcDataType; + poJob->args.bPropagateNoData = bPropagateNoData; if (poJobQueue) { @@ -5208,27 +5222,12 @@ CPLErr GDALRegenerateOverviewsMultiBand( std::unique_ptr oSrcBufferHolder{}; std::unique_ptr oDstBufferHolder{}; + GDALRasterBand *poDstBand = nullptr; + // Input parameters of pfnResampleFn GDALResampleFunction pfnResampleFn = nullptr; - double dfXRatioDstToSrc{}; - double dfYRatioDstToSrc{}; - GDALDataType eWrkDataType = GDT_Unknown; + GDALOverviewResampleArgs args{}; const void *pChunk = nullptr; - const GByte *pabyChunkNodataMask = nullptr; - int nChunkXOff = 0; - int nChunkXSize = 0; - int nChunkYOff = 0; - int nChunkYSize = 0; - int nDstXOff = 0; - int nDstXOff2 = 0; - int nDstYOff = 0; - int nDstYOff2 = 0; - GDALRasterBand *poOverview = nullptr; - const char *pszResampling = nullptr; - bool bHasNoData = false; - double dfNoDataValue = 0.0; - GDALDataType eSrcDataType = GDT_Unknown; - bool bPropagateNoData = false; // Output values of resampling function CPLErr eErr = CE_Failure; @@ -5246,15 +5245,9 @@ CPLErr GDALRegenerateOverviewsMultiBand( { OvrJob *poJob = static_cast(pData); - poJob->eErr = poJob->pfnResampleFn( - poJob->dfXRatioDstToSrc, poJob->dfYRatioDstToSrc, 0.0, 0.0, - poJob->eWrkDataType, poJob->pChunk, poJob->pabyChunkNodataMask, - poJob->nChunkXOff, poJob->nChunkXSize, poJob->nChunkYOff, - poJob->nChunkYSize, poJob->nDstXOff, poJob->nDstXOff2, - poJob->nDstYOff, poJob->nDstYOff2, poJob->poOverview, - &(poJob->pDstBuffer), &(poJob->eDstBufferDataType), - poJob->pszResampling, poJob->bHasNoData, poJob->dfNoDataValue, - nullptr, poJob->eSrcDataType, poJob->bPropagateNoData); + poJob->eErr = poJob->pfnResampleFn(poJob->args, poJob->pChunk, + &(poJob->pDstBuffer), + &(poJob->eDstBufferDataType)); poJob->oDstBufferHolder.reset(new PointerHolder(poJob->pDstBuffer)); @@ -5268,13 +5261,13 @@ CPLErr GDALRegenerateOverviewsMultiBand( // Function to write resample data to target band const auto WriteJobData = [](const OvrJob *poJob) { - return poJob->poOverview->RasterIO( - GF_Write, poJob->nDstXOff, poJob->nDstYOff, - poJob->nDstXOff2 - poJob->nDstXOff, - poJob->nDstYOff2 - poJob->nDstYOff, poJob->pDstBuffer, - poJob->nDstXOff2 - poJob->nDstXOff, - poJob->nDstYOff2 - poJob->nDstYOff, poJob->eDstBufferDataType, - 0, 0, nullptr); + return poJob->poDstBand->RasterIO( + GF_Write, poJob->args.nDstXOff, poJob->args.nDstYOff, + poJob->args.nDstXOff2 - poJob->args.nDstXOff, + poJob->args.nDstYOff2 - poJob->args.nDstYOff, poJob->pDstBuffer, + poJob->args.nDstXOff2 - poJob->args.nDstXOff, + poJob->args.nDstYOff2 - poJob->args.nDstYOff, + poJob->eDstBufferDataType, 0, 0, nullptr); }; // Wait for completion of oldest job and serialize it @@ -5471,27 +5464,35 @@ CPLErr GDALRegenerateOverviewsMultiBand( // Compute the resulting overview block. for (int iBand = 0; iBand < nBands && eErr == CE_None; ++iBand) { - auto poJob = std::unique_ptr(new OvrJob()); + auto poJob = std::make_unique(); poJob->pfnResampleFn = pfnResampleFn; - poJob->dfXRatioDstToSrc = dfXRatioDstToSrc; - poJob->dfYRatioDstToSrc = dfYRatioDstToSrc; - poJob->eWrkDataType = eWrkDataType; + poJob->poDstBand = papapoOverviewBands[iBand][iOverview]; + poJob->args.eOvrDataType = + poJob->poDstBand->GetRasterDataType(); + poJob->args.nOvrXSize = poJob->poDstBand->GetXSize(); + poJob->args.nOvrYSize = poJob->poDstBand->GetYSize(); + const char *pszNBITS = poJob->poDstBand->GetMetadataItem( + "NBITS", "IMAGE_STRUCTURE"); + poJob->args.nOvrNBITS = pszNBITS ? atoi(pszNBITS) : 0; + poJob->args.dfXRatioDstToSrc = dfXRatioDstToSrc; + poJob->args.dfYRatioDstToSrc = dfYRatioDstToSrc; + poJob->args.eWrkDataType = eWrkDataType; poJob->pChunk = apaChunk[iBand]; - poJob->pabyChunkNodataMask = apabyChunkNoDataMask[iBand]; - poJob->nChunkXOff = nChunkXOffQueried; - poJob->nChunkXSize = nChunkXSizeQueried; - poJob->nChunkYOff = nChunkYOffQueried; - poJob->nChunkYSize = nChunkYSizeQueried; - poJob->nDstXOff = nDstXOff; - poJob->nDstXOff2 = nDstXOff + nDstXCount; - poJob->nDstYOff = nDstYOff; - poJob->nDstYOff2 = nDstYOff + nDstYCount; - poJob->poOverview = papapoOverviewBands[iBand][iOverview]; - poJob->pszResampling = pszResampling; - poJob->bHasNoData = pabHasNoData[iBand]; - poJob->dfNoDataValue = padfNoDataValue[iBand]; - poJob->eSrcDataType = eDataType; - poJob->bPropagateNoData = bPropagateNoData; + poJob->args.pabyChunkNodataMask = + apabyChunkNoDataMask[iBand]; + poJob->args.nChunkXOff = nChunkXOffQueried; + poJob->args.nChunkXSize = nChunkXSizeQueried; + poJob->args.nChunkYOff = nChunkYOffQueried; + poJob->args.nChunkYSize = nChunkYSizeQueried; + poJob->args.nDstXOff = nDstXOff; + poJob->args.nDstXOff2 = nDstXOff + nDstXCount; + poJob->args.nDstYOff = nDstYOff; + poJob->args.nDstYOff2 = nDstYOff + nDstYCount; + poJob->args.pszResampling = pszResampling; + poJob->args.bHasNoData = pabHasNoData[iBand]; + poJob->args.dfNoDataValue = padfNoDataValue[iBand]; + poJob->args.eSrcDataType = eDataType; + poJob->args.bPropagateNoData = bPropagateNoData; if (poJobQueue) { diff --git a/gcore/rasterio.cpp b/gcore/rasterio.cpp index 77f3d5ec4a5d..62e3c7d88ccc 100644 --- a/gcore/rasterio.cpp +++ b/gcore/rasterio.cpp @@ -1077,8 +1077,9 @@ CPLErr GDALRasterBand::RasterIOResampled( poMEMDS->SetBand(1, GDALRasterBand::FromHandle(hMEMBand)); const char *pszNBITS = GetMetadataItem("NBITS", "IMAGE_STRUCTURE"); + const int nNBITS = pszNBITS ? atoi(pszNBITS) : 0; if (pszNBITS) - reinterpret_cast(hMEMBand)->SetMetadataItem( + GDALRasterBand::FromHandle(hMEMBand)->SetMetadataItem( "NBITS", pszNBITS, "IMAGE_STRUCTURE"); CPLErr eErr = CE_None; @@ -1417,22 +1418,38 @@ CPLErr GDALRasterBand::RasterIOResampled( GDALDataType eDstBufferDataType = GDT_Unknown; GDALRasterBand *poMEMBand = GDALRasterBand::FromHandle(hMEMBand); - eErr = pfnResampleFunc( - dfXRatioDstToSrc, dfYRatioDstToSrc, - dfXOff - nXOff, /* == 0 if bHasXOffVirtual */ - dfYOff - nYOff, /* == 0 if bHasYOffVirtual */ - eWrkDataType, pChunk, - bNoDataMaskFullyOpaque ? nullptr : pabyChunkNoDataMask, - nChunkXOffQueried - (bHasXOffVirtual ? 0 : nXOff), - nChunkXSizeQueried, - nChunkYOffQueried - (bHasYOffVirtual ? 0 : nYOff), - nChunkYSizeQueried, nDstXOff + nDestXOffVirtual, - nDstXOff + nDestXOffVirtual + nDstXCount, - nDstYOff + nDestYOffVirtual, - nDstYOff + nDestYOffVirtual + nDstYCount, poMEMBand, - &pDstBuffer, &eDstBufferDataType, pszResampling, - bHasNoData, dfNoDataValue, GetColorTable(), eDataType, - bPropagateNoData); + GDALOverviewResampleArgs args; + args.eSrcDataType = eDataType; + args.eOvrDataType = poMEMBand->GetRasterDataType(); + args.nOvrXSize = poMEMBand->GetXSize(); + args.nOvrYSize = poMEMBand->GetYSize(); + args.nOvrNBITS = nNBITS; + args.dfXRatioDstToSrc = dfXRatioDstToSrc; + args.dfYRatioDstToSrc = dfYRatioDstToSrc; + args.dfSrcXDelta = + dfXOff - nXOff; /* == 0 if bHasXOffVirtual */ + args.dfSrcYDelta = + dfYOff - nYOff; /* == 0 if bHasYOffVirtual */ + args.eWrkDataType = eWrkDataType; + args.pabyChunkNodataMask = + bNoDataMaskFullyOpaque ? nullptr : pabyChunkNoDataMask; + args.nChunkXOff = + nChunkXOffQueried - (bHasXOffVirtual ? 0 : nXOff); + args.nChunkXSize = nChunkXSizeQueried; + args.nChunkYOff = + nChunkYOffQueried - (bHasYOffVirtual ? 0 : nYOff); + args.nChunkYSize = nChunkYSizeQueried; + args.nDstXOff = nDstXOff + nDestXOffVirtual; + args.nDstXOff2 = nDstXOff + nDestXOffVirtual + nDstXCount; + args.nDstYOff = nDstYOff + nDestYOffVirtual; + args.nDstYOff2 = nDstYOff + nDestYOffVirtual + nDstYCount; + args.pszResampling = pszResampling; + args.bHasNoData = bHasNoData; + args.dfNoDataValue = dfNoDataValue; + args.poColorTable = GetColorTable(); + args.bPropagateNoData = bPropagateNoData; + eErr = pfnResampleFunc(args, pChunk, &pDstBuffer, + &eDstBufferDataType); if (eErr == CE_None) { eErr = poMEMBand->RasterIO( @@ -1534,6 +1551,7 @@ CPLErr GDALDataset::RasterIOResampled( nDestYOffVirtual + nBufYSize, 0, eBufType, nullptr); GDALRasterBand **papoDstBands = static_cast( CPLMalloc(nBandCount * sizeof(GDALRasterBand *))); + int nNBITS = 0; for (int i = 0; i < nBandCount; i++) { char szBuffer[32] = {'\0'}; @@ -1564,8 +1582,11 @@ CPLErr GDALDataset::RasterIOResampled( const char *pszNBITS = poSrcBand->GetMetadataItem("NBITS", "IMAGE_STRUCTURE"); if (pszNBITS) + { + nNBITS = atoi(pszNBITS); poMEMDS->GetRasterBand(i + 1)->SetMetadataItem("NBITS", pszNBITS, "IMAGE_STRUCTURE"); + } } CPLErr eErr = CE_None; @@ -1903,26 +1924,45 @@ CPLErr GDALDataset::RasterIOResampled( GDALDataType eDstBufferDataType = GDT_Unknown; GDALRasterBand *poMEMBand = poMEMDS->GetRasterBand(i + 1); - eErr = pfnResampleFunc( - dfXRatioDstToSrc, dfYRatioDstToSrc, - dfXOff - nXOff, /* == 0 if bHasXOffVirtual */ - dfYOff - nYOff, /* == 0 if bHasYOffVirtual */ - eWrkDataType, - reinterpret_cast(pChunk) + - i * nChunkBandOffset, - bNoDataMaskFullyOpaque ? nullptr - : pabyChunkNoDataMask, - nChunkXOffQueried - (bHasXOffVirtual ? 0 : nXOff), - nChunkXSizeQueried, - nChunkYOffQueried - (bHasYOffVirtual ? 0 : nYOff), - nChunkYSizeQueried, nDstXOff + nDestXOffVirtual, - nDstXOff + nDestXOffVirtual + nDstXCount, - nDstYOff + nDestYOffVirtual, - nDstYOff + nDestYOffVirtual + nDstYCount, poMEMBand, - &pDstBuffer, &eDstBufferDataType, pszResampling, - false /*bHasNoData*/, 0.0 /* dfNoDataValue */, - nullptr /* color table*/, eDataType, - bPropagateNoData); + GDALOverviewResampleArgs args; + args.eSrcDataType = eDataType; + args.eOvrDataType = poMEMBand->GetRasterDataType(); + args.nOvrXSize = poMEMBand->GetXSize(); + args.nOvrYSize = poMEMBand->GetYSize(); + args.nOvrNBITS = nNBITS; + args.dfXRatioDstToSrc = dfXRatioDstToSrc; + args.dfYRatioDstToSrc = dfYRatioDstToSrc; + args.dfSrcXDelta = + dfXOff - nXOff; /* == 0 if bHasXOffVirtual */ + args.dfSrcYDelta = + dfYOff - nYOff; /* == 0 if bHasYOffVirtual */ + args.eWrkDataType = eWrkDataType; + args.pabyChunkNodataMask = bNoDataMaskFullyOpaque + ? nullptr + : pabyChunkNoDataMask; + args.nChunkXOff = + nChunkXOffQueried - (bHasXOffVirtual ? 0 : nXOff); + args.nChunkXSize = nChunkXSizeQueried; + args.nChunkYOff = + nChunkYOffQueried - (bHasYOffVirtual ? 0 : nYOff); + args.nChunkYSize = nChunkYSizeQueried; + args.nDstXOff = nDstXOff + nDestXOffVirtual; + args.nDstXOff2 = + nDstXOff + nDestXOffVirtual + nDstXCount; + args.nDstYOff = nDstYOff + nDestYOffVirtual; + args.nDstYOff2 = + nDstYOff + nDestYOffVirtual + nDstYCount; + args.pszResampling = pszResampling; + args.bHasNoData = false; + args.dfNoDataValue = 0.0; + args.poColorTable = nullptr; + args.bPropagateNoData = bPropagateNoData; + + eErr = + pfnResampleFunc(args, + reinterpret_cast(pChunk) + + i * nChunkBandOffset, + &pDstBuffer, &eDstBufferDataType); if (eErr == CE_None) { eErr = poMEMBand->RasterIO( From 06d2503b37dd1bf20bf019b3d99e1fe4041e4497 Mon Sep 17 00:00:00 2001 From: "Peter A. Jonsson" Date: Thu, 20 Jun 2024 16:01:55 +0200 Subject: [PATCH 0198/1119] ubuntu/Dockefile: fix rm cmd There seems to be a missing &&, but join the two lines into a single rm invocation instead. --- docker/ubuntu-full/Dockerfile | 3 +-- docker/ubuntu-small/Dockerfile | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docker/ubuntu-full/Dockerfile b/docker/ubuntu-full/Dockerfile index a11fdc7b1beb..1bf8ee6093f5 100644 --- a/docker/ubuntu-full/Dockerfile +++ b/docker/ubuntu-full/Dockerfile @@ -19,8 +19,7 @@ COPY ./bh-set-envvars.sh /buildscripts/bh-set-envvars.sh RUN . /buildscripts/bh-set-envvars.sh \ && if test "${TARGET_ARCH}" != ""; then \ - rm -f /etc/apt/sources.list \ - rm -f /etc/apt/sources.list.d/ubuntu.sources \ + rm -f /etc/apt/sources.list /etc/apt/sources.list.d/ubuntu.sources \ && echo "deb [arch=amd64] http://us.archive.ubuntu.com/ubuntu/ noble main restricted universe" >> /etc/apt/sources.list \ && echo "deb [arch=amd64] http://us.archive.ubuntu.com/ubuntu/ noble-updates main restricted universe" >> /etc/apt/sources.list \ && echo "deb [arch=amd64] http://us.archive.ubuntu.com/ubuntu/ noble-backports main restricted universe" >> /etc/apt/sources.list \ diff --git a/docker/ubuntu-small/Dockerfile b/docker/ubuntu-small/Dockerfile index 18cc07288604..03000c66710a 100644 --- a/docker/ubuntu-small/Dockerfile +++ b/docker/ubuntu-small/Dockerfile @@ -20,8 +20,7 @@ COPY ./bh-set-envvars.sh /buildscripts/bh-set-envvars.sh RUN . /buildscripts/bh-set-envvars.sh \ && if test "${TARGET_ARCH}" != ""; then \ - rm -f /etc/apt/sources.list \ - rm -f /etc/apt/sources.list.d/ubuntu.sources \ + rm -f /etc/apt/sources.list /etc/apt/sources.list.d/ubuntu.sources \ && echo "deb [arch=amd64] http://us.archive.ubuntu.com/ubuntu/ noble main restricted universe" >> /etc/apt/sources.list \ && echo "deb [arch=amd64] http://us.archive.ubuntu.com/ubuntu/ noble-updates main restricted universe" >> /etc/apt/sources.list \ && echo "deb [arch=amd64] http://us.archive.ubuntu.com/ubuntu/ noble-backports main restricted universe" >> /etc/apt/sources.list \ From 198791a630a41f894547c23764dd30d10e9dfd92 Mon Sep 17 00:00:00 2001 From: "Peter A. Jonsson" Date: Thu, 20 Jun 2024 16:05:14 +0200 Subject: [PATCH 0199/1119] ubuntu/Dockefile: remove apt lists The apt lists are sizable, so remove them from the image. --- docker/ubuntu-full/Dockerfile | 1 + docker/ubuntu-full/bh-proj.sh | 1 + docker/ubuntu-small/Dockerfile | 1 + 3 files changed, 3 insertions(+) diff --git a/docker/ubuntu-full/Dockerfile b/docker/ubuntu-full/Dockerfile index 1bf8ee6093f5..9beeb5f3898d 100644 --- a/docker/ubuntu-full/Dockerfile +++ b/docker/ubuntu-full/Dockerfile @@ -133,6 +133,7 @@ RUN . /buildscripts/bh-set-envvars.sh \ && apt-get update -y \ && DEBIAN_FRONTEND=noninteractive apt-get install -y \ libspdlog-dev${APT_ARCH_SUFFIX} libmagic-dev${APT_ARCH_SUFFIX} \ + && rm -rf /var/lib/apt/lists/* \ && mkdir tiledb \ && wget -q https://github.com/TileDB-Inc/TileDB/archive/${TILEDB_VERSION}.tar.gz -O - \ | tar xz -C tiledb --strip-components=1 \ diff --git a/docker/ubuntu-full/bh-proj.sh b/docker/ubuntu-full/bh-proj.sh index ea82edf6cddc..56d90a76fe35 100755 --- a/docker/ubuntu-full/bh-proj.sh +++ b/docker/ubuntu-full/bh-proj.sh @@ -99,6 +99,7 @@ fi apt-get update -y DEBIAN_FRONTEND=noninteractive apt-get install -y patchelf +rm -rf /var/lib/apt/lists/* patchelf --set-soname libinternalproj.so.${PROJ_SO_FIRST} ${DESTDIR}${PROJ_INSTALL_PREFIX}/lib/libinternalproj.so.${PROJ_SO} for i in "${DESTDIR}${PROJ_INSTALL_PREFIX}/bin"/*; do patchelf --replace-needed libproj.so.${PROJ_SO_FIRST} libinternalproj.so.${PROJ_SO_FIRST} $i; diff --git a/docker/ubuntu-small/Dockerfile b/docker/ubuntu-small/Dockerfile index 03000c66710a..58396eb3c4da 100644 --- a/docker/ubuntu-small/Dockerfile +++ b/docker/ubuntu-small/Dockerfile @@ -138,6 +138,7 @@ RUN . /buildscripts/bh-set-envvars.sh \ && for i in /build${PROJ_INSTALL_PREFIX}/bin/*; do ${GCC_ARCH}-linux-gnu-strip -s $i 2>/dev/null || /bin/true; done \ && apt-get update -y \ && DEBIAN_FRONTEND=noninteractive apt-get install -y patchelf \ + && rm -rf /var/lib/apt/lists/* \ && patchelf --set-soname libinternalproj.so.${PROJ_SO_FIRST} /build${PROJ_INSTALL_PREFIX}/lib/libinternalproj.so.${PROJ_SO} \ && for i in /build${PROJ_INSTALL_PREFIX}/bin/*; do patchelf --replace-needed libproj.so.${PROJ_SO_FIRST} libinternalproj.so.${PROJ_SO_FIRST} $i; done From adc81b5741db5259d27eb15a76e1c2f6555cacb8 Mon Sep 17 00:00:00 2001 From: "Peter A. Jonsson" Date: Thu, 20 Jun 2024 16:12:09 +0200 Subject: [PATCH 0200/1119] ubuntu/Dockerfile: only install patchelf Add --no-install-recommends to avoid extra surprise packages in the future. --- docker/ubuntu-full/bh-proj.sh | 2 +- docker/ubuntu-small/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/ubuntu-full/bh-proj.sh b/docker/ubuntu-full/bh-proj.sh index 56d90a76fe35..410958247947 100755 --- a/docker/ubuntu-full/bh-proj.sh +++ b/docker/ubuntu-full/bh-proj.sh @@ -98,7 +98,7 @@ else fi apt-get update -y -DEBIAN_FRONTEND=noninteractive apt-get install -y patchelf +DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y patchelf rm -rf /var/lib/apt/lists/* patchelf --set-soname libinternalproj.so.${PROJ_SO_FIRST} ${DESTDIR}${PROJ_INSTALL_PREFIX}/lib/libinternalproj.so.${PROJ_SO} for i in "${DESTDIR}${PROJ_INSTALL_PREFIX}/bin"/*; do diff --git a/docker/ubuntu-small/Dockerfile b/docker/ubuntu-small/Dockerfile index 58396eb3c4da..06a40416e35a 100644 --- a/docker/ubuntu-small/Dockerfile +++ b/docker/ubuntu-small/Dockerfile @@ -137,7 +137,7 @@ RUN . /buildscripts/bh-set-envvars.sh \ && ${GCC_ARCH}-linux-gnu-strip -s /build${PROJ_INSTALL_PREFIX}/lib/libinternalproj.so.${PROJ_SO} \ && for i in /build${PROJ_INSTALL_PREFIX}/bin/*; do ${GCC_ARCH}-linux-gnu-strip -s $i 2>/dev/null || /bin/true; done \ && apt-get update -y \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y patchelf \ + && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y patchelf \ && rm -rf /var/lib/apt/lists/* \ && patchelf --set-soname libinternalproj.so.${PROJ_SO_FIRST} /build${PROJ_INSTALL_PREFIX}/lib/libinternalproj.so.${PROJ_SO} \ && for i in /build${PROJ_INSTALL_PREFIX}/bin/*; do patchelf --replace-needed libproj.so.${PROJ_SO_FIRST} libinternalproj.so.${PROJ_SO_FIRST} $i; done From 5ab2d77a635ed248e727f7564e57df15837767e0 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 20 Jun 2024 17:34:49 +0200 Subject: [PATCH 0201/1119] CSV: avoid Coverity Scan warning about WRAPPER_ESCAPE (CID 1547190) --- ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp b/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp index d50068323c7f..f07fcf3f434c 100644 --- a/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp +++ b/ogr/ogrsf_frmts/csv/ogrcsvdriver.cpp @@ -166,15 +166,18 @@ static GDALDataset *OGRCSVDriverOpen(GDALOpenInfo *poOpenInfo) } } - auto poDS = std::make_unique(); + auto poDSUniquePtr = std::make_unique(); - if (!poDS->Open(poOpenInfo->pszFilename, poOpenInfo->eAccess == GA_Update, - false, poOpenInfo->papszOpenOptions, - poOpenInfo->IsSingleAllowedDriver("CSV"))) + if (!poDSUniquePtr->Open(poOpenInfo->pszFilename, + poOpenInfo->eAccess == GA_Update, false, + poOpenInfo->papszOpenOptions, + poOpenInfo->IsSingleAllowedDriver("CSV"))) { - poDS.reset(); + poDSUniquePtr.reset(); } + auto poDS = poDSUniquePtr.release(); + if (poOpenInfo->eAccess == GA_Update && poDS != nullptr) { CPLMutexHolderD(&hMutex); @@ -182,11 +185,11 @@ static GDALDataset *OGRCSVDriverOpen(GDALOpenInfo *poOpenInfo) poMap = new std::map(); if (poMap->find(poOpenInfo->pszFilename) == poMap->end()) { - (*poMap)[poOpenInfo->pszFilename] = poDS.get(); + (*poMap)[poOpenInfo->pszFilename] = poDS; } } - return poDS.release(); + return poDS; } /************************************************************************/ From 082d0e78c54dd544a14947ac216fdcca8080595c Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 20 Jun 2024 17:35:34 +0200 Subject: [PATCH 0202/1119] autotest/cpp/test_viewshed.cpp: pass argument by const reference (CID 1547189) --- autotest/cpp/test_viewshed.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autotest/cpp/test_viewshed.cpp b/autotest/cpp/test_viewshed.cpp index be733b02d65c..0bda10763dfd 100644 --- a/autotest/cpp/test_viewshed.cpp +++ b/autotest/cpp/test_viewshed.cpp @@ -62,7 +62,8 @@ Viewshed::Options stdOptions(const Coord &observer) return stdOptions(observer.first, observer.second); } -DatasetPtr runViewshed(int8_t *in, int edgeLength, Viewshed::Options opts) +DatasetPtr runViewshed(int8_t *in, int edgeLength, + const Viewshed::Options &opts) { Viewshed v(opts); From c83797a30f103f9d1fe798fa2257489607137050 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 20 Jun 2024 18:08:28 +0200 Subject: [PATCH 0203/1119] VRT: fix serialization of separatable kernel in VRTKernelFilteredSource Fixes #10253 --- autotest/gdrivers/vrtfilt.py | 24 ++++++++++++++++++++++++ frmts/vrt/vrtfilters.cpp | 20 +++++++++----------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/autotest/gdrivers/vrtfilt.py b/autotest/gdrivers/vrtfilt.py index 706e69085efe..579a4003509f 100755 --- a/autotest/gdrivers/vrtfilt.py +++ b/autotest/gdrivers/vrtfilt.py @@ -238,3 +238,27 @@ def test_vrtfilt_invalid_kernel_size(): with pytest.raises(Exception): vrt_ds.GetRasterBand(1).SetMetadata(md, "vrt_sources") + + +############################################################################### + + +def test_vrtfilt_serialize_separatable_kernel(): + + vrt_ds = gdal.GetDriverByName("VRT").Create("", 1, 1, 1) + + filterSourceXML = """ + data/rgbsmall.tif + 1 + + 3 + 1 1 1 + + """ + + md = {} + md["source_0"] = filterSourceXML + + vrt_ds.GetRasterBand(1).SetMetadata(md, "vrt_sources") + + assert filterSourceXML in vrt_ds.GetMetadata("xml:VRT")[0] diff --git a/frmts/vrt/vrtfilters.cpp b/frmts/vrt/vrtfilters.cpp index 3350afeff354..6d8ba867df17 100644 --- a/frmts/vrt/vrtfilters.cpp +++ b/frmts/vrt/vrtfilters.cpp @@ -682,20 +682,18 @@ CPLXMLNode *VRTKernelFilteredSource::SerializeToXML(const char *pszVRTPath) CPLCreateXMLNode(psKernel, CXT_Attribute, "normalized"), CXT_Text, "0"); - const int nCoefCount = m_nKernelSize * m_nKernelSize; - const size_t nBufLen = nCoefCount * 32; - char *pszKernelCoefs = static_cast(CPLMalloc(nBufLen)); - - strcpy(pszKernelCoefs, ""); + const int nCoefCount = + m_bSeparable ? m_nKernelSize : m_nKernelSize * m_nKernelSize; + std::string osCoefs; for (int iCoef = 0; iCoef < nCoefCount; iCoef++) - CPLsnprintf(pszKernelCoefs + strlen(pszKernelCoefs), - nBufLen - strlen(pszKernelCoefs), "%.8g ", - m_padfKernelCoefs[iCoef]); + { + if (!osCoefs.empty()) + osCoefs += ' '; + osCoefs += CPLSPrintf("%.8g", m_padfKernelCoefs[iCoef]); + } CPLSetXMLValue(psKernel, "Size", CPLSPrintf("%d", m_nKernelSize)); - CPLSetXMLValue(psKernel, "Coefs", pszKernelCoefs); - - CPLFree(pszKernelCoefs); + CPLSetXMLValue(psKernel, "Coefs", osCoefs.c_str()); return psSrc; } From 1bfce62c3c284166a59ce0e4041968ed1974eca7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 20 Jun 2024 18:21:52 +0200 Subject: [PATCH 0204/1119] Code cleanup in VRTKernelFilteredSource --- frmts/vrt/vrtdataset.h | 18 ++++----- frmts/vrt/vrtfilters.cpp | 83 +++++++++++++++------------------------- 2 files changed, 39 insertions(+), 62 deletions(-) diff --git a/frmts/vrt/vrtdataset.h b/frmts/vrt/vrtdataset.h index bbc0d60bbc46..7809c8b2211d 100644 --- a/frmts/vrt/vrtdataset.h +++ b/frmts/vrt/vrtdataset.h @@ -1601,17 +1601,14 @@ class VRTKernelFilteredSource CPL_NON_FINAL : public VRTFilteredSource CPL_DISALLOW_COPY_ASSIGN(VRTKernelFilteredSource) protected: - int m_nKernelSize; - - bool m_bSeparable; - - double *m_padfKernelCoefs; - - int m_bNormalized; + int m_nKernelSize = 0; + bool m_bSeparable = false; + // m_nKernelSize elements if m_bSeparable, m_nKernelSize * m_nKernelSize otherwise + std::vector m_adfKernelCoefs{}; + bool m_bNormalized = false; public: VRTKernelFilteredSource(); - virtual ~VRTKernelFilteredSource(); virtual CPLErr XMLInit(const CPLXMLNode *psTree, const char *, std::map &) override; @@ -1620,8 +1617,9 @@ class VRTKernelFilteredSource CPL_NON_FINAL : public VRTFilteredSource virtual CPLErr FilterData(int nXSize, int nYSize, GDALDataType eType, GByte *pabySrcData, GByte *pabyDstData) override; - CPLErr SetKernel(int nKernelSize, bool bSeparable, double *padfCoefs); - void SetNormalized(int); + CPLErr SetKernel(int nKernelSize, bool bSeparable, + const std::vector &adfNewCoefs); + void SetNormalized(bool); }; /************************************************************************/ diff --git a/frmts/vrt/vrtfilters.cpp b/frmts/vrt/vrtfilters.cpp index 6d8ba867df17..e3378cdf303e 100644 --- a/frmts/vrt/vrtfilters.cpp +++ b/frmts/vrt/vrtfilters.cpp @@ -420,28 +420,16 @@ CPLErr VRTFilteredSource::RasterIO(GDALDataType eVRTBandDataType, int nXOff, /************************************************************************/ VRTKernelFilteredSource::VRTKernelFilteredSource() - : m_nKernelSize(0), m_bSeparable(FALSE), m_padfKernelCoefs(nullptr), - m_bNormalized(FALSE) { GDALDataType aeSupTypes[] = {GDT_Float32}; SetFilteringDataTypesSupported(1, aeSupTypes); } -/************************************************************************/ -/* ~VRTKernelFilteredSource() */ -/************************************************************************/ - -VRTKernelFilteredSource::~VRTKernelFilteredSource() - -{ - CPLFree(m_padfKernelCoefs); -} - /************************************************************************/ /* SetNormalized() */ /************************************************************************/ -void VRTKernelFilteredSource::SetNormalized(int bNormalizedIn) +void VRTKernelFilteredSource::SetNormalized(bool bNormalizedIn) { m_bNormalized = bNormalizedIn; @@ -451,8 +439,9 @@ void VRTKernelFilteredSource::SetNormalized(int bNormalizedIn) /* SetKernel() */ /************************************************************************/ -CPLErr VRTKernelFilteredSource::SetKernel(int nNewKernelSize, bool bSeparable, - double *padfNewCoefs) +CPLErr +VRTKernelFilteredSource::SetKernel(int nNewKernelSize, bool bSeparable, + const std::vector &adfNewCoefs) { if (nNewKernelSize < 1 || (nNewKernelSize % 2) != 1) @@ -463,16 +452,17 @@ CPLErr VRTKernelFilteredSource::SetKernel(int nNewKernelSize, bool bSeparable, nNewKernelSize); return CE_Failure; } + if (adfNewCoefs.size() != + static_cast(nNewKernelSize) * (bSeparable ? 1 : nNewKernelSize)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "adfNewCoefs[] is not of expected size"); + return CE_Failure; + } - CPLFree(m_padfKernelCoefs); m_nKernelSize = nNewKernelSize; m_bSeparable = bSeparable; - - int nKernelBufferSize = m_nKernelSize * (m_bSeparable ? 1 : m_nKernelSize); - - m_padfKernelCoefs = - static_cast(CPLMalloc(sizeof(double) * nKernelBufferSize)); - memcpy(m_padfKernelCoefs, padfNewCoefs, sizeof(double) * nKernelBufferSize); + m_adfKernelCoefs = adfNewCoefs; SetExtraEdgePixels((nNewKernelSize - 1) / 2); @@ -565,8 +555,8 @@ CPLErr VRTKernelFilteredSource::FilterData(int nXSize, int nYSize, iJJ * nJStride; if (bHasNoData && *pfData == fNoData) continue; - dfSum += *pfData * m_padfKernelCoefs[iK]; - dfKernSum += m_padfKernelCoefs[iK]; + dfSum += *pfData * m_adfKernelCoefs[iK]; + dfKernSum += m_adfKernelCoefs[iK]; } } @@ -618,17 +608,16 @@ CPLErr VRTKernelFilteredSource::XMLInit( return CE_Failure; } - char **papszCoefItems = - CSLTokenizeString(CPLGetXMLValue(psTree, "Kernel.Coefs", "")); + const CPLStringList aosCoefItems( + CSLTokenizeString(CPLGetXMLValue(psTree, "Kernel.Coefs", ""))); - const int nCoefs = CSLCount(papszCoefItems); + const int nCoefs = aosCoefItems.size(); const bool bSquare = nCoefs == nNewKernelSize * nNewKernelSize; const bool bSeparable = nCoefs == nNewKernelSize && nCoefs != 1; if (!bSquare && !bSeparable) { - CSLDestroy(papszCoefItems); CPLError(CE_Failure, CPLE_AppDefined, "Got wrong number of filter kernel coefficients (%s). " "Expected %d or %d, got %d.", @@ -637,19 +626,17 @@ CPLErr VRTKernelFilteredSource::XMLInit( return CE_Failure; } - double *padfNewCoefs = - static_cast(CPLMalloc(sizeof(double) * nCoefs)); - + std::vector adfNewCoefs; + adfNewCoefs.reserve(nCoefs); for (int i = 0; i < nCoefs; i++) - padfNewCoefs[i] = CPLAtof(papszCoefItems[i]); - - const CPLErr eErr = SetKernel(nNewKernelSize, bSeparable, padfNewCoefs); - - CPLFree(padfNewCoefs); - CSLDestroy(papszCoefItems); - - SetNormalized(atoi(CPLGetXMLValue(psTree, "Kernel.normalized", "0"))); + adfNewCoefs.push_back(CPLAtof(aosCoefItems[i])); + const CPLErr eErr = SetKernel(nNewKernelSize, bSeparable, adfNewCoefs); + if (eErr == CE_None) + { + SetNormalized(atoi(CPLGetXMLValue(psTree, "Kernel.normalized", "0")) != + 0); + } return eErr; } @@ -673,23 +660,15 @@ CPLXMLNode *VRTKernelFilteredSource::SerializeToXML(const char *pszVRTPath) CPLXMLNode *psKernel = CPLCreateXMLNode(psSrc, CXT_Element, "Kernel"); - if (m_bNormalized) - CPLCreateXMLNode( - CPLCreateXMLNode(psKernel, CXT_Attribute, "normalized"), CXT_Text, - "1"); - else - CPLCreateXMLNode( - CPLCreateXMLNode(psKernel, CXT_Attribute, "normalized"), CXT_Text, - "0"); - - const int nCoefCount = - m_bSeparable ? m_nKernelSize : m_nKernelSize * m_nKernelSize; + CPLCreateXMLNode(CPLCreateXMLNode(psKernel, CXT_Attribute, "normalized"), + CXT_Text, m_bNormalized ? "1" : "0"); + std::string osCoefs; - for (int iCoef = 0; iCoef < nCoefCount; iCoef++) + for (auto dfVal : m_adfKernelCoefs) { if (!osCoefs.empty()) osCoefs += ' '; - osCoefs += CPLSPrintf("%.8g", m_padfKernelCoefs[iCoef]); + osCoefs += CPLSPrintf("%.8g", dfVal); } CPLSetXMLValue(psKernel, "Size", CPLSPrintf("%d", m_nKernelSize)); From 800f922d24399f90108c7a87bbaa134f38a88ee3 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 20 Jun 2024 19:39:22 +0200 Subject: [PATCH 0205/1119] OGR layer algebra: honour PROMOTE_TO_MULTI=YES for Points Fixes https://lists.osgeo.org/pipermail/gdal-dev/2024-June/059103.html --- autotest/ogr/ogr_layer_algebra.py | 25 +++ ogr/ogrsf_frmts/generic/ogrlayer.cpp | 245 +++++++++++++++++++-------- 2 files changed, 198 insertions(+), 72 deletions(-) diff --git a/autotest/ogr/ogr_layer_algebra.py b/autotest/ogr/ogr_layer_algebra.py index f1d0cb14ca65..cb43912ef18e 100755 --- a/autotest/ogr/ogr_layer_algebra.py +++ b/autotest/ogr/ogr_layer_algebra.py @@ -261,6 +261,31 @@ def test_algebra_intersection_3(D1, D2, C): assert is_same(D1, C), "D1 != C" +def test_algebra_intersection_multipoint(): + + driver = ogr.GetDriverByName("MEMORY") + ds = driver.CreateDataSource("ds") + layer1 = ds.CreateLayer("layer1") + layer2 = ds.CreateLayer("layer2") + + g1 = "LINESTRING (0 0, 1 1)" + geom1 = ogr.CreateGeometryFromWkt(g1) + feat1 = ogr.Feature(layer1.GetLayerDefn()) + feat1.SetGeometry(geom1) + layer1.CreateFeature(feat1) + + g2 = "LINESTRING (0 1, 1 0)" + geom2 = ogr.CreateGeometryFromWkt(g2) + feat2 = ogr.Feature(layer2.GetLayerDefn()) + feat2.SetGeometry(geom2) + layer2.CreateFeature(feat2) + + layer3 = ds.CreateLayer("layer3") + layer1.Intersection(layer2, layer3, ["PROMOTE_TO_MULTI=YES"]) + f = layer3.GetNextFeature() + assert f.GetGeometryRef().ExportToIsoWkt() == "MULTIPOINT ((0.5 0.5))" + + def test_algebra_KEEP_LOWER_DIMENSION_GEOMETRIES(): driver = ogr.GetDriverByName("MEMORY") diff --git a/ogr/ogrsf_frmts/generic/ogrlayer.cpp b/ogr/ogrsf_frmts/generic/ogrlayer.cpp index fdf592d0a931..91d1102580ef 100644 --- a/ogr/ogrsf_frmts/generic/ogrlayer.cpp +++ b/ogr/ogrsf_frmts/generic/ogrlayer.cpp @@ -2551,7 +2551,9 @@ static OGRGeometry *set_filter_from(OGRLayer *pLayer, static OGRGeometry *promote_to_multi(OGRGeometry *poGeom) { OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType()); - if (eType == wkbPolygon) + if (eType == wkbPoint) + return OGRGeometryFactory::forceToMultiPoint(poGeom); + else if (eType == wkbPolygon) return OGRGeometryFactory::forceToMultiPolygon(poGeom); else if (eType == wkbLineString) return OGRGeometryFactory::forceToMultiLineString(poGeom); @@ -2587,23 +2589,32 @@ static OGRGeometry *promote_to_multi(OGRGeometry *poGeom) *
      *
    • SKIP_FAILURES=YES/NO. Set to YES to go on, even when a * feature could not be inserted or a GEOS call failed. + *
    • *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    • USE_PREPARED_GEOMETRIES=YES/NO. Set to NO to not use prepared * geometries to pretest intersection of features of method layer * with features of this layer. + *
    • *
    • PRETEST_CONTAINMENT=YES/NO. Set to YES to pretest the * containment of features of method layer within the features of * this layer. This will speed up the method significantly in some * cases. Requires that the prepared geometries are in effect. + *
    • *
    • KEEP_LOWER_DIMENSION_GEOMETRIES=YES/NO. Set to NO to skip * result features with lower dimension geometry that would - * otherwise be added to the result layer. The default is to add - * but only if the result layer has an unknown geometry type. + * otherwise be added to the result layer. The default is YES, to add + * features with lower dimension geometry, but only if the result layer + * has an unknown geometry type. + *
    • *
    * * This method is the same as the C function OGR_L_Intersection(). @@ -2645,22 +2656,22 @@ OGRErr OGRLayer::Intersection(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, double progress_max = static_cast(GetFeatureCount(FALSE)); double progress_counter = 0; double progress_ticker = 0; - int bSkipFailures = + const bool bSkipFailures = CPLTestBool(CSLFetchNameValueDef(papszOptions, "SKIP_FAILURES", "NO")); - int bPromoteToMulti = CPLTestBool( + const bool bPromoteToMulti = CPLTestBool( CSLFetchNameValueDef(papszOptions, "PROMOTE_TO_MULTI", "NO")); - int bUsePreparedGeometries = CPLTestBool( + const bool bUsePreparedGeometries = CPLTestBool( CSLFetchNameValueDef(papszOptions, "USE_PREPARED_GEOMETRIES", "YES")); - if (bUsePreparedGeometries) - bUsePreparedGeometries = OGRHasPreparedGeometrySupport(); - int bPretestContainment = CPLTestBool( + const bool bPretestContainment = CPLTestBool( CSLFetchNameValueDef(papszOptions, "PRETEST_CONTAINMENT", "NO")); - int bKeepLowerDimGeom = CPLTestBool(CSLFetchNameValueDef( + bool bKeepLowerDimGeom = CPLTestBool(CSLFetchNameValueDef( papszOptions, "KEEP_LOWER_DIMENSION_GEOMETRIES", "YES")); // check for GEOS if (!OGRGeometryFactory::haveGEOS()) { + CPLError(CE_Failure, CPLE_AppDefined, + "OGRLayer::Intersection() requires GEOS support"); return OGRERR_UNSUPPORTED_OPERATION; } @@ -2687,7 +2698,7 @@ OGRErr OGRLayer::Intersection(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, { CPLDebug("OGR", "Resetting KEEP_LOWER_DIMENSION_GEOMETRIES to NO " "since the result layer does not allow it."); - bKeepLowerDimGeom = FALSE; + bKeepLowerDimGeom = false; } } @@ -2900,23 +2911,32 @@ OGRErr OGRLayer::Intersection(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    • USE_PREPARED_GEOMETRIES=YES/NO. Set to NO to not use prepared * geometries to pretest intersection of features of method layer * with features of this layer. + *
    • *
    • PRETEST_CONTAINMENT=YES/NO. Set to YES to pretest the * containment of features of method layer within the features of * this layer. This will speed up the method significantly in some * cases. Requires that the prepared geometries are in effect. + *
    • *
    • KEEP_LOWER_DIMENSION_GEOMETRIES=YES/NO. Set to NO to skip * result features with lower dimension geometry that would - * otherwise be added to the result layer. The default is to add - * but only if the result layer has an unknown geometry type. + * otherwise be added to the result layer. The default is YES, to add + * features with lower dimension geometry, but only if the result layer + * has an unknown geometry type. + *
    • *
    * * This function is the same as the C++ method OGRLayer::Intersection(). @@ -2992,19 +3012,27 @@ OGRErr OGR_L_Intersection(OGRLayerH pLayerInput, OGRLayerH pLayerMethod, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    • USE_PREPARED_GEOMETRIES=YES/NO. Set to NO to not use prepared * geometries to pretest intersection of features of method layer * with features of this layer. + *
    • *
    • KEEP_LOWER_DIMENSION_GEOMETRIES=YES/NO. Set to NO to skip * result features with lower dimension geometry that would - * otherwise be added to the result layer. The default is to add - * but only if the result layer has an unknown geometry type. + * otherwise be added to the result layer. The default is YES, to add + * features with lower dimension geometry, but only if the result layer + * has an unknown geometry type. + *
    • *
    * * This method is the same as the C function OGR_L_Union(). @@ -3047,20 +3075,20 @@ OGRErr OGRLayer::Union(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, static_cast(pLayerMethod->GetFeatureCount(FALSE)); double progress_counter = 0; double progress_ticker = 0; - int bSkipFailures = + const bool bSkipFailures = CPLTestBool(CSLFetchNameValueDef(papszOptions, "SKIP_FAILURES", "NO")); - int bPromoteToMulti = CPLTestBool( + const bool bPromoteToMulti = CPLTestBool( CSLFetchNameValueDef(papszOptions, "PROMOTE_TO_MULTI", "NO")); - int bUsePreparedGeometries = CPLTestBool( + const bool bUsePreparedGeometries = CPLTestBool( CSLFetchNameValueDef(papszOptions, "USE_PREPARED_GEOMETRIES", "YES")); - if (bUsePreparedGeometries) - bUsePreparedGeometries = OGRHasPreparedGeometrySupport(); - int bKeepLowerDimGeom = CPLTestBool(CSLFetchNameValueDef( + bool bKeepLowerDimGeom = CPLTestBool(CSLFetchNameValueDef( papszOptions, "KEEP_LOWER_DIMENSION_GEOMETRIES", "YES")); // check for GEOS if (!OGRGeometryFactory::haveGEOS()) { + CPLError(CE_Failure, CPLE_AppDefined, + "OGRLayer::Union() requires GEOS support"); return OGRERR_UNSUPPORTED_OPERATION; } @@ -3438,19 +3466,27 @@ OGRErr OGRLayer::Union(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    • USE_PREPARED_GEOMETRIES=YES/NO. Set to NO to not use prepared * geometries to pretest intersection of features of method layer * with features of this layer. + *
    • *
    • KEEP_LOWER_DIMENSION_GEOMETRIES=YES/NO. Set to NO to skip * result features with lower dimension geometry that would - * otherwise be added to the result layer. The default is to add - * but only if the result layer has an unknown geometry type. + * otherwise be added to the result layer. The default is YES, to add + * features with lower dimension geometry, but only if the result layer + * has an unknown geometry type. + *
    • *
    * * This function is the same as the C++ method OGRLayer::Union(). @@ -3524,12 +3560,16 @@ OGRErr OGR_L_Union(OGRLayerH pLayerInput, OGRLayerH pLayerMethod, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. + *
    • *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    * * This method is the same as the C function OGR_L_SymDifference(). @@ -3572,14 +3612,16 @@ OGRErr OGRLayer::SymDifference(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, static_cast(pLayerMethod->GetFeatureCount(FALSE)); double progress_counter = 0; double progress_ticker = 0; - int bSkipFailures = + const bool bSkipFailures = CPLTestBool(CSLFetchNameValueDef(papszOptions, "SKIP_FAILURES", "NO")); - int bPromoteToMulti = CPLTestBool( + const bool bPromoteToMulti = CPLTestBool( CSLFetchNameValueDef(papszOptions, "PROMOTE_TO_MULTI", "NO")); // check for GEOS if (!OGRGeometryFactory::haveGEOS()) { + CPLError(CE_Failure, CPLE_AppDefined, + "OGRLayer::SymDifference() requires GEOS support"); return OGRERR_UNSUPPORTED_OPERATION; } @@ -3852,12 +3894,17 @@ OGRErr OGRLayer::SymDifference(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    * * This function is the same as the C++ method OGRLayer::SymDifference(). @@ -3932,19 +3979,27 @@ OGRErr OGR_L_SymDifference(OGRLayerH pLayerInput, OGRLayerH pLayerMethod, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    • USE_PREPARED_GEOMETRIES=YES/NO. Set to NO to not use prepared * geometries to pretest intersection of features of method layer * with features of this layer. + *
    • *
    • KEEP_LOWER_DIMENSION_GEOMETRIES=YES/NO. Set to NO to skip * result features with lower dimension geometry that would - * otherwise be added to the result layer. The default is to add - * but only if the result layer has an unknown geometry type. + * otherwise be added to the result layer. The default is YES, to add + * features with lower dimension geometry, but only if the result layer + * has an unknown geometry type. + *
    • *
    * * This method is the same as the C function OGR_L_Identity(). @@ -3984,20 +4039,20 @@ OGRErr OGRLayer::Identity(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, double progress_max = static_cast(GetFeatureCount(FALSE)); double progress_counter = 0; double progress_ticker = 0; - int bSkipFailures = + const bool bSkipFailures = CPLTestBool(CSLFetchNameValueDef(papszOptions, "SKIP_FAILURES", "NO")); - int bPromoteToMulti = CPLTestBool( + const bool bPromoteToMulti = CPLTestBool( CSLFetchNameValueDef(papszOptions, "PROMOTE_TO_MULTI", "NO")); - int bUsePreparedGeometries = CPLTestBool( + const bool bUsePreparedGeometries = CPLTestBool( CSLFetchNameValueDef(papszOptions, "USE_PREPARED_GEOMETRIES", "YES")); - if (bUsePreparedGeometries) - bUsePreparedGeometries = OGRHasPreparedGeometrySupport(); - int bKeepLowerDimGeom = CPLTestBool(CSLFetchNameValueDef( + bool bKeepLowerDimGeom = CPLTestBool(CSLFetchNameValueDef( papszOptions, "KEEP_LOWER_DIMENSION_GEOMETRIES", "YES")); // check for GEOS if (!OGRGeometryFactory::haveGEOS()) { + CPLError(CE_Failure, CPLE_AppDefined, + "OGRLayer::Identity() requires GEOS support"); return OGRERR_UNSUPPORTED_OPERATION; } if (bKeepLowerDimGeom) @@ -4258,19 +4313,27 @@ OGRErr OGRLayer::Identity(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    • USE_PREPARED_GEOMETRIES=YES/NO. Set to NO to not use prepared * geometries to pretest intersection of features of method layer * with features of this layer. + *
    • *
    • KEEP_LOWER_DIMENSION_GEOMETRIES=YES/NO. Set to NO to skip * result features with lower dimension geometry that would - * otherwise be added to the result layer. The default is to add - * but only if the result layer has an unknown geometry type. + * otherwise be added to the result layer. The default is YES, to add + * features with lower dimension geometry, but only if the result layer + * has an unknown geometry type. + *
    • *
    * * This function is the same as the C++ method OGRLayer::Identity(). @@ -4344,12 +4407,17 @@ OGRErr OGR_L_Identity(OGRLayerH pLayerInput, OGRLayerH pLayerMethod, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    * * This method is the same as the C function OGR_L_Update(). @@ -4391,14 +4459,16 @@ OGRErr OGRLayer::Update(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, static_cast(pLayerMethod->GetFeatureCount(FALSE)); double progress_counter = 0; double progress_ticker = 0; - int bSkipFailures = + const bool bSkipFailures = CPLTestBool(CSLFetchNameValueDef(papszOptions, "SKIP_FAILURES", "NO")); - int bPromoteToMulti = CPLTestBool( + const bool bPromoteToMulti = CPLTestBool( CSLFetchNameValueDef(papszOptions, "PROMOTE_TO_MULTI", "NO")); // check for GEOS if (!OGRGeometryFactory::haveGEOS()) { + CPLError(CE_Failure, CPLE_AppDefined, + "OGRLayer::Update() requires GEOS support"); return OGRERR_UNSUPPORTED_OPERATION; } @@ -4609,12 +4679,17 @@ OGRErr OGRLayer::Update(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    * * This function is the same as the C++ method OGRLayer::Update(). @@ -4681,12 +4756,17 @@ OGRErr OGR_L_Update(OGRLayerH pLayerInput, OGRLayerH pLayerMethod, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    * * This method is the same as the C function OGR_L_Clip(). @@ -4724,14 +4804,16 @@ OGRErr OGRLayer::Clip(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, double progress_max = static_cast(GetFeatureCount(FALSE)); double progress_counter = 0; double progress_ticker = 0; - int bSkipFailures = + const bool bSkipFailures = CPLTestBool(CSLFetchNameValueDef(papszOptions, "SKIP_FAILURES", "NO")); - int bPromoteToMulti = CPLTestBool( + const bool bPromoteToMulti = CPLTestBool( CSLFetchNameValueDef(papszOptions, "PROMOTE_TO_MULTI", "NO")); // check for GEOS if (!OGRGeometryFactory::haveGEOS()) { + CPLError(CE_Failure, CPLE_AppDefined, + "OGRLayer::Clip() requires GEOS support"); return OGRERR_UNSUPPORTED_OPERATION; } @@ -4906,12 +4988,17 @@ OGRErr OGRLayer::Clip(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    * * This function is the same as the C++ method OGRLayer::Clip(). @@ -4978,12 +5065,17 @@ OGRErr OGR_L_Clip(OGRLayerH pLayerInput, OGRLayerH pLayerMethod, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    * * This method is the same as the C function OGR_L_Erase(). @@ -5021,14 +5113,16 @@ OGRErr OGRLayer::Erase(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, double progress_max = static_cast(GetFeatureCount(FALSE)); double progress_counter = 0; double progress_ticker = 0; - int bSkipFailures = + const bool bSkipFailures = CPLTestBool(CSLFetchNameValueDef(papszOptions, "SKIP_FAILURES", "NO")); - int bPromoteToMulti = CPLTestBool( + const bool bPromoteToMulti = CPLTestBool( CSLFetchNameValueDef(papszOptions, "PROMOTE_TO_MULTI", "NO")); // check for GEOS if (!OGRGeometryFactory::haveGEOS()) { + CPLError(CE_Failure, CPLE_AppDefined, + "OGRLayer::Erase() requires GEOS support"); return OGRERR_UNSUPPORTED_OPERATION; } @@ -5182,12 +5276,17 @@ OGRErr OGRLayer::Erase(OGRLayer *pLayerMethod, OGRLayer *pLayerResult, *
      *
    • SKIP_FAILURES=YES/NO. Set it to YES to go on, even when a * feature could not be inserted or a GEOS call failed. - *
    • PROMOTE_TO_MULTI=YES/NO. Set it to YES to convert Polygons - * into MultiPolygons, or LineStrings to MultiLineStrings. + *
    • + *
    • PROMOTE_TO_MULTI=YES/NO. Set to YES to convert Polygons + * into MultiPolygons, LineStrings to MultiLineStrings or + * Points to MultiPoints (only since GDAL 3.9.2 for the later) + *
    • *
    • INPUT_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the input layer. + *
    • *
    • METHOD_PREFIX=string. Set a prefix for the field names that * will be created from the fields of the method layer. + *
    • *
    * * This function is the same as the C++ method OGRLayer::Erase(). @@ -5607,9 +5706,10 @@ OGRSpatialReferenceH *OGR_L_GetSupportedSRSList(OGRLayerH hLayer, *
      *
    • the SRS in which geometries of returned features are expressed,
    • *
    • the SRS in which geometries of passed features (CreateFeature(), - * SetFeature()) are expressed,
    • the SRS returned by GetSpatialRef() and - * GetGeomFieldDefn()->GetSpatialRef(),
    • the SRS used to interpret - * SetSpatialFilter() values.
    • + * SetFeature()) are expressed, + *
    • the SRS returned by GetSpatialRef() and + * GetGeomFieldDefn()->GetSpatialRef(),
    • + *
    • the SRS used to interpret SetSpatialFilter() values.
    • *
    * This also resets feature reading and the spatial filter. * Note however that this does not modify the storage SRS of the features of @@ -5642,9 +5742,10 @@ OGRErr OGRLayer::SetActiveSRS(CPL_UNUSED int iGeomField, *
      *
    • the SRS in which geometries of returned features are expressed,
    • *
    • the SRS in which geometries of passed features (CreateFeature(), - * SetFeature()) are expressed,
    • the SRS returned by GetSpatialRef() and - * GetGeomFieldDefn()->GetSpatialRef(),
    • the SRS used to interpret - * SetSpatialFilter() values.
    • + * SetFeature()) are expressed, + *
    • the SRS returned by GetSpatialRef() and + * GetGeomFieldDefn()->GetSpatialRef(),
    • + *
    • the SRS used to interpret SetSpatialFilter() values.
    • *
    * This also resets feature reading and the spatial filter. * Note however that this does not modify the storage SRS of the features of From 852fc9a4ee4b1e79aaa5627713309d861b03e8c5 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 21 Jun 2024 14:07:33 +0200 Subject: [PATCH 0206/1119] DXF: add a DXF_CLOSED_LINE_AS_POLYGON=YES/NO configuration option to control whether closed POLYLINE/LWPOLYLINE should be exposed as a polygon. Defaults to NO to match current behavior. Fixes #10153 Also available in DWF driver as DWG_CLOSED_LINE_AS_POLYGON=YES/NO Also, for DXF, expose existing reader configuration options as open options: INLINE_BLOCKS, MERGE_BLOCK_GEOMETRIES, TRANSLATE_ESCAPE_SEQUENCES, INCLUDE_RAW_CODE_VALUES, 3D_EXTENSIBLE_MODE, CLOSED_LINE_AS_POLYGON, HATCH_TOLERANCE ENCODING --- autotest/ogr/ogr_dxf.py | 26 ++- doc/source/drivers/vector/dwg.rst | 8 + doc/source/drivers/vector/dxf.rst | 149 ++++++++++++------ ogr/ogrsf_frmts/dwg/ogr_dwg.h | 7 + ogr/ogrsf_frmts/dwg/ogrdwg_hatch.cpp | 2 +- ogr/ogrsf_frmts/dwg/ogrdwgdatasource.cpp | 2 + ogr/ogrsf_frmts/dwg/ogrdwglayer.cpp | 4 +- ogr/ogrsf_frmts/dxf/ogr_dxf.h | 15 +- ogr/ogrsf_frmts/dxf/ogrdxf_hatch.cpp | 4 +- .../dxf/ogrdxf_polyline_smooth.cpp | 21 +-- ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.h | 2 +- ogr/ogrsf_frmts/dxf/ogrdxfdatasource.cpp | 46 ++++-- ogr/ogrsf_frmts/dxf/ogrdxfdriver.cpp | 32 +++- ogr/ogrsf_frmts/dxf/ogrdxflayer.cpp | 15 +- ogr/ogrsf_frmts/dxf/ogrdxfwriterds.cpp | 2 +- 15 files changed, 244 insertions(+), 91 deletions(-) diff --git a/autotest/ogr/ogr_dxf.py b/autotest/ogr/ogr_dxf.py index 21b283e779e9..d2ecbd72d34d 100644 --- a/autotest/ogr/ogr_dxf.py +++ b/autotest/ogr/ogr_dxf.py @@ -3890,12 +3890,21 @@ def test_ogr_dxf_54(): # Test hidden objects in blocks -def test_ogr_dxf_55(): - - with gdaltest.config_option("DXF_MERGE_BLOCK_GEOMETRIES", "FALSE"): - ds = ogr.Open("data/dxf/block-hidden-entities.dxf") +@pytest.mark.parametrize("use_config_option", [True, False]) +def test_ogr_dxf_55(use_config_option): + + if use_config_option: + with gdaltest.config_option("DXF_MERGE_BLOCK_GEOMETRIES", "FALSE"): + ds = ogr.Open("data/dxf/block-hidden-entities.dxf") + else: + ds = gdal.OpenEx( + "data/dxf/block-hidden-entities.dxf", + open_options={"MERGE_BLOCK_GEOMETRIES": False}, + ) lyr = ds.GetLayer(0) + assert lyr.GetFeatureCount() == 6 + # Red features should be hidden, black features should be visible for number, f in enumerate(lyr): assert "#ff000000)" in f.GetStyleString() or "#000000)" in f.GetStyleString(), ( @@ -4011,3 +4020,12 @@ def test_ogr_dxf_read_closed_polyline_with_bulge(): g.ExportToWkt() == "LINESTRING (40585366.7065058 3433935.53809098,40585329.9256486 3433998.44081707,40585329.9256486 3433998.44081707,40585328.5387678 3434000.63680805,40585327.0051198 3434002.73293274,40585325.3318693 3434004.71939884,40585323.526833 3434006.58692634,40585321.5984435 3434008.32679087,40585319.5557093 3434009.93086443,40585317.4081735 3434011.39165342,40585315.1658683 3434012.70233358,40585312.8392691 3434013.85678191,40585310.4392448 3434014.84960528,40585307.9770074 3434015.67616559,40585305.4640596 3434016.33260146,40585302.9121409 3434016.81584629,40585300.3331728 3434017.12364253,40585297.7392033 3434017.25455227,40585271.1313178 3434017.68678191,40585252.1698149 3433885.99037548,40585256.74147 3433885.9161116,40585256.74147 3433885.9161116,40585266.2920614 3433886.0916242,40585275.8076317 3433886.92740148,40585285.2425893 3433888.41943902,40585294.551729 3433890.56058809,40585303.6904483 3433893.34058991,40585312.6149614 3433896.74612477,40585321.2825086 3433900.76087591,40585329.6515615 3433905.36560764,40585364.2483736 3433925.99220872,40585364.2483736 3433925.99220872,40585364.6481964 3433926.24937651,40585365.0296424 3433926.53308859,40585365.3909523 3433926.84203644,40585365.7304596 3433927.17479516,40585366.0465985 3433927.52983003,40585366.337911 3433927.90550359,40585366.6030535 3433928.30008319,40585366.840803 3433928.71174899,40585367.0500632 3433929.13860232,40585367.2298688 3433929.5786745,40585367.3793906 3433930.02993587,40585367.4979389 3433930.49030515,40585367.5849671 3433930.95765907,40585367.6400736 3433931.42984214,40585367.6630045 3433931.9046766,40585367.6536538 3433932.37997246,40585367.6120647 3433932.85353759,40585367.5384291 3433933.32318787,40585367.4330866 3433933.7867572,40585367.2965229 3433934.24210757,40585367.129368 3433934.68713883,40585366.9323928 3433935.11979846,40585366.7065058 3433935.53809098)" ) + + ds = gdal.OpenEx( + "data/dxf/closed_polyline_with_bulge.dxf", + open_options=["CLOSED_LINE_AS_POLYGON=YES"], + ) + lyr = ds.GetLayer(0) + f = lyr.GetNextFeature() + g = f.GetGeometryRef() + assert g.GetGeometryType() == ogr.wkbPolygon diff --git a/doc/source/drivers/vector/dwg.rst b/doc/source/drivers/vector/dwg.rst index 5669134281a4..6e7b58ec762d 100644 --- a/doc/source/drivers/vector/dwg.rst +++ b/doc/source/drivers/vector/dwg.rst @@ -85,6 +85,14 @@ The following configuration options are available: :config:`DWG_ALL_ATTRIBUTES` to TRUE value (this is the default value). +- .. config:: DWG_CLOSED_LINE_AS_POLYGON + :choices: TRUE, FALSE + :default: FALSE + :since: 3.10 + + This option can be set to TRUE specified to ask for closed POLYLINE and + LWPOLYLINE to be exposed as OGR polygons. + Building -------- diff --git a/doc/source/drivers/vector/dxf.rst b/doc/source/drivers/vector/dxf.rst index 63ab76fe5b67..30b348621b34 100644 --- a/doc/source/drivers/vector/dxf.rst +++ b/doc/source/drivers/vector/dxf.rst @@ -34,9 +34,7 @@ fields: NULL otherwise. - SubClasses: Where available, a list of classes to which an entity belongs. -- ExtendedEntity (GDAL <= 2.2.x): The values of extended entity - attributes all appended to form a single text field, where available. -- RawCodeValues (GDAL >= 2.3.0): A string list +- RawCodeValues: A string list containing all group codes and values that are not handled by the DXF reader. Only available when the configuration option :config:`DXF_INCLUDE_RAW_CODE_VALUES=TRUE`. @@ -73,18 +71,27 @@ The following entity types are supported: polylines (those with their vertices' bulge attributes set) will be tessellated. Single-vertex polylines are translated to POINT. Polyface meshes are translated as POLYHEDRALSURFACE geometries. -- MLINE: - - (GDAL >= 2.3.0) Translated as a MULTILINESTRING. Only the geometry - is reconstructed; styling applied to individual line elements - within the MLINE is ignored. Fill colors and start/end caps are - also omitted. - - (GDAL <= 2.2.x) No support. + Starting with GDAL 3.10, the :config:`DXF_CLOSED_LINE_AS_POLYGON` + configuration option can be set to TRUE to ask for closed POLYLINE and + LWPOLYLINE to be exposed as OGR polygons. -- CIRCLE, ELLIPSE, ARC, SPLINE, (GDAL >= 2.3.0) HELIX: Translated as a + .. config:: DXF_CLOSED_LINE_AS_POLYGON + :choices: TRUE, FALSE + :default: FALSE + + +- MLINE: Translated as a MULTILINESTRING. Only the geometry + is reconstructed; styling applied to individual line elements + within the MLINE is ignored. Fill colors and start/end caps are + also omitted. + +- CIRCLE, ELLIPSE, ARC, SPLINE,HELIX: Translated as a LINESTRING, tessellating the curve into line segments. - (GDAL >= 2.3.0) CIRCLEs with nonzero "thickness" (cylinders) are + + CIRCLEs with nonzero "thickness" (cylinders) are approximated as a POLYHEDRALSURFACE. + - INSERT: By default, the block definition referenced by the INSERT will be inserted as a compound geometry (for example, a MULTILINESTRING for a block containing many lines, or a @@ -97,6 +104,7 @@ The following entity types are supported: - .. config:: DXF_MERGE_BLOCK_GEOMETRIES :choices: TRUE, FALSE + :default: FALSE To avoid merging blocks into a compound geometry the :config:`DXF_MERGE_BLOCK_GEOMETRIES` config option may @@ -115,13 +123,9 @@ The following entity types are supported: Maximum number of features inserted from a single block. Set to -1 for no limit. -- ATTDEF, ATTRIB: - - - (GDAL >= 2.3.0) Attributes (ATTRIB) are treated as TEXT entities, - and attribute definitions (ATTDEF) inside blocks are ignored. The - behavior is different when :config:`DXF_INLINE_BLOCKS` is false (see below). - - (GDAL <= 2.2.x) ATTDEF entities are treated as TEXT. ATTRIB - entities are not supported. +- ATTDEF, ATTRIB: Attributes (ATTRIB) are treated as TEXT entities, + and attribute definitions (ATTDEF) inside blocks are ignored. The + behavior is different when :config:`DXF_INLINE_BLOCKS` is false (see below). - HATCH: Line and arc boundaries are collected as a polygon geometry, but no effort is currently made to represent the fill style of HATCH @@ -134,36 +138,27 @@ The following entity types are supported: tolerance used when looking for the next component to add to the hatch boundary. - (GDAL <= 2.2.x) Only line and polyline boundary paths are translated - correctly. - - 3DFACE, SOLID, (GDAL >= 2.3.0) TRACE: Translated as POLYGON, except for SOLID and TRACE entities with only one distinct vertex (translated as POINT) or two distinct vertices (translated as LINESTRING). -- DIMENSION: - - - (GDAL >= 2.3.0) The DXF format allows each DIMENSION entity to - reference an "anonymous" block (a block whose name starts with - \*D) that contains the geometry of the DIMENSION. If present, this - anonymous block will be inlined at the required position. - Otherwise, fallback will occur to a simple DIMENSION renderer that - explodes a linear dimension as a MULTILINESTRING feature. - Arrowheads, if present, are translated as one or more additional - features. The fallback renderer will render nonlinear dimensions - as if they were linear. - - (GDAL <= 2.2.x) Dimensions are translated as a MULTILINESTRING and - a POINT for the text. - -- LEADER, MULTILEADER: - - - (GDAL >= 2.3.0) The leader line is translated as a LINESTRING - (LEADER) or MULTILINESTRING (MULTILEADER). Arrowheads, if present, - are translated as one or more additional features. Text for - MULTILEADER entities is translated into a POINT feature with a - label. Block content for MULTILEADERS is treated as for INSERT. - Spline leaders are tessellated into line segments. - - (GDAL <= 2.2.x) No support. + +- DIMENSION: The DXF format allows each DIMENSION entity to + reference an "anonymous" block (a block whose name starts with + \*D) that contains the geometry of the DIMENSION. If present, this + anonymous block will be inlined at the required position. + Otherwise, fallback will occur to a simple DIMENSION renderer that + explodes a linear dimension as a MULTILINESTRING feature. + Arrowheads, if present, are translated as one or more additional + features. The fallback renderer will render nonlinear dimensions + as if they were linear. + +- LEADER, MULTILEADER: The leader line is translated as a LINESTRING + (LEADER) or MULTILINESTRING (MULTILEADER). Arrowheads, if present, + are translated as one or more additional features. Text for + MULTILEADER entities is translated into a POINT feature with a + label. Block content for MULTILEADERS is treated as for INSERT. + Spline leaders are tessellated into line segments. - 3DSOLID, REGION, BODY, SURFACE: See below. @@ -186,6 +181,9 @@ specification, except DIMENSION, LEADER and MULTILEADER. These three entity types also currently lack support for elevations; the geometries will always be 2D. + +.. _dxf_inline_blocks: + DXF_INLINE_BLOCKS ~~~~~~~~~~~~~~~~~ @@ -272,7 +270,68 @@ override what id will be used by OGR in transcoding: name. Using a value "UTF-8" will avoid any attempt to recode the text as it is read. --------------- +Open options +------------ + +.. versionadded:: 3.10 + +|about-open-options| +The following open options are supported: + +- .. oo:: CLOSED_LINE_AS_POLYGON + :since: 3.10 + :default: NO + :choices: YES, NO + + See :config:`DXF_CLOSED_LINE_AS_POLYGON` + +- .. oo:: INLINE_BLOCKS + :since: 3.10 + :default: YES + :choices: YES, NO + + See :ref:`dxf_inline_blocks` + +- .. oo:: MERGE_BLOCK_GEOMETRIES + :since: 3.10 + :default: YES + :choices: YES, NO + + See :config:`DXF_MERGE_BLOCK_GEOMETRIES` + +- .. oo:: TRANSLATE_ESCAPE_SEQUENCES + :since: 3.10 + :default: YES + :choices: YES, NO + + See :config:`DXF_TRANSLATE_ESCAPE_SEQUENCES` + +- .. oo:: INCLUDE_RAW_CODE_VALUES + :since: 3.10 + :default: NO + :choices: YES, NO + + See :config:`DXF_INCLUDE_RAW_CODE_VALUES` + +- .. oo:: 3D_EXTENSIBLE_MODE + :since: 3.10 + :default: NO + :choices: YES, NO + + See :config:`DXF_3D_EXTENSIBLE_MODE` + +- .. oo:: HATCH_TOLERANCE + :since: 3.10 + :choices: + + See :config:`DXF_HATCH_TOLERANCE` + +- .. oo:: ENCODING + :since: 3.10 + :choices: + + See :config:`DXF_ENCODING` + DXF Writer ---------- diff --git a/ogr/ogrsf_frmts/dwg/ogr_dwg.h b/ogr/ogrsf_frmts/dwg/ogr_dwg.h index a9e74b508b49..7b29f37a6473 100644 --- a/ogr/ogrsf_frmts/dwg/ogr_dwg.h +++ b/ogr/ogrsf_frmts/dwg/ogr_dwg.h @@ -189,6 +189,8 @@ class OGRDWGDataSource final : public OGRDataSource int bAttributes; int bAllAttributes; + bool m_bClosedLineAsPolygon = false; + OGRDWGServices *poServices; OdDbDatabasePtr poDb; @@ -235,6 +237,11 @@ class OGRDWGDataSource final : public OGRDataSource return bAllAttributes; } + bool ClosedLineAsPolygon() const + { + return m_bClosedLineAsPolygon; + } + void AddStandardFields(OGRFeatureDefn *poDef); // Implemented in ogrdxf_blockmap.cpp diff --git a/ogr/ogrsf_frmts/dwg/ogrdwg_hatch.cpp b/ogr/ogrsf_frmts/dwg/ogrdwg_hatch.cpp index 5e181de37ad2..6fc6a2c0c379 100644 --- a/ogr/ogrsf_frmts/dwg/ogrdwg_hatch.cpp +++ b/ogr/ogrsf_frmts/dwg/ogrdwg_hatch.cpp @@ -144,7 +144,7 @@ static OGRErr DWGCollectBoundaryLoop(OdDbHatchPtr poHatch, int iLoop, oSmoothPolyline.Close(); - OGRLineString *poLS = oSmoothPolyline.Tessellate()->toLineString(); + OGRLineString *poLS = oSmoothPolyline.Tessellate(false)->toLineString(); poGC->addGeometryDirectly(poLS); return OGRERR_NONE; diff --git a/ogr/ogrsf_frmts/dwg/ogrdwgdatasource.cpp b/ogr/ogrsf_frmts/dwg/ogrdwgdatasource.cpp index f91dcab83553..15676b01b5e1 100644 --- a/ogr/ogrsf_frmts/dwg/ogrdwgdatasource.cpp +++ b/ogr/ogrsf_frmts/dwg/ogrdwgdatasource.cpp @@ -100,6 +100,8 @@ int OGRDWGDataSource::Open(OGRDWGServices *poServicesIn, bAttributes = CPLTestBool(CPLGetConfigOption("DWG_ATTRIBUTES", "FALSE")); bAllAttributes = CPLTestBool(CPLGetConfigOption("DWG_ALL_ATTRIBUTES", "TRUE")); + m_bClosedLineAsPolygon = + CPLTestBool(CPLGetConfigOption("DWG_CLOSED_LINE_AS_POLYGON", "FALSE")); /* -------------------------------------------------------------------- */ /* Open the file. */ diff --git a/ogr/ogrsf_frmts/dwg/ogrdwglayer.cpp b/ogr/ogrsf_frmts/dwg/ogrdwglayer.cpp index e32050f5ca43..c79476e1b2d5 100644 --- a/ogr/ogrsf_frmts/dwg/ogrdwglayer.cpp +++ b/ogr/ogrsf_frmts/dwg/ogrdwglayer.cpp @@ -692,7 +692,9 @@ OGRFeature *OGRDWGLayer::TranslateLWPOLYLINE(OdDbEntityPtr poEntity) if (poPL->isClosed()) oSmoothPolyline.Close(); - poFeature->SetGeometryDirectly(oSmoothPolyline.Tessellate()); + const bool bAsPolygon = poPL->isClosed() && poDS->ClosedLineAsPolygon(); + + poFeature->SetGeometryDirectly(oSmoothPolyline.Tessellate(bAsPolygon)); PrepareLineStyle(poFeature); diff --git a/ogr/ogrsf_frmts/dxf/ogr_dxf.h b/ogr/ogrsf_frmts/dxf/ogr_dxf.h index 7badbce4a891..31de83214961 100644 --- a/ogr/ogrsf_frmts/dxf/ogr_dxf.h +++ b/ogr/ogrsf_frmts/dxf/ogr_dxf.h @@ -675,6 +675,8 @@ class OGRDXFDataSource final : public OGRDataSource bool bMergeBlockGeometries; bool bTranslateEscapeSequences; bool bIncludeRawCodeValues; + bool m_bClosedLineAsPolygon = false; + double m_dfHatchTolerance = -1.0; bool b3DExtensibleMode; bool bHaveReadSolidData; @@ -688,7 +690,8 @@ class OGRDXFDataSource final : public OGRDataSource OGRDXFDataSource(); ~OGRDXFDataSource(); - int Open(const char *pszFilename, int bHeaderOnly = FALSE); + int Open(const char *pszFilename, bool bHeaderOnly, + CSLConstList papszOptionsIn); const char *GetName() override { @@ -731,6 +734,16 @@ class OGRDXFDataSource final : public OGRDataSource return b3DExtensibleMode; } + bool ClosedLineAsPolygon() const + { + return m_bClosedLineAsPolygon; + } + + double HatchTolerance() const + { + return m_dfHatchTolerance; + } + static void AddStandardFields(OGRFeatureDefn *poDef, const int nFieldModes); // Implemented in ogrdxf_blockmap.cpp diff --git a/ogr/ogrsf_frmts/dxf/ogrdxf_hatch.cpp b/ogr/ogrsf_frmts/dxf/ogrdxf_hatch.cpp index 6affd2e8b8d3..bf50ce8a55e8 100644 --- a/ogr/ogrsf_frmts/dxf/ogrdxf_hatch.cpp +++ b/ogr/ogrsf_frmts/dxf/ogrdxf_hatch.cpp @@ -106,7 +106,7 @@ OGRDXFFeature *OGRDXFLayer::TranslateHATCH() /* -------------------------------------------------------------------- */ /* Obtain a tolerance value used when building the polygon. */ /* -------------------------------------------------------------------- */ - double dfTolerance = atof(CPLGetConfigOption("DXF_HATCH_TOLERANCE", "-1")); + double dfTolerance = poDS->HatchTolerance(); if (dfTolerance < 0) { // If the configuration variable isn't set, compute the bounding box @@ -696,7 +696,7 @@ OGRErr OGRDXFLayer::CollectPolylinePath(OGRGeometryCollection *poGC, if (nVertexCount >= 2) { oSmoothPolyline.SetUseMaxGapWhenTessellatingArcs(poDS->InlineBlocks()); - poGC->addGeometryDirectly(oSmoothPolyline.Tessellate()); + poGC->addGeometryDirectly(oSmoothPolyline.Tessellate(false)); } /* -------------------------------------------------------------------- */ diff --git a/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.cpp b/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.cpp index 17ee916fae6c..1920b3eef05a 100644 --- a/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.cpp +++ b/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.cpp @@ -63,7 +63,7 @@ static double GetOGRangle(double angle) /* DXFSmoothPolyline::Tessellate() */ /************************************************************************/ -OGRGeometry *DXFSmoothPolyline::Tessellate() const +OGRGeometry *DXFSmoothPolyline::Tessellate(bool bAsPolygon) const { assert(!m_vertices.empty()); @@ -84,7 +84,8 @@ OGRGeometry *DXFSmoothPolyline::Tessellate() const /* Otherwise, presume a line string */ /* -------------------------------------------------------------------- */ - OGRLineString *poLS = new OGRLineString; + OGRLinearRing *poLR = m_bClosed && bAsPolygon ? new OGRLinearRing : nullptr; + OGRLineString *poLS = poLR ? poLR : new OGRLineString; m_blinestringstarted = false; @@ -126,28 +127,14 @@ OGRGeometry *DXFSmoothPolyline::Tessellate() const if (m_dim == 2) poLS->flattenTo2D(); -/* -------------------------------------------------------------------- */ -/* If polyline is closed, convert linestring to a linear ring */ -/* */ -/* Actually, on review I'm not convinced this is a good idea. */ -/* Note that most (all) "filled polygons" are expressed with */ -/* hatches which are now handled fairly well and they tend to */ -/* echo linear polylines. */ -/* -------------------------------------------------------------------- */ -#ifdef notdef - if (m_bClosed) + if (poLR) { - OGRLinearRing *poLR = new OGRLinearRing(); - poLR->addSubLineString(poLS, 0); - delete poLS; - // Wrap as polygon. OGRPolygon *poPoly = new OGRPolygon(); poPoly->addRingDirectly(poLR); return poPoly; } -#endif return poLS; } diff --git a/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.h b/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.h index 2ee6a08ba090..8861ccb1831d 100644 --- a/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.h +++ b/ogr/ogrsf_frmts/dxf/ogrdxf_polyline_smooth.h @@ -114,7 +114,7 @@ class DXFSmoothPolyline { } - OGRGeometry *Tessellate() const; + OGRGeometry *Tessellate(bool bAsPolygon) const; size_t size() const { diff --git a/ogr/ogrsf_frmts/dxf/ogrdxfdatasource.cpp b/ogr/ogrsf_frmts/dxf/ogrdxfdatasource.cpp index 55f622fd3e67..b00d06e37f41 100644 --- a/ogr/ogrsf_frmts/dxf/ogrdxfdatasource.cpp +++ b/ogr/ogrsf_frmts/dxf/ogrdxfdatasource.cpp @@ -100,26 +100,44 @@ OGRLayer *OGRDXFDataSource::GetLayer(int iLayer) /* Open() */ /************************************************************************/ -int OGRDXFDataSource::Open(const char *pszFilename, int bHeaderOnly) +int OGRDXFDataSource::Open(const char *pszFilename, bool bHeaderOnly, + CSLConstList papszOptionsIn) { osEncoding = CPL_ENC_ISO8859_1; osName = pszFilename; - bInlineBlocks = - CPLTestBool(CPLGetConfigOption("DXF_INLINE_BLOCKS", "TRUE")); - bMergeBlockGeometries = - CPLTestBool(CPLGetConfigOption("DXF_MERGE_BLOCK_GEOMETRIES", "TRUE")); - bTranslateEscapeSequences = CPLTestBool( - CPLGetConfigOption("DXF_TRANSLATE_ESCAPE_SEQUENCES", "TRUE")); - bIncludeRawCodeValues = - CPLTestBool(CPLGetConfigOption("DXF_INCLUDE_RAW_CODE_VALUES", "FALSE")); - b3DExtensibleMode = - CPLTestBool(CPLGetConfigOption("DXF_3D_EXTENSIBLE_MODE", "FALSE")); + bInlineBlocks = CPLTestBool( + CSLFetchNameValueDef(papszOptionsIn, "INLINE_BLOCKS", + CPLGetConfigOption("DXF_INLINE_BLOCKS", "TRUE"))); + bMergeBlockGeometries = CPLTestBool(CSLFetchNameValueDef( + papszOptionsIn, "MERGE_BLOCK_GEOMETRIES", + CPLGetConfigOption("DXF_MERGE_BLOCK_GEOMETRIES", "TRUE"))); + bTranslateEscapeSequences = CPLTestBool(CSLFetchNameValueDef( + papszOptionsIn, "TRANSLATE_ESCAPE_SEQUENCES", + CPLGetConfigOption("DXF_TRANSLATE_ESCAPE_SEQUENCES", "TRUE"))); + + bIncludeRawCodeValues = CPLTestBool(CSLFetchNameValueDef( + papszOptionsIn, "INCLUDE_RAW_CODE_VALUES", + CPLGetConfigOption("DXF_INCLUDE_RAW_CODE_VALUES", "FALSE"))); + + b3DExtensibleMode = CPLTestBool(CSLFetchNameValueDef( + papszOptionsIn, "3D_EXTENSIBLE_MODE", + CPLGetConfigOption("DXF_3D_EXTENSIBLE_MODE", "FALSE"))); + + m_bClosedLineAsPolygon = CPLTestBool(CSLFetchNameValueDef( + papszOptionsIn, "CLOSED_LINE_AS_POLYGON", + CPLGetConfigOption("DXF_CLOSED_LINE_AS_POLYGON", "FALSE"))); + + m_dfHatchTolerance = CPLAtof( + CSLFetchNameValueDef(papszOptionsIn, "HATCH_TOLERANCE", + CPLGetConfigOption("DXF_HATCH_TOLERANCE", "-1"))); + + // Only for debugging if (CPLTestBool(CPLGetConfigOption("DXF_HEADER_ONLY", "FALSE"))) - bHeaderOnly = TRUE; + bHeaderOnly = true; /* -------------------------------------------------------------------- */ /* Open the file. */ @@ -151,7 +169,9 @@ int OGRDXFDataSource::Open(const char *pszFilename, int bHeaderOnly) */ else if (EQUAL(szLineBuf, "TABLES")) { - osEncoding = CPLGetConfigOption("DXF_ENCODING", osEncoding); + osEncoding = CSLFetchNameValueDef( + papszOptionsIn, "ENCODING", + CPLGetConfigOption("DXF_ENCODING", osEncoding)); if (!ReadTablesSection()) return FALSE; diff --git a/ogr/ogrsf_frmts/dxf/ogrdxfdriver.cpp b/ogr/ogrsf_frmts/dxf/ogrdxfdriver.cpp index f109b890811d..33490946a918 100644 --- a/ogr/ogrsf_frmts/dxf/ogrdxfdriver.cpp +++ b/ogr/ogrsf_frmts/dxf/ogrdxfdriver.cpp @@ -85,7 +85,8 @@ static GDALDataset *OGRDXFDriverOpen(GDALOpenInfo *poOpenInfo) OGRDXFDataSource *poDS = new OGRDXFDataSource(); - if (!poDS->Open(poOpenInfo->pszFilename)) + if (!poDS->Open(poOpenInfo->pszFilename, false, + poOpenInfo->papszOpenOptions)) { delete poDS; poDS = nullptr; @@ -146,6 +147,35 @@ void RegisterOGRDXF() "first entity'/>" ""); + poDriver->SetMetadataItem( + GDAL_DMD_OPENOPTIONLIST, + "" + " "); + poDriver->SetMetadataItem(GDAL_DS_LAYER_CREATIONOPTIONLIST, ""); diff --git a/ogr/ogrsf_frmts/dxf/ogrdxflayer.cpp b/ogr/ogrsf_frmts/dxf/ogrdxflayer.cpp index 7bacee8e844c..ba61e2a5b2ea 100644 --- a/ogr/ogrsf_frmts/dxf/ogrdxflayer.cpp +++ b/ogr/ogrsf_frmts/dxf/ogrdxflayer.cpp @@ -1170,11 +1170,15 @@ OGRDXFFeature *OGRDXFLayer::TranslateLWPOLYLINE() /* -------------------------------------------------------------------- */ /* Close polyline if necessary. */ /* -------------------------------------------------------------------- */ - if (nPolylineFlag & 0x01) + const bool bIsClosed = (nPolylineFlag & 0x01) != 0; + if (bIsClosed) smoothPolyline.Close(); + const bool bAsPolygon = bIsClosed && poDS->ClosedLineAsPolygon(); + smoothPolyline.SetUseMaxGapWhenTessellatingArcs(poDS->InlineBlocks()); - auto poGeom = std::unique_ptr(smoothPolyline.Tessellate()); + auto poGeom = + std::unique_ptr(smoothPolyline.Tessellate(bAsPolygon)); poFeature->ApplyOCSTransformer(poGeom.get()); poFeature->SetGeometryDirectly(poGeom.release()); @@ -1419,11 +1423,14 @@ OGRDXFFeature *OGRDXFLayer::TranslatePOLYLINE() /* -------------------------------------------------------------------- */ /* Close polyline if necessary. */ /* -------------------------------------------------------------------- */ - if (nPolylineFlag & 0x01) + const bool bIsClosed = (nPolylineFlag & 0x01) != 0; + if (bIsClosed) smoothPolyline.Close(); + const bool bAsPolygon = bIsClosed && poDS->ClosedLineAsPolygon(); + smoothPolyline.SetUseMaxGapWhenTessellatingArcs(poDS->InlineBlocks()); - OGRGeometry *poGeom = smoothPolyline.Tessellate(); + OGRGeometry *poGeom = smoothPolyline.Tessellate(bAsPolygon); if ((nPolylineFlag & 8) == 0) poFeature->ApplyOCSTransformer(poGeom); diff --git a/ogr/ogrsf_frmts/dxf/ogrdxfwriterds.cpp b/ogr/ogrsf_frmts/dxf/ogrdxfwriterds.cpp index 77466d699746..8cbfc1c05b31 100644 --- a/ogr/ogrsf_frmts/dxf/ogrdxfwriterds.cpp +++ b/ogr/ogrsf_frmts/dxf/ogrdxfwriterds.cpp @@ -237,7 +237,7 @@ int OGRDXFWriterDS::Open(const char *pszFilename, char **papszOptions) /* Attempt to read the template header file so we have a list */ /* of layers, linestyles and blocks. */ /* -------------------------------------------------------------------- */ - if (!oHeaderDS.Open(osHeaderFile, TRUE)) + if (!oHeaderDS.Open(osHeaderFile, true, nullptr)) return FALSE; /* -------------------------------------------------------------------- */ From e67a9db47ea67b198dbb51226c84193dad4c2719 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 21 Jun 2024 18:08:39 +0200 Subject: [PATCH 0207/1119] SQLite/GPKG: detect UNIQUE constraints expressed as a ', CONSTRAINT name UNIQUE (column_name)' at the end of CREATE TABLE Fixes https://github.com/qgis/QGIS/issues/57823 --- autotest/ogr/ogr_sqlite.py | 10 +- ogr/ogrsf_frmts/sqlite/ogrsqliteutility.cpp | 105 +++++++++++++++++++- 2 files changed, 111 insertions(+), 4 deletions(-) diff --git a/autotest/ogr/ogr_sqlite.py b/autotest/ogr/ogr_sqlite.py index eb21a81fd517..a85b64511b75 100755 --- a/autotest/ogr/ogr_sqlite.py +++ b/autotest/ogr/ogr_sqlite.py @@ -3314,7 +3314,7 @@ def test_ogr_sqlite_unique(tmp_vsimem): # and indexes # Note: leave create table in a single line because of regex spaces testing sql = ( - 'CREATE TABLE IF NOT EXISTS "test2" ( "fid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"field_default" TEXT, "field_no_unique" TEXT DEFAULT \'UNIQUE\',"field_unique" TEXT UNIQUE,`field unique2` TEXT UNIQUE,field_unique3 TEXT UNIQUE, FIELD_UNIQUE_INDEX TEXT, `field unique index2`, "field_unique_index3" TEXT, NOT_UNIQUE TEXT);', + 'CREATE TABLE IF NOT EXISTS "test2" ( "fid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n"field_default" TEXT, "field_no_unique" TEXT DEFAULT \'UNIQUE\',"field_unique" TEXT UNIQUE,`field unique2` TEXT UNIQUE,field_unique3 TEXT UNIQUE, FIELD_UNIQUE_INDEX TEXT, `field unique index2`, "field_unique_index3" TEXT, NOT_UNIQUE TEXT,field4 TEXT,field5 TEXT,field6 TEXT,CONSTRAINT ignored_constraint CHECK (fid >= 0),CONSTRAINT field5_6_uniq UNIQUE (field5, field6), CONSTRAINT field4_uniq UNIQUE (field4));', "CREATE UNIQUE INDEX test2_unique_idx ON test2(field_unique_index);", # field_unique_index in lowercase whereas in uppercase in CREATE TABLE statement "CREATE UNIQUE INDEX test2_unique_idx2 ON test2(`field unique index2`);", 'CREATE UNIQUE INDEX test2_unique_idx3 ON test2("field_unique_index3");', @@ -3365,6 +3365,14 @@ def test_ogr_sqlite_unique(tmp_vsimem): fldDef = layerDefinition.GetFieldDefn(8) assert not fldDef.IsUnique() + # Constraint given by CONSTRAINT field4_uniq UNIQUE (field4) + fldDef = layerDefinition.GetFieldDefn(layerDefinition.GetFieldIndex("field4")) + assert fldDef.IsUnique() + + # Constraint given by CONSTRAINT field5_6_uniq UNIQUE (field5, field6) ==> ignored + fldDef = layerDefinition.GetFieldDefn(layerDefinition.GetFieldIndex("field5")) + assert not fldDef.IsUnique() + ds = None diff --git a/ogr/ogrsf_frmts/sqlite/ogrsqliteutility.cpp b/ogr/ogrsf_frmts/sqlite/ogrsqliteutility.cpp index 43301e140682..7e3466e56ad1 100644 --- a/ogr/ogrsf_frmts/sqlite/ogrsqliteutility.cpp +++ b/ogr/ogrsf_frmts/sqlite/ogrsqliteutility.cpp @@ -424,14 +424,14 @@ std::set SQLGetUniqueFieldUCConstraints( } } } - else if (osStr[pos] == ',') + else if (osStr[pos] == ',' || osStr[pos] == '(' || osStr[pos] == ')') { - osToken = ','; + osToken = osStr[pos]; pos++; } else { - size_t pos2 = osStr.find_first_of(" \t\n\r,", pos); + size_t pos2 = osStr.find_first_of(") \t\n\r,", pos); if (pos2 == std::string::npos) osToken = osStr.substr(pos); else @@ -453,8 +453,18 @@ std::set SQLGetUniqueFieldUCConstraints( tableDefinition = tableDefinition.substr(nPosStart + 1, nPosEnd - nPosStart - 1); size_t pos = 0; + bool bHasConstraint = false; while (true) { + size_t posBackup = pos; + if (EQUAL( + GetNextToken(tableDefinition, posBackup, true).c_str(), + "CONSTRAINT")) + { + bHasConstraint = true; + break; + } + const std::string osColName = GetNextToken(tableDefinition, pos, false); if (osColName.empty()) @@ -473,6 +483,95 @@ std::set SQLGetUniqueFieldUCConstraints( } } } + + // Process https://www.sqlite.org/syntax/table-constraint.html + if (bHasConstraint) + { + while (true) + { + if (!EQUAL(GetNextToken(tableDefinition, pos, true).c_str(), + "CONSTRAINT")) + { + break; + } + + const std::string osConstraintName = + GetNextToken(tableDefinition, pos, false); + if (osConstraintName.empty()) + { + break; + } + + const std::string osConstraintType = + GetNextToken(tableDefinition, pos, true); + if (osConstraintType.empty()) + { + break; + } + + if (EQUAL(osConstraintType.c_str(), "UNIQUE")) + { + std::string osToken = + GetNextToken(tableDefinition, pos, true); + if (osToken != "(") + break; + + const std::string osColName = + GetNextToken(tableDefinition, pos, false); + osToken = GetNextToken(tableDefinition, pos, true); + if (osToken == ")") + { + // Only takes into account single column unique constraint + uniqueFieldsUC.insert( + CPLString(osColName).toupper()); + } + else + { + // Skip tokens until ')' + if (!osToken.empty()) + { + do + { + osToken = GetNextToken(tableDefinition, pos, + true); + } while (!osToken.empty() && osToken != ")"); + } + if (osToken.empty()) + break; + } + osToken = GetNextToken(tableDefinition, pos, true); + if (osToken != ",") + break; + } + else + { + // Skip ignored constraint types by looking for the + // next "," token, that is not inside parenthesis. + int nCountParenthesis = 0; + std::string osToken; + while (true) + { + osToken = GetNextToken(tableDefinition, pos, true); + if (osToken.empty()) + break; + if (nCountParenthesis == 0 && osToken == ",") + break; + else if (osToken == "(") + nCountParenthesis++; + else if (osToken == ")") + { + nCountParenthesis--; + if (nCountParenthesis < 0) + { + break; + } + } + } + if (!(nCountParenthesis == 0 && osToken == ",")) + break; + } + } + } } } From c8ccf54f646bd3df4df5f9fe6abcebd08ccd66ea Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 20 Jun 2024 22:53:16 +0200 Subject: [PATCH 0208/1119] OGRGenSQLResultsLayer: code cleanups --- ogr/ogrsf_frmts/generic/ogr_gensql.cpp | 66 ++++++++++++-------------- ogr/ogrsf_frmts/generic/ogr_gensql.h | 2 +- 2 files changed, 31 insertions(+), 37 deletions(-) diff --git a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp index b67645c67f77..6ba68e0bf3a8 100644 --- a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp +++ b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp @@ -1056,12 +1056,14 @@ bool OGRGenSQLResultsLayer::PrepareSummary() /* OGRMultiFeatureFetcher() */ /************************************************************************/ +typedef std::vector> VectorOfUniquePtrFeature; + static swq_expr_node *OGRMultiFeatureFetcher(swq_expr_node *op, void *pFeatureList) { - std::vector *papoFeatures = - static_cast *>(pFeatureList); + auto &apoFeatures = + *(static_cast(pFeatureList)); swq_expr_node *poRetNode = nullptr; CPLAssert(op->eNodeType == SNT_COLUMN); @@ -1070,7 +1072,7 @@ static swq_expr_node *OGRMultiFeatureFetcher(swq_expr_node *op, /* What feature are we using? The primary or one of the joined ones?*/ /* -------------------------------------------------------------------- */ if (op->table_index < 0 || - op->table_index >= static_cast(papoFeatures->size())) + op->table_index >= static_cast(apoFeatures.size())) { CPLError(CE_Failure, CPLE_AppDefined, "Request for unexpected table_index (%d) in field fetcher.", @@ -1078,7 +1080,7 @@ static swq_expr_node *OGRMultiFeatureFetcher(swq_expr_node *op, return nullptr; } - OGRFeature *poFeature = (*papoFeatures)[op->table_index]; + OGRFeature *poFeature = apoFeatures[op->table_index].get(); /* -------------------------------------------------------------------- */ /* Fetch the value. */ @@ -1271,27 +1273,27 @@ static CPLString GetFilterForJoin(swq_expr_node *poExpr, OGRFeature *poSrcFeat, /* TranslateFeature() */ /************************************************************************/ -OGRFeature *OGRGenSQLResultsLayer::TranslateFeature(OGRFeature *poSrcFeat) +std::unique_ptr OGRGenSQLResultsLayer::TranslateFeature( + std::unique_ptr poSrcFeatUniquePtr) { swq_select *psSelectInfo = m_pSelectInfo.get(); - std::vector apoFeatures; + VectorOfUniquePtrFeature apoFeatures; - if (poSrcFeat == nullptr) + if (poSrcFeatUniquePtr == nullptr) return nullptr; m_nFeaturesRead++; - apoFeatures.push_back(poSrcFeat); + apoFeatures.push_back(std::move(poSrcFeatUniquePtr)); + auto poSrcFeat = apoFeatures.front().get(); /* -------------------------------------------------------------------- */ /* Fetch the corresponding features from any jointed tables. */ /* -------------------------------------------------------------------- */ for (int iJoin = 0; iJoin < psSelectInfo->join_count; iJoin++) { - CPLString osFilter; - - swq_join_def *psJoinInfo = psSelectInfo->join_defs + iJoin; + const swq_join_def *psJoinInfo = psSelectInfo->join_defs + iJoin; /* OGRMultiFeatureFetcher assumes that the features are pushed in */ /* apoFeatures with increasing secondary_table, so make sure */ @@ -1300,8 +1302,9 @@ OGRFeature *OGRGenSQLResultsLayer::TranslateFeature(OGRFeature *poSrcFeat) OGRLayer *poJoinLayer = m_apoTableLayers[psJoinInfo->secondary_table]; - osFilter = GetFilterForJoin(psJoinInfo->poExpr, poSrcFeat, poJoinLayer, - psJoinInfo->secondary_table); + const std::string osFilter = + GetFilterForJoin(psJoinInfo->poExpr, poSrcFeat, poJoinLayer, + psJoinInfo->secondary_table); // CPLDebug("OGR", "Filter = %s\n", osFilter.c_str()); // if source key is null, we can't do join. @@ -1311,19 +1314,19 @@ OGRFeature *OGRGenSQLResultsLayer::TranslateFeature(OGRFeature *poSrcFeat) continue; } - OGRFeature *poJoinFeature = nullptr; + std::unique_ptr poJoinFeature; poJoinLayer->ResetReading(); if (poJoinLayer->SetAttributeFilter(osFilter.c_str()) == OGRERR_NONE) - poJoinFeature = poJoinLayer->GetNextFeature(); + poJoinFeature.reset(poJoinLayer->GetNextFeature()); - apoFeatures.push_back(poJoinFeature); + apoFeatures.push_back(std::move(poJoinFeature)); } /* -------------------------------------------------------------------- */ /* Create destination feature. */ /* -------------------------------------------------------------------- */ - OGRFeature *poDstFeat = new OGRFeature(m_poDefn); + auto poDstFeat = std::make_unique(m_poDefn); poDstFeat->SetFID(poSrcFeat->GetFID()); @@ -1339,7 +1342,7 @@ OGRFeature *OGRGenSQLResultsLayer::TranslateFeature(OGRFeature *poSrcFeat) swq_evaluation_context sContext; for (int iField = 0; iField < psSelectInfo->result_columns(); iField++) { - swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; + const swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; if (psColDef->field_index != -1) { if (psColDef->field_type == SWQ_GEOMETRY || @@ -1350,12 +1353,11 @@ OGRFeature *OGRGenSQLResultsLayer::TranslateFeature(OGRFeature *poSrcFeat) continue; } - swq_expr_node *poResult = psColDef->expr->Evaluate( - OGRMultiFeatureFetcher, &apoFeatures, sContext); + auto poResult = std::unique_ptr(psColDef->expr->Evaluate( + OGRMultiFeatureFetcher, &apoFeatures, sContext)); - if (poResult == nullptr) + if (!poResult) { - delete poDstFeat; return nullptr; } @@ -1365,7 +1367,6 @@ OGRFeature *OGRGenSQLResultsLayer::TranslateFeature(OGRFeature *poSrcFeat) iGeomField++; else iRegularField++; - delete poResult; continue; } @@ -1431,8 +1432,6 @@ OGRFeature *OGRGenSQLResultsLayer::TranslateFeature(OGRFeature *poSrcFeat) poDstFeat->SetField(iRegularField++, poResult->string_value); break; } - - delete poResult; } /* -------------------------------------------------------------------- */ @@ -1442,7 +1441,7 @@ OGRFeature *OGRGenSQLResultsLayer::TranslateFeature(OGRFeature *poSrcFeat) iGeomField = 0; for (int iField = 0; iField < psSelectInfo->result_columns(); iField++) { - swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; + const swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; if (psColDef->table_index != 0) { @@ -1534,10 +1533,8 @@ OGRFeature *OGRGenSQLResultsLayer::TranslateFeature(OGRFeature *poSrcFeat) /* -------------------------------------------------------------------- */ for (int iJoin = 0; iJoin < psSelectInfo->join_count; iJoin++) { - CPLString osFilter; - - swq_join_def *psJoinInfo = psSelectInfo->join_defs + iJoin; - OGRFeature *poJoinFeature = apoFeatures[iJoin + 1]; + const swq_join_def *psJoinInfo = psSelectInfo->join_defs + iJoin; + const OGRFeature *poJoinFeature = apoFeatures[iJoin + 1].get(); if (poJoinFeature == nullptr) continue; @@ -1546,7 +1543,7 @@ OGRFeature *OGRGenSQLResultsLayer::TranslateFeature(OGRFeature *poSrcFeat) iRegularField = 0; for (int iField = 0; iField < psSelectInfo->result_columns(); iField++) { - swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; + const swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; if (psColDef->field_type == SWQ_GEOMETRY || psColDef->target_type == SWQ_GEOMETRY) @@ -1559,8 +1556,6 @@ OGRFeature *OGRGenSQLResultsLayer::TranslateFeature(OGRFeature *poSrcFeat) iRegularField++; } - - delete poJoinFeature; } return poDstFeat; @@ -1634,8 +1629,7 @@ OGRFeature *OGRGenSQLResultsLayer::GetNextFeature() if (poSrcFeat == nullptr) return nullptr; - auto poFeature = - std::unique_ptr(TranslateFeature(poSrcFeat.get())); + auto poFeature = TranslateFeature(std::move(poSrcFeat)); if (poFeature == nullptr) return nullptr; @@ -1751,7 +1745,7 @@ OGRFeature *OGRGenSQLResultsLayer::GetFeature(GIntBig nFID) if (poSrcFeature == nullptr) return nullptr; - return TranslateFeature(poSrcFeature.get()); + return TranslateFeature(std::move(poSrcFeature)).release(); } /************************************************************************/ diff --git a/ogr/ogrsf_frmts/generic/ogr_gensql.h b/ogr/ogrsf_frmts/generic/ogr_gensql.h index dcbd87e7a101..465d025805cd 100644 --- a/ogr/ogrsf_frmts/generic/ogr_gensql.h +++ b/ogr/ogrsf_frmts/generic/ogr_gensql.h @@ -92,7 +92,7 @@ class OGRGenSQLResultsLayer final : public OGRLayer bool PrepareSummary(); - OGRFeature *TranslateFeature(OGRFeature *); + std::unique_ptr TranslateFeature(std::unique_ptr); void CreateOrderByIndex(); void ReadIndexFields(OGRFeature *poSrcFeat, int nOrderItems, OGRField *pasIndexFields); From d7e394ebd3d6e1093ed0fc478f1093fd331664e3 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 21 Jun 2024 00:12:59 +0200 Subject: [PATCH 0209/1119] OGR SQL: add SELECT expression [AS] OGR_STYLE HIDDEN to be able to specify feature style string without generating a visible column This is a bit inspired by the HIDDEN keyword used by some other SQL implementations, but generally in other contexts, such as hidden columns in SQLite3 virtual tables (https://www.sqlite.org/vtab.html) or MySQL invisible columns (https://dev.mysql.com/doc/refman/8.4/en/invisible-columns.html), although those are in a CREATE TABLE context. Fixes #10259 --- .pre-commit-config.yaml | 4 +- autotest/ogr/ogr_sql_test.py | 103 + doc/source/user/ogr_sql_dialect.rst | 19 + ogr/CMakeLists.txt | 2 +- ogr/ogr_swq.h | 9 +- ogr/ogrsf_frmts/generic/ogr_gensql.cpp | 83 +- ogr/swq.cpp | 5 + ogr/swq_expr_node.cpp | 5 +- ogr/swq_parser.cpp | 3236 ++++++++++++------------ ogr/swq_parser.hpp | 108 +- ogr/swq_parser.y | 86 +- ogr/swq_select.cpp | 23 +- scripts/cppcheck.sh | 6 + 13 files changed, 2036 insertions(+), 1653 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d07cd0fca1e9..7ea6c49bfc82 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -57,5 +57,7 @@ repos: ogr/ogrsf_frmts/geojson/libjson/| ogr/ogrsf_frmts/flatgeobuf/flatbuffers/| ogr/ogrsf_frmts/pmtiles/pmtiles/| - ogr/ogrsf_frmts/sqlite/sqlite_rtree_bulk_load + ogr/ogrsf_frmts/sqlite/sqlite_rtree_bulk_load| + ogr/swq_parser.cpp| + ogr/swq_parser.hpp ) diff --git a/autotest/ogr/ogr_sql_test.py b/autotest/ogr/ogr_sql_test.py index 0b101b8b7b4b..a328f8b36bda 100755 --- a/autotest/ogr/ogr_sql_test.py +++ b/autotest/ogr/ogr_sql_test.py @@ -1999,3 +1999,106 @@ def test_ogr_sql_on_null(where, feature_count, dialect, ds_for_test_ogr_sql_on_n "select * from layer where " + where, dialect=dialect ) as sql_lyr: assert sql_lyr.GetFeatureCount() == feature_count + + +def test_ogr_sql_ogr_style_hidden(): + + ds = ogr.GetDriverByName("Memory").CreateDataSource("test_ogr_sql_ogr_style_hidden") + lyr = ds.CreateLayer("layer") + lyr.CreateField(ogr.FieldDefn("intfield", ogr.OFTInteger)) + lyr.CreateField(ogr.FieldDefn("strfield", ogr.OFTString)) + feat = ogr.Feature(lyr.GetLayerDefn()) + feat["intfield"] = 1 + feat["strfield"] = "my_style" + feat.SetGeometry(ogr.CreateGeometryFromWkt("POINT (1 2)")) + lyr.CreateFeature(feat) + feat = ogr.Feature(lyr.GetLayerDefn()) + lyr.CreateFeature(feat) + + with ds.ExecuteSQL( + "SELECT 'BRUSH(fc:#01234567)' AS OGR_STYLE HIDDEN FROM layer" + ) as sql_lyr: + assert sql_lyr.GetLayerDefn().GetFieldCount() == 0 + f = sql_lyr.GetNextFeature() + assert f.GetStyleString() == "BRUSH(fc:#01234567)" + + with ds.ExecuteSQL("SELECT strfield OGR_STYLE HIDDEN FROM layer") as sql_lyr: + assert sql_lyr.GetLayerDefn().GetFieldCount() == 0 + f = sql_lyr.GetNextFeature() + assert f.GetStyleString() == "my_style" + f = sql_lyr.GetNextFeature() + assert f.GetStyleString() is None + + with ds.ExecuteSQL( + "SELECT CAST(strfield AS CHARACTER(255)) AS OGR_STYLE HIDDEN FROM layer" + ) as sql_lyr: + assert sql_lyr.GetLayerDefn().GetFieldCount() == 0 + f = sql_lyr.GetNextFeature() + assert f.GetStyleString() == "my_style" + f = sql_lyr.GetNextFeature() + assert f.GetStyleString() is None + + with ds.ExecuteSQL("SELECT strfield OGR_STYLE HIDDEN, * FROM layer") as sql_lyr: + assert sql_lyr.GetLayerDefn().GetFieldCount() == 2 + f = sql_lyr.GetNextFeature() + assert f.GetStyleString() == "my_style" + assert f["intfield"] == 1 + assert f["strfield"] == "my_style" + f = sql_lyr.GetNextFeature() + assert f.GetStyleString() is None + assert not f.IsFieldSet("intfield") + assert not f.IsFieldSet("strfield") + + with pytest.raises( + Exception, match="HIDDEN keyword only supported on a column named OGR_STYLE" + ): + with ds.ExecuteSQL( + "SELECT 'foo' AS not_OGR_STYLE HIDDEN FROM layer" + ) as sql_lyr: + pass + + with ds.ExecuteSQL("SELECT 123 AS OGR_STYLE HIDDEN FROM layer") as sql_lyr: + gdal.ErrorReset() + with gdal.quiet_errors(): + f = sql_lyr.GetNextFeature() + assert f.GetStyleString() is None + + with ds.ExecuteSQL("SELECT intfield AS OGR_STYLE HIDDEN FROM layer") as sql_lyr: + gdal.ErrorReset() + with gdal.quiet_errors(): + f = sql_lyr.GetNextFeature() + assert f.GetStyleString() is None + + with ds.ExecuteSQL( + 'SELECT "_ogr_geometry_" AS OGR_STYLE HIDDEN FROM layer' + ) as sql_lyr: + gdal.ErrorReset() + with gdal.quiet_errors(): + f = sql_lyr.GetNextFeature() + assert f.GetStyleString() is None + + +def test_ogr_sql_identifier_hidden(): + + ds = ogr.GetDriverByName("Memory").CreateDataSource("test_ogr_sql_ogr_style_hidden") + lyr = ds.CreateLayer("hidden") + lyr.CreateField(ogr.FieldDefn("hidden", ogr.OFTString)) + feat = ogr.Feature(lyr.GetLayerDefn()) + feat["hidden"] = "val" + lyr.CreateFeature(feat) + + with ds.ExecuteSQL("SELECT hidden FROM hidden") as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f["hidden"] == "val" + + with ds.ExecuteSQL("SELECT hidden hidden FROM hidden hidden") as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f["hidden"] == "val" + + with ds.ExecuteSQL("SELECT hidden AS hidden FROM hidden AS hidden") as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f["hidden"] == "val" + + with ds.ExecuteSQL("SELECT 'foo' AS hidden FROM hidden") as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f["hidden"] == "foo" diff --git a/doc/source/user/ogr_sql_dialect.rst b/doc/source/user/ogr_sql_dialect.rst index bcf02b195945..207d934bc869 100644 --- a/doc/source/user/ogr_sql_dialect.rst +++ b/doc/source/user/ogr_sql_dialect.rst @@ -634,6 +634,25 @@ For example we can select the annotation features as: SELECT * FROM nation WHERE OGR_STYLE LIKE 'LABEL%' + +It is possible to use the ``OGR_STYLE`` field name as a special field name in +the field selection as an alternate way of setting the :cpp:func:`OGRFeature::SetStyleString` +value, typically by aliasing another field or a string literal. + +.. code-block:: + + SELECT *, 'BRUSH(fc:#01234567)' AS OGR_STYLE FROM source_layer + + +By default, the OGR_STYLE field will still be visible as a regular field. If this +is undesirable, starting with GDAL 3.10, it can be hidden by adding the HIDDEN +keyword at the end of the field specification. + +.. code-block:: + + SELECT * EXCLUDE(my_style_field), my_style_field AS OGR_STYLE HIDDEN FROM source_layer + + CREATE INDEX ------------ diff --git a/ogr/CMakeLists.txt b/ogr/CMakeLists.txt index 258564dfc9d7..8cb85f7513ee 100644 --- a/ogr/CMakeLists.txt +++ b/ogr/CMakeLists.txt @@ -180,7 +180,7 @@ add_custom_target(check_swq_parser_md5 ALL COMMAND ${CMAKE_COMMAND} "-DIN_FILE=swq_parser.y" "-DTARGET=generate_swq_parser" - "-DEXPECTED_MD5SUM=f7faffefe55128fc8951556909523ef7" + "-DEXPECTED_MD5SUM=6c82680a0abb18a011e872929a4a4ece" "-DFILENAME_CMAKE=${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt" -P "${PROJECT_SOURCE_DIR}/cmake/helpers/check_md5sum.cmake" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" diff --git a/ogr/ogr_swq.h b/ogr/ogr_swq.h index 208b98b69013..d2461e4b0482 100644 --- a/ogr/ogr_swq.h +++ b/ogr/ogr_swq.h @@ -186,6 +186,10 @@ class CPL_UNSTABLE_API swq_expr_node /* nOperation == SWQ_CUSTOM_FUNC */ char *string_value = nullptr; /* column name when SNT_COLUMN */ + // May be transiently used by swq_parser.h, but should not be relied upon + // after parsing. swq_col_def.bHidden captures it afterwards. + bool bHidden = false; + static CPLString QuoteIfNecessary(const CPLString &, char chQuote = '\''); static CPLString Quote(const CPLString &, char chQuote = '\''); }; @@ -331,6 +335,7 @@ typedef struct int field_length; int field_precision; int distinct_flag; + bool bHidden; OGRwkbGeometryType eGeomType; int nSRID; swq_expr_node *expr; @@ -412,8 +417,8 @@ class CPL_UNSTABLE_API swq_select char *raw_select = nullptr; - int PushField(swq_expr_node *poExpr, const char *pszAlias = nullptr, - int distinct_flag = FALSE); + int PushField(swq_expr_node *poExpr, const char *pszAlias, + bool distinct_flag, bool bHidden); int PushExcludeField(swq_expr_node *poExpr); diff --git a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp index 6ba68e0bf3a8..c6a76b7a7161 100644 --- a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp +++ b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp @@ -176,6 +176,9 @@ OGRGenSQLResultsLayer::OGRGenSQLResultsLayer( OGRFeatureDefn *poLayerDefn = nullptr; int iSrcGeomField = -1; + if (psColDef->bHidden) + continue; + if (psColDef->table_index != -1) poLayerDefn = m_apoTableLayers[psColDef->table_index]->GetLayerDefn(); @@ -1343,7 +1346,73 @@ std::unique_ptr OGRGenSQLResultsLayer::TranslateFeature( for (int iField = 0; iField < psSelectInfo->result_columns(); iField++) { const swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; - if (psColDef->field_index != -1) + + if (psColDef->bHidden) + { + const char *pszDstFieldName = psColDef->field_alias + ? psColDef->field_alias + : psColDef->field_name; + if (EQUAL(pszDstFieldName, "OGR_STYLE")) + { + if (psColDef->field_type == SWQ_STRING) + { + // Does this column definition directly references a + // source field ? + if (psColDef->field_index >= 0) + { + if (!IS_GEOM_FIELD_INDEX(poSrcFeat->GetDefnRef(), + psColDef->field_index)) + { + if (poSrcFeat->IsFieldSetAndNotNull( + psColDef->field_index)) + { + const char *pszVal = + poSrcFeat->GetFieldAsString( + psColDef->field_index); + poDstFeat->SetStyleString(pszVal); + } + else + { + poDstFeat->SetStyleString(nullptr); + } + } + else + { + CPLError(CE_Warning, CPLE_AppDefined, + "OGR_STYLE HIDDEN field should reference " + "a column of type String"); + } + } + else + { + auto poResult = std::unique_ptr( + psColDef->expr->Evaluate(OGRMultiFeatureFetcher, + &apoFeatures, sContext)); + + if (!poResult) + { + return nullptr; + } + + poDstFeat->SetStyleString(poResult->is_null + ? nullptr + : poResult->string_value); + } + } + else + { + CPLError(CE_Warning, CPLE_AppDefined, + "OGR_STYLE HIDDEN field should be of type String"); + } + } + continue; + } + + // Does this column definition directly references a + // source field ? + // If so, skip it for now, as it will be taken into account in the + // next loop. + if (psColDef->field_index >= 0) { if (psColDef->field_type == SWQ_GEOMETRY || psColDef->target_type == SWQ_GEOMETRY) @@ -1443,6 +1512,13 @@ std::unique_ptr OGRGenSQLResultsLayer::TranslateFeature( { const swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; + if (psColDef->bHidden) + { + continue; + } + + // Skip this column definition if it doesn't reference a field from + // the main feature if (psColDef->table_index != 0) { if (psColDef->field_type == SWQ_GEOMETRY || @@ -1549,6 +1625,11 @@ std::unique_ptr OGRGenSQLResultsLayer::TranslateFeature( psColDef->target_type == SWQ_GEOMETRY) continue; + if (psColDef->bHidden) + { + continue; + } + if (psColDef->table_index == psJoinInfo->secondary_table) poDstFeat->SetField( iRegularField, diff --git a/ogr/swq.cpp b/ogr/swq.cpp index 3379ef531db2..c4cd3d2edc84 100644 --- a/ogr/swq.cpp +++ b/ogr/swq.cpp @@ -289,6 +289,11 @@ int swqlex(YYSTYPE *ppNode, swq_parse_context *context) nReturn = SWQT_LIMIT; else if (EQUAL(osToken, "OFFSET")) nReturn = SWQT_OFFSET; + else if (EQUAL(osToken, "HIDDEN")) + { + *ppNode = new swq_expr_node(osToken); + nReturn = SWQT_HIDDEN; + } // Unhandled by OGR SQL. else if (EQUAL(osToken, "OUTER") || EQUAL(osToken, "INNER")) diff --git a/ogr/swq_expr_node.cpp b/ogr/swq_expr_node.cpp index 0adfa01fb9dd..a3b8082673e3 100644 --- a/ogr/swq_expr_node.cpp +++ b/ogr/swq_expr_node.cpp @@ -147,7 +147,8 @@ bool swq_expr_node::operator==(const swq_expr_node &other) const nOperation != other.nOperation || field_index != other.field_index || table_index != other.table_index || nSubExprCount != other.nSubExprCount || is_null != other.is_null || - int_value != other.int_value || float_value != other.float_value) + int_value != other.int_value || float_value != other.float_value || + bHidden != other.bHidden) { return false; } @@ -234,6 +235,7 @@ swq_expr_node &swq_expr_node::operator=(const swq_expr_node &other) geometry_value = other.geometry_value->clone(); if (other.string_value) string_value = CPLStrdup(other.string_value); + bHidden = other.bHidden; } return *this; } @@ -269,6 +271,7 @@ swq_expr_node &swq_expr_node::operator=(swq_expr_node &&other) float_value = other.float_value; std::swap(geometry_value, other.geometry_value); std::swap(string_value, other.string_value); + bHidden = other.bHidden; } return *this; } diff --git a/ogr/swq_parser.cpp b/ogr/swq_parser.cpp index 8b938405cc09..2c3659644f95 100644 --- a/ogr/swq_parser.cpp +++ b/ogr/swq_parser.cpp @@ -63,12 +63,13 @@ /* Pull parsers. */ #define YYPULL 1 + /* Substitute the variable and function names. */ -#define yyparse swqparse -#define yylex swqlex -#define yyerror swqerror -#define yydebug swqdebug -#define yynerrs swqnerrs +#define yyparse swqparse +#define yylex swqlex +#define yyerror swqerror +#define yydebug swqdebug +#define yynerrs swqnerrs /* First part of user prologue. */ @@ -114,6 +115,7 @@ #include "ogr_core.h" #include "ogr_geometry.h" + #define YYSTYPE swq_expr_node * /* Defining YYSTYPE_IS_TRIVIAL is needed because the parser is generated as a C++ file. */ @@ -123,116 +125,122 @@ /* it appears to be a non documented feature of Bison */ #define YYSTYPE_IS_TRIVIAL 1 -#ifndef YY_CAST -#ifdef __cplusplus -#define YY_CAST(Type, Val) static_cast(Val) -#define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast(Val) -#else -#define YY_CAST(Type, Val) ((Type)(Val)) -#define YY_REINTERPRET_CAST(Type, Val) ((Type)(Val)) -#endif -#endif -#ifndef YY_NULLPTR -#if defined __cplusplus -#if 201103L <= __cplusplus -#define YY_NULLPTR nullptr -#else -#define YY_NULLPTR 0 -#endif -#else -#define YY_NULLPTR ((void *)0) -#endif -#endif -#include "swq_parser.hpp" +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (Val) +# else +# define YY_CAST(Type, Val) ((Type) (Val)) +# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) +# endif +# endif +# ifndef YY_NULLPTR +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# else +# define YY_NULLPTR ((void*)0) +# endif +# endif +#include "swq_parser.hpp" /* Symbol kind. */ enum yysymbol_kind_t { - YYSYMBOL_YYEMPTY = -2, - YYSYMBOL_YYEOF = 0, /* "end of string" */ - YYSYMBOL_YYerror = 1, /* error */ - YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ - YYSYMBOL_SWQT_INTEGER_NUMBER = 3, /* "integer number" */ - YYSYMBOL_SWQT_FLOAT_NUMBER = 4, /* "floating point number" */ - YYSYMBOL_SWQT_STRING = 5, /* "string" */ - YYSYMBOL_SWQT_IDENTIFIER = 6, /* "identifier" */ - YYSYMBOL_SWQT_IN = 7, /* "IN" */ - YYSYMBOL_SWQT_LIKE = 8, /* "LIKE" */ - YYSYMBOL_SWQT_ILIKE = 9, /* "ILIKE" */ - YYSYMBOL_SWQT_ESCAPE = 10, /* "ESCAPE" */ - YYSYMBOL_SWQT_BETWEEN = 11, /* "BETWEEN" */ - YYSYMBOL_SWQT_NULL = 12, /* "NULL" */ - YYSYMBOL_SWQT_IS = 13, /* "IS" */ - YYSYMBOL_SWQT_SELECT = 14, /* "SELECT" */ - YYSYMBOL_SWQT_LEFT = 15, /* "LEFT" */ - YYSYMBOL_SWQT_JOIN = 16, /* "JOIN" */ - YYSYMBOL_SWQT_WHERE = 17, /* "WHERE" */ - YYSYMBOL_SWQT_ON = 18, /* "ON" */ - YYSYMBOL_SWQT_ORDER = 19, /* "ORDER" */ - YYSYMBOL_SWQT_BY = 20, /* "BY" */ - YYSYMBOL_SWQT_FROM = 21, /* "FROM" */ - YYSYMBOL_SWQT_AS = 22, /* "AS" */ - YYSYMBOL_SWQT_ASC = 23, /* "ASC" */ - YYSYMBOL_SWQT_DESC = 24, /* "DESC" */ - YYSYMBOL_SWQT_DISTINCT = 25, /* "DISTINCT" */ - YYSYMBOL_SWQT_CAST = 26, /* "CAST" */ - YYSYMBOL_SWQT_UNION = 27, /* "UNION" */ - YYSYMBOL_SWQT_ALL = 28, /* "ALL" */ - YYSYMBOL_SWQT_LIMIT = 29, /* "LIMIT" */ - YYSYMBOL_SWQT_OFFSET = 30, /* "OFFSET" */ - YYSYMBOL_SWQT_EXCEPT = 31, /* "EXCEPT" */ - YYSYMBOL_SWQT_EXCLUDE = 32, /* "EXCLUDE" */ - YYSYMBOL_SWQT_VALUE_START = 33, /* SWQT_VALUE_START */ - YYSYMBOL_SWQT_SELECT_START = 34, /* SWQT_SELECT_START */ - YYSYMBOL_SWQT_NOT = 35, /* "NOT" */ - YYSYMBOL_SWQT_OR = 36, /* "OR" */ - YYSYMBOL_SWQT_AND = 37, /* "AND" */ - YYSYMBOL_38_ = 38, /* '=' */ - YYSYMBOL_39_ = 39, /* '<' */ - YYSYMBOL_40_ = 40, /* '>' */ - YYSYMBOL_41_ = 41, /* '!' */ - YYSYMBOL_42_ = 42, /* '+' */ - YYSYMBOL_43_ = 43, /* '-' */ - YYSYMBOL_44_ = 44, /* '*' */ - YYSYMBOL_45_ = 45, /* '/' */ - YYSYMBOL_46_ = 46, /* '%' */ - YYSYMBOL_SWQT_UMINUS = 47, /* SWQT_UMINUS */ - YYSYMBOL_SWQT_RESERVED_KEYWORD = 48, /* "reserved keyword" */ - YYSYMBOL_49_ = 49, /* '(' */ - YYSYMBOL_50_ = 50, /* ')' */ - YYSYMBOL_51_ = 51, /* ',' */ - YYSYMBOL_52_ = 52, /* '.' */ - YYSYMBOL_YYACCEPT = 53, /* $accept */ - YYSYMBOL_input = 54, /* input */ - YYSYMBOL_value_expr = 55, /* value_expr */ - YYSYMBOL_value_expr_list = 56, /* value_expr_list */ - YYSYMBOL_field_value = 57, /* field_value */ - YYSYMBOL_value_expr_non_logical = 58, /* value_expr_non_logical */ - YYSYMBOL_type_def = 59, /* type_def */ - YYSYMBOL_select_statement = 60, /* select_statement */ - YYSYMBOL_select_core = 61, /* select_core */ - YYSYMBOL_opt_union_all = 62, /* opt_union_all */ - YYSYMBOL_union_all = 63, /* union_all */ - YYSYMBOL_select_field_list = 64, /* select_field_list */ - YYSYMBOL_exclude_field = 65, /* exclude_field */ - YYSYMBOL_exclude_field_list = 66, /* exclude_field_list */ - YYSYMBOL_except_or_exclude = 67, /* except_or_exclude */ - YYSYMBOL_column_spec = 68, /* column_spec */ - YYSYMBOL_as_clause = 69, /* as_clause */ - YYSYMBOL_opt_where = 70, /* opt_where */ - YYSYMBOL_opt_joins = 71, /* opt_joins */ - YYSYMBOL_opt_order_by = 72, /* opt_order_by */ - YYSYMBOL_sort_spec_list = 73, /* sort_spec_list */ - YYSYMBOL_sort_spec = 74, /* sort_spec */ - YYSYMBOL_opt_limit = 75, /* opt_limit */ - YYSYMBOL_opt_offset = 76, /* opt_offset */ - YYSYMBOL_table_def = 77 /* table_def */ + YYSYMBOL_YYEMPTY = -2, + YYSYMBOL_YYEOF = 0, /* "end of string" */ + YYSYMBOL_YYerror = 1, /* error */ + YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ + YYSYMBOL_SWQT_INTEGER_NUMBER = 3, /* "integer number" */ + YYSYMBOL_SWQT_FLOAT_NUMBER = 4, /* "floating point number" */ + YYSYMBOL_SWQT_STRING = 5, /* "string" */ + YYSYMBOL_SWQT_IDENTIFIER = 6, /* "identifier" */ + YYSYMBOL_SWQT_IN = 7, /* "IN" */ + YYSYMBOL_SWQT_LIKE = 8, /* "LIKE" */ + YYSYMBOL_SWQT_ILIKE = 9, /* "ILIKE" */ + YYSYMBOL_SWQT_ESCAPE = 10, /* "ESCAPE" */ + YYSYMBOL_SWQT_BETWEEN = 11, /* "BETWEEN" */ + YYSYMBOL_SWQT_NULL = 12, /* "NULL" */ + YYSYMBOL_SWQT_IS = 13, /* "IS" */ + YYSYMBOL_SWQT_SELECT = 14, /* "SELECT" */ + YYSYMBOL_SWQT_LEFT = 15, /* "LEFT" */ + YYSYMBOL_SWQT_JOIN = 16, /* "JOIN" */ + YYSYMBOL_SWQT_WHERE = 17, /* "WHERE" */ + YYSYMBOL_SWQT_ON = 18, /* "ON" */ + YYSYMBOL_SWQT_ORDER = 19, /* "ORDER" */ + YYSYMBOL_SWQT_BY = 20, /* "BY" */ + YYSYMBOL_SWQT_FROM = 21, /* "FROM" */ + YYSYMBOL_SWQT_AS = 22, /* "AS" */ + YYSYMBOL_SWQT_ASC = 23, /* "ASC" */ + YYSYMBOL_SWQT_DESC = 24, /* "DESC" */ + YYSYMBOL_SWQT_DISTINCT = 25, /* "DISTINCT" */ + YYSYMBOL_SWQT_CAST = 26, /* "CAST" */ + YYSYMBOL_SWQT_UNION = 27, /* "UNION" */ + YYSYMBOL_SWQT_ALL = 28, /* "ALL" */ + YYSYMBOL_SWQT_LIMIT = 29, /* "LIMIT" */ + YYSYMBOL_SWQT_OFFSET = 30, /* "OFFSET" */ + YYSYMBOL_SWQT_EXCEPT = 31, /* "EXCEPT" */ + YYSYMBOL_SWQT_EXCLUDE = 32, /* "EXCLUDE" */ + YYSYMBOL_SWQT_HIDDEN = 33, /* "HIDDEN" */ + YYSYMBOL_SWQT_VALUE_START = 34, /* SWQT_VALUE_START */ + YYSYMBOL_SWQT_SELECT_START = 35, /* SWQT_SELECT_START */ + YYSYMBOL_SWQT_NOT = 36, /* "NOT" */ + YYSYMBOL_SWQT_OR = 37, /* "OR" */ + YYSYMBOL_SWQT_AND = 38, /* "AND" */ + YYSYMBOL_39_ = 39, /* '=' */ + YYSYMBOL_40_ = 40, /* '<' */ + YYSYMBOL_41_ = 41, /* '>' */ + YYSYMBOL_42_ = 42, /* '!' */ + YYSYMBOL_43_ = 43, /* '+' */ + YYSYMBOL_44_ = 44, /* '-' */ + YYSYMBOL_45_ = 45, /* '*' */ + YYSYMBOL_46_ = 46, /* '/' */ + YYSYMBOL_47_ = 47, /* '%' */ + YYSYMBOL_SWQT_UMINUS = 48, /* SWQT_UMINUS */ + YYSYMBOL_SWQT_RESERVED_KEYWORD = 49, /* "reserved keyword" */ + YYSYMBOL_50_ = 50, /* '(' */ + YYSYMBOL_51_ = 51, /* ')' */ + YYSYMBOL_52_ = 52, /* ',' */ + YYSYMBOL_53_ = 53, /* '.' */ + YYSYMBOL_YYACCEPT = 54, /* $accept */ + YYSYMBOL_input = 55, /* input */ + YYSYMBOL_value_expr = 56, /* value_expr */ + YYSYMBOL_value_expr_list = 57, /* value_expr_list */ + YYSYMBOL_identifier = 58, /* identifier */ + YYSYMBOL_field_value = 59, /* field_value */ + YYSYMBOL_value_expr_non_logical = 60, /* value_expr_non_logical */ + YYSYMBOL_type_def = 61, /* type_def */ + YYSYMBOL_select_statement = 62, /* select_statement */ + YYSYMBOL_select_core = 63, /* select_core */ + YYSYMBOL_opt_union_all = 64, /* opt_union_all */ + YYSYMBOL_union_all = 65, /* union_all */ + YYSYMBOL_select_field_list = 66, /* select_field_list */ + YYSYMBOL_exclude_field = 67, /* exclude_field */ + YYSYMBOL_exclude_field_list = 68, /* exclude_field_list */ + YYSYMBOL_except_or_exclude = 69, /* except_or_exclude */ + YYSYMBOL_column_spec = 70, /* column_spec */ + YYSYMBOL_as_clause = 71, /* as_clause */ + YYSYMBOL_as_clause_with_hidden = 72, /* as_clause_with_hidden */ + YYSYMBOL_opt_where = 73, /* opt_where */ + YYSYMBOL_opt_joins = 74, /* opt_joins */ + YYSYMBOL_opt_order_by = 75, /* opt_order_by */ + YYSYMBOL_sort_spec_list = 76, /* sort_spec_list */ + YYSYMBOL_sort_spec = 77, /* sort_spec */ + YYSYMBOL_opt_limit = 78, /* opt_limit */ + YYSYMBOL_opt_offset = 79, /* opt_offset */ + YYSYMBOL_table_def = 80 /* table_def */ }; typedef enum yysymbol_kind_t yysymbol_kind_t; + + + #ifdef short -#undef short +# undef short #endif /* On compilers that do not define __PTRDIFF_MAX__ etc., make sure @@ -240,11 +248,11 @@ typedef enum yysymbol_kind_t yysymbol_kind_t; so that the code can choose integer types of a good width. */ #ifndef __PTRDIFF_MAX__ -#include /* INFRINGES ON USER NAME SPACE */ -#if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ -#include /* INFRINGES ON USER NAME SPACE */ -#define YY_STDINT_H -#endif +# include /* INFRINGES ON USER NAME SPACE */ +# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_STDINT_H +# endif #endif /* Narrow types that promote to a signed type and that can represent a @@ -274,16 +282,16 @@ typedef short yytype_int16; (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of . */ #ifdef __hpux -#undef UINT_LEAST8_MAX -#undef UINT_LEAST16_MAX -#define UINT_LEAST8_MAX 255 -#define UINT_LEAST16_MAX 65535 +# undef UINT_LEAST8_MAX +# undef UINT_LEAST16_MAX +# define UINT_LEAST8_MAX 255 +# define UINT_LEAST16_MAX 65535 #endif #if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ typedef __UINT_LEAST8_TYPE__ yytype_uint8; -#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H && \ - UINT_LEAST8_MAX <= INT_MAX) +#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST8_MAX <= INT_MAX) typedef uint_least8_t yytype_uint8; #elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX typedef unsigned char yytype_uint8; @@ -293,8 +301,8 @@ typedef short yytype_uint8; #if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ typedef __UINT_LEAST16_TYPE__ yytype_uint16; -#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H && \ - UINT_LEAST16_MAX <= INT_MAX) +#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST16_MAX <= INT_MAX) typedef uint_least16_t yytype_uint16; #elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX typedef unsigned short yytype_uint16; @@ -303,40 +311,42 @@ typedef int yytype_uint16; #endif #ifndef YYPTRDIFF_T -#if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ -#define YYPTRDIFF_T __PTRDIFF_TYPE__ -#define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ -#elif defined PTRDIFF_MAX -#ifndef ptrdiff_t -#include /* INFRINGES ON USER NAME SPACE */ -#endif -#define YYPTRDIFF_T ptrdiff_t -#define YYPTRDIFF_MAXIMUM PTRDIFF_MAX -#else -#define YYPTRDIFF_T long -#define YYPTRDIFF_MAXIMUM LONG_MAX -#endif +# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ +# define YYPTRDIFF_T __PTRDIFF_TYPE__ +# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ +# elif defined PTRDIFF_MAX +# ifndef ptrdiff_t +# include /* INFRINGES ON USER NAME SPACE */ +# endif +# define YYPTRDIFF_T ptrdiff_t +# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX +# else +# define YYPTRDIFF_T long +# define YYPTRDIFF_MAXIMUM LONG_MAX +# endif #endif #ifndef YYSIZE_T -#ifdef __SIZE_TYPE__ -#define YYSIZE_T __SIZE_TYPE__ -#elif defined size_t -#define YYSIZE_T size_t -#elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ -#include /* INFRINGES ON USER NAME SPACE */ -#define YYSIZE_T size_t -#else -#define YYSIZE_T unsigned -#endif +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned +# endif #endif -#define YYSIZE_MAXIMUM \ - YY_CAST(YYPTRDIFF_T, (YYPTRDIFF_MAXIMUM < YY_CAST(YYSIZE_T, -1) \ - ? YYPTRDIFF_MAXIMUM \ - : YY_CAST(YYSIZE_T, -1))) +#define YYSIZE_MAXIMUM \ + YY_CAST (YYPTRDIFF_T, \ + (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ + ? YYPTRDIFF_MAXIMUM \ + : YY_CAST (YYSIZE_T, -1))) + +#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) -#define YYSIZEOF(X) YY_CAST(YYPTRDIFF_T, sizeof(X)) /* Stored state numbers (used for stacks). */ typedef yytype_uint8 yy_state_t; @@ -345,592 +355,670 @@ typedef yytype_uint8 yy_state_t; typedef int yy_state_fast_t; #ifndef YY_ -#if defined YYENABLE_NLS && YYENABLE_NLS -#if ENABLE_NLS -#include /* INFRINGES ON USER NAME SPACE */ -#define YY_(Msgid) dgettext("bison-runtime", Msgid) -#endif -#endif -#ifndef YY_ -#define YY_(Msgid) Msgid -#endif +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) +# endif +# endif +# ifndef YY_ +# define YY_(Msgid) Msgid +# endif #endif + #ifndef YY_ATTRIBUTE_PURE -#if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) -#define YY_ATTRIBUTE_PURE __attribute__((__pure__)) -#else -#define YY_ATTRIBUTE_PURE -#endif +# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) +# else +# define YY_ATTRIBUTE_PURE +# endif #endif #ifndef YY_ATTRIBUTE_UNUSED -#if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) -#define YY_ATTRIBUTE_UNUSED __attribute__((__unused__)) -#else -#define YY_ATTRIBUTE_UNUSED -#endif +# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +# else +# define YY_ATTRIBUTE_UNUSED +# endif #endif /* Suppress unused-variable warnings by "using" E. */ -#if !defined lint || defined __GNUC__ -#define YY_USE(E) ((void)(E)) +#if ! defined lint || defined __GNUC__ +# define YY_USE(E) ((void) (E)) #else -#define YY_USE(E) /* empty */ +# define YY_USE(E) /* empty */ #endif /* Suppress an incorrect diagnostic about yylval being uninitialized. */ -#if defined __GNUC__ && !defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__ -#if __GNUC__ * 100 + __GNUC_MINOR__ < 407 -#define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ - _Pragma("GCC diagnostic push") \ - _Pragma("GCC diagnostic ignored \"-Wuninitialized\"") -#else -#define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ - _Pragma("GCC diagnostic push") \ - _Pragma("GCC diagnostic ignored \"-Wuninitialized\"") \ - _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") -#endif -#define YY_IGNORE_MAYBE_UNINITIALIZED_END _Pragma("GCC diagnostic pop") +#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__ +# if __GNUC__ * 100 + __GNUC_MINOR__ < 407 +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") +# else +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# endif +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") #else -#define YY_INITIAL_VALUE(Value) Value +# define YY_INITIAL_VALUE(Value) Value #endif #ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN -#define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN -#define YY_IGNORE_MAYBE_UNINITIALIZED_END +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END #endif #ifndef YY_INITIAL_VALUE -#define YY_INITIAL_VALUE(Value) /* Nothing. */ +# define YY_INITIAL_VALUE(Value) /* Nothing. */ #endif -#if defined __cplusplus && defined __GNUC__ && !defined __ICC && 6 <= __GNUC__ -#define YY_IGNORE_USELESS_CAST_BEGIN \ - _Pragma("GCC diagnostic push") \ - _Pragma("GCC diagnostic ignored \"-Wuseless-cast\"") -#define YY_IGNORE_USELESS_CAST_END _Pragma("GCC diagnostic pop") +#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ +# define YY_IGNORE_USELESS_CAST_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") +# define YY_IGNORE_USELESS_CAST_END \ + _Pragma ("GCC diagnostic pop") #endif #ifndef YY_IGNORE_USELESS_CAST_BEGIN -#define YY_IGNORE_USELESS_CAST_BEGIN -#define YY_IGNORE_USELESS_CAST_END +# define YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_END #endif -#define YY_ASSERT(E) ((void)(0 && (E))) + +#define YY_ASSERT(E) ((void) (0 && (E))) #if 1 /* The parser invokes alloca or malloc; define the necessary symbols. */ -#ifdef YYSTACK_USE_ALLOCA -#if YYSTACK_USE_ALLOCA -#ifdef __GNUC__ -#define YYSTACK_ALLOC __builtin_alloca -#elif defined __BUILTIN_VA_ARG_INCR -#include /* INFRINGES ON USER NAME SPACE */ -#elif defined _AIX -#define YYSTACK_ALLOC __alloca -#elif defined _MSC_VER -#include /* INFRINGES ON USER NAME SPACE */ -#define alloca _alloca -#else -#define YYSTACK_ALLOC alloca -#if !defined _ALLOCA_H && !defined EXIT_SUCCESS -#include /* INFRINGES ON USER NAME SPACE */ -/* Use EXIT_SUCCESS as a witness for stdlib.h. */ -#ifndef EXIT_SUCCESS -#define EXIT_SUCCESS 0 -#endif -#endif -#endif -#endif -#endif - -#ifdef YYSTACK_ALLOC -/* Pacify GCC's 'empty if-body' warning. */ -#define YYSTACK_FREE(Ptr) \ - do \ - { /* empty */ \ - ; \ - } while (0) -#ifndef YYSTACK_ALLOC_MAXIMUM -/* The OS might guarantee only one guard page at the bottom of the stack, +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include /* INFRINGES ON USER NAME SPACE */ + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, and a page size can be as small as 4096 bytes. So we cannot safely invoke alloca (N) if N exceeds 4096. Use a slightly smaller number to allow for a few compiler-allocated temporary stack slots. */ -#define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ -#endif -#else -#define YYSTACK_ALLOC YYMALLOC -#define YYSTACK_FREE YYFREE -#ifndef YYSTACK_ALLOC_MAXIMUM -#define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM -#endif -#if (defined __cplusplus && !defined EXIT_SUCCESS && \ - !((defined YYMALLOC || defined malloc) && \ - (defined YYFREE || defined free))) -#include /* INFRINGES ON USER NAME SPACE */ -#ifndef EXIT_SUCCESS -#define EXIT_SUCCESS 0 -#endif -#endif -#ifndef YYMALLOC -#define YYMALLOC malloc -#if !defined malloc && !defined EXIT_SUCCESS -void *malloc(YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ -#endif -#endif -#ifndef YYFREE -#define YYFREE free -#if !defined free && !defined EXIT_SUCCESS -void free(void *); /* INFRINGES ON USER NAME SPACE */ -#endif -#endif -#endif +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif #endif /* 1 */ -#if (!defined yyoverflow && \ - (!defined __cplusplus || \ - (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) /* A type that is properly aligned for any stack member. */ union yyalloc { - yy_state_t yyss_alloc; - YYSTYPE yyvs_alloc; + yy_state_t yyss_alloc; + YYSTYPE yyvs_alloc; }; /* The size of the maximum gap between one aligned stack and the next. */ -#define YYSTACK_GAP_MAXIMUM (YYSIZEOF(union yyalloc) - 1) +# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) /* The size of an array large to enough to hold all stacks, each with N elements. */ -#define YYSTACK_BYTES(N) \ - ((N) * (YYSIZEOF(yy_state_t) + YYSIZEOF(YYSTYPE)) + YYSTACK_GAP_MAXIMUM) +# define YYSTACK_BYTES(N) \ + ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) -#define YYCOPY_NEEDED 1 +# define YYCOPY_NEEDED 1 /* Relocate STACK from its old location to the new one. The local variables YYSIZE and YYSTACKSIZE give the old and new number of elements in the stack, and YYPTR gives the new location of the stack. Advance YYPTR to a properly aligned location for the next stack. */ -#define YYSTACK_RELOCATE(Stack_alloc, Stack) \ - do \ - { \ - YYPTRDIFF_T yynewbytes; \ - YYCOPY(&yyptr->Stack_alloc, Stack, yysize); \ - Stack = &yyptr->Stack_alloc; \ - yynewbytes = yystacksize * YYSIZEOF(*Stack) + YYSTACK_GAP_MAXIMUM; \ - yyptr += yynewbytes / YYSIZEOF(*yyptr); \ - } while (0) +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYPTRDIFF_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / YYSIZEOF (*yyptr); \ + } \ + while (0) #endif #if defined YYCOPY_NEEDED && YYCOPY_NEEDED /* Copy COUNT objects from SRC to DST. The source and destination do not overlap. */ -#ifndef YYCOPY -#if defined __GNUC__ && 1 < __GNUC__ -#define YYCOPY(Dst, Src, Count) \ - __builtin_memcpy(Dst, Src, YY_CAST(YYSIZE_T, (Count)) * sizeof(*(Src))) -#else -#define YYCOPY(Dst, Src, Count) \ - do \ - { \ - YYPTRDIFF_T yyi; \ - for (yyi = 0; yyi < (Count); yyi++) \ - (Dst)[yyi] = (Src)[yyi]; \ - } while (0) -#endif -#endif +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYPTRDIFF_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif #endif /* !YYCOPY_NEEDED */ /* YYFINAL -- State number of the termination state. */ -#define YYFINAL 20 +#define YYFINAL 22 /* YYLAST -- Last index in YYTABLE. */ -#define YYLAST 409 +#define YYLAST 469 /* YYNTOKENS -- Number of terminals. */ -#define YYNTOKENS 53 +#define YYNTOKENS 54 /* YYNNTS -- Number of nonterminals. */ -#define YYNNTS 25 +#define YYNNTS 27 /* YYNRULES -- Number of rules. */ -#define YYNRULES 101 +#define YYNRULES 105 /* YYNSTATES -- Number of states. */ -#define YYNSTATES 211 +#define YYNSTATES 215 /* YYMAXUTOK -- Last valid token kind. */ -#define YYMAXUTOK 294 +#define YYMAXUTOK 295 + /* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM as returned by yylex, with out-of-bounds checking. */ -#define YYTRANSLATE(YYX) \ - (0 <= (YYX) && (YYX) <= YYMAXUTOK \ - ? YY_CAST(yysymbol_kind_t, yytranslate[YYX]) \ - : YYSYMBOL_YYUNDEF) +#define YYTRANSLATE(YYX) \ + (0 <= (YYX) && (YYX) <= YYMAXUTOK \ + ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \ + : YYSYMBOL_YYUNDEF) /* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM as returned by yylex. */ -static const yytype_int8 yytranslate[] = { - 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 41, 2, 2, 2, 46, - 2, 2, 49, 50, 44, 42, 51, 43, 52, 45, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 39, 38, 40, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, - 30, 31, 32, 33, 34, 35, 36, 37, 47, 48}; +static const yytype_int8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 42, 2, 2, 2, 47, 2, 2, + 50, 51, 45, 43, 52, 44, 53, 46, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 40, 39, 41, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 48, 49 +}; #if YYDEBUG /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ -static const yytype_int16 yyrline[] = { - 0, 124, 124, 125, 131, 138, 143, 148, 153, 160, 168, 176, 184, 192, 200, - 208, 216, 224, 232, 240, 252, 261, 274, 282, 294, 303, 316, 325, 338, 347, - 360, 367, 379, 385, 392, 400, 413, 418, 423, 427, 432, 437, 442, 477, 484, - 491, 498, 505, 512, 548, 556, 562, 569, 578, 596, 616, 617, 620, 625, 631, - 632, 634, 642, 643, 646, 656, 657, 660, 661, 664, 673, 684, 699, 714, 735, - 766, 801, 826, 855, 861, 863, 864, 869, 870, 876, 883, 884, 887, 888, 891, - 897, 903, 910, 911, 918, 919, 927, 937, 948, 959, 972, 983}; +static const yytype_int16 yyrline[] = +{ + 0, 125, 125, 126, 132, 139, 144, 149, 154, 161, + 169, 177, 185, 193, 201, 209, 217, 225, 233, 241, + 253, 262, 275, 283, 295, 304, 317, 326, 339, 348, + 361, 368, 380, 386, 393, 395, 398, 406, 419, 424, + 429, 433, 438, 443, 448, 483, 490, 497, 504, 511, + 518, 554, 562, 568, 575, 584, 602, 622, 623, 626, + 631, 637, 638, 640, 648, 649, 652, 662, 663, 666, + 667, 670, 679, 690, 705, 720, 741, 772, 807, 832, + 861, 867, 870, 872, 881, 882, 887, 888, 894, 901, + 902, 905, 906, 909, 915, 921, 928, 929, 936, 937, + 945, 955, 966, 977, 990, 1001 +}; #endif /** Accessing symbol of state STATE. */ -#define YY_ACCESSING_SYMBOL(State) YY_CAST(yysymbol_kind_t, yystos[State]) +#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) #if 1 +/* The user-facing name of the symbol whose (internal) number is + YYSYMBOL. No bounds checking. */ +static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; + /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. First, the terminals, then, starting at YYNTOKENS, nonterminals. */ -static const char *const yytname[] = {"\"end of string\"", - "error", - "\"invalid token\"", - "\"integer number\"", - "\"floating point number\"", - "\"string\"", - "\"identifier\"", - "\"IN\"", - "\"LIKE\"", - "\"ILIKE\"", - "\"ESCAPE\"", - "\"BETWEEN\"", - "\"NULL\"", - "\"IS\"", - "\"SELECT\"", - "\"LEFT\"", - "\"JOIN\"", - "\"WHERE\"", - "\"ON\"", - "\"ORDER\"", - "\"BY\"", - "\"FROM\"", - "\"AS\"", - "\"ASC\"", - "\"DESC\"", - "\"DISTINCT\"", - "\"CAST\"", - "\"UNION\"", - "\"ALL\"", - "\"LIMIT\"", - "\"OFFSET\"", - "\"EXCEPT\"", - "\"EXCLUDE\"", - "SWQT_VALUE_START", - "SWQT_SELECT_START", - "\"NOT\"", - "\"OR\"", - "\"AND\"", - "'='", - "'<'", - "'>'", - "'!'", - "'+'", - "'-'", - "'*'", - "'/'", - "'%'", - "SWQT_UMINUS", - "\"reserved keyword\"", - "'('", - "')'", - "','", - "'.'", - "$accept", - "input", - "value_expr", - "value_expr_list", - "field_value", - "value_expr_non_logical", - "type_def", - "select_statement", - "select_core", - "opt_union_all", - "union_all", - "select_field_list", - "exclude_field", - "exclude_field_list", - "except_or_exclude", - "column_spec", - "as_clause", - "opt_where", - "opt_joins", - "opt_order_by", - "sort_spec_list", - "sort_spec", - "opt_limit", - "opt_offset", - "table_def", - YY_NULLPTR}; +static const char *const yytname[] = +{ + "\"end of string\"", "error", "\"invalid token\"", "\"integer number\"", + "\"floating point number\"", "\"string\"", "\"identifier\"", "\"IN\"", + "\"LIKE\"", "\"ILIKE\"", "\"ESCAPE\"", "\"BETWEEN\"", "\"NULL\"", + "\"IS\"", "\"SELECT\"", "\"LEFT\"", "\"JOIN\"", "\"WHERE\"", "\"ON\"", + "\"ORDER\"", "\"BY\"", "\"FROM\"", "\"AS\"", "\"ASC\"", "\"DESC\"", + "\"DISTINCT\"", "\"CAST\"", "\"UNION\"", "\"ALL\"", "\"LIMIT\"", + "\"OFFSET\"", "\"EXCEPT\"", "\"EXCLUDE\"", "\"HIDDEN\"", + "SWQT_VALUE_START", "SWQT_SELECT_START", "\"NOT\"", "\"OR\"", "\"AND\"", + "'='", "'<'", "'>'", "'!'", "'+'", "'-'", "'*'", "'/'", "'%'", + "SWQT_UMINUS", "\"reserved keyword\"", "'('", "')'", "','", "'.'", + "$accept", "input", "value_expr", "value_expr_list", "identifier", + "field_value", "value_expr_non_logical", "type_def", "select_statement", + "select_core", "opt_union_all", "union_all", "select_field_list", + "exclude_field", "exclude_field_list", "except_or_exclude", + "column_spec", "as_clause", "as_clause_with_hidden", "opt_where", + "opt_joins", "opt_order_by", "sort_spec_list", "sort_spec", "opt_limit", + "opt_offset", "table_def", YY_NULLPTR +}; + +static const char * +yysymbol_name (yysymbol_kind_t yysymbol) +{ + return yytname[yysymbol]; +} #endif -#define YYPACT_NINF (-137) +#define YYPACT_NINF (-136) -#define yypact_value_is_default(Yyn) ((Yyn) == YYPACT_NINF) +#define yypact_value_is_default(Yyn) \ + ((Yyn) == YYPACT_NINF) #define YYTABLE_NINF (-1) -#define yytable_value_is_error(Yyn) 0 +#define yytable_value_is_error(Yyn) \ + 0 /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ -static const yytype_int16 yypact[] = { - 34, 206, -10, 16, -137, -137, -137, -35, -137, -37, 206, 211, - 206, 326, -137, 298, 79, 11, -137, 4, -137, 206, 23, 206, - 368, -137, 254, -11, 206, 206, 211, -5, 12, 206, 206, 94, - 104, 155, 5, 211, 211, 211, 211, 211, 8, 196, 93, 274, - 48, 26, 30, 65, -137, -10, 235, 45, -137, 310, -137, 206, - 91, 101, 44, -137, 103, 68, 206, 206, 211, 345, 361, 206, - 206, -137, 206, 206, -137, 206, -137, 206, 18, 18, -137, -137, - -137, 145, -3, 100, -137, -137, 89, -137, 140, -137, 121, 196, - 4, -137, -137, 206, -137, 146, 114, 206, 206, 211, -137, 206, - 144, 158, 182, -137, -137, -137, -137, -137, -137, 159, 119, -137, - 121, 159, -137, 120, 2, 116, -137, -137, -137, 124, 125, -137, - -137, -137, 298, 127, 206, 206, 211, 122, 128, 20, 116, -137, - 131, 129, 177, 178, -137, 170, 121, 174, 53, -137, -137, -137, - -137, 298, 20, -137, 174, 159, -137, 20, 20, 121, 169, 206, - 173, 90, 105, -137, 173, -137, -137, -137, 179, 206, 326, 175, - 167, -137, 200, -137, 202, 167, 206, 290, 159, 203, 183, 157, - 171, 183, 290, -137, 139, -137, 184, -137, 217, -137, -137, -137, - -137, -137, -137, -137, 159, -137, -137}; +static const yytype_int16 yypact[] = +{ + 66, 93, -13, 11, -136, -136, -136, -136, -136, -24, + -136, 93, 292, 93, 413, -34, -136, 174, 136, 21, + -136, 30, -136, 93, 427, -136, 315, -9, 93, 93, + 292, 2, 116, 93, 93, 172, 197, 239, 25, 93, + 0, 292, 292, 292, 292, 292, 258, 84, 356, -32, + 27, 19, 23, 48, -136, -13, 377, -136, 93, 73, + 75, 145, -136, 92, 57, 93, 93, 292, 420, 298, + 93, 93, -136, 93, 93, -136, 93, -136, 93, 308, + 62, -136, -22, -22, -136, -136, -136, 107, -136, -136, + 72, 0, -136, 111, -136, 223, 14, 7, 258, 30, + -136, -136, 0, 95, 93, 93, 292, -136, 93, 144, + 149, 399, -136, -136, -136, -136, -136, -136, 93, -136, + 7, 0, -136, -136, 0, 109, -136, 110, 55, 102, + -136, -136, 114, 115, -136, -136, -136, 174, 117, 93, + 93, 292, -136, 102, 118, -136, 121, 119, 128, 9, + 0, 0, -136, 151, 7, 165, -1, -136, -136, -136, + -136, 174, 165, 0, -136, 9, -136, 9, 9, 7, + 167, 93, 176, 83, 101, 176, -136, -136, -136, -136, + 175, 93, 413, 179, 178, -136, 193, -136, 201, 178, + 93, 364, 0, 207, 181, 163, 164, 181, 364, -136, + 134, -136, 173, -136, 221, -136, -136, -136, -136, -136, + -136, -136, 0, -136, -136 +}; /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. Performed when YYTABLE does not specify something else to do. Zero means the default is an error. */ -static const yytype_int8 yydefact[] = { - 2, 0, 0, 0, 36, 37, 38, 34, 41, 0, 0, 0, 0, 3, 39, 5, 0, 0, - 4, 59, 1, 0, 0, 0, 8, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 72, 69, 0, 62, 0, 0, 55, 0, - 33, 0, 35, 0, 40, 0, 18, 22, 0, 30, 0, 0, 0, 0, 0, 7, 6, 0, - 0, 9, 0, 0, 12, 0, 13, 0, 43, 44, 45, 46, 47, 0, 0, 0, 67, 68, - 0, 79, 0, 70, 0, 0, 59, 61, 60, 0, 48, 0, 0, 0, 0, 0, 31, 0, - 19, 23, 0, 15, 16, 14, 10, 17, 11, 0, 0, 73, 0, 0, 78, 0, 96, 82, - 63, 56, 32, 50, 0, 26, 20, 24, 28, 0, 0, 0, 0, 34, 0, 74, 82, 64, - 65, 0, 0, 0, 97, 0, 0, 80, 0, 49, 27, 21, 25, 29, 76, 75, 80, 0, - 71, 98, 100, 0, 0, 0, 85, 0, 0, 77, 85, 66, 99, 101, 0, 0, 81, 0, - 92, 51, 0, 53, 0, 92, 0, 82, 0, 0, 94, 0, 0, 94, 82, 83, 89, 86, - 88, 93, 0, 57, 52, 54, 58, 84, 90, 91, 0, 95, 87}; +static const yytype_int8 yydefact[] = +{ + 2, 0, 0, 0, 38, 39, 40, 34, 43, 0, + 35, 0, 0, 0, 3, 36, 41, 5, 0, 0, + 4, 61, 1, 0, 8, 44, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 74, 71, 36, + 0, 64, 0, 0, 57, 0, 0, 42, 0, 18, + 22, 0, 30, 0, 0, 0, 0, 0, 7, 6, + 0, 0, 9, 0, 0, 12, 0, 13, 0, 33, + 0, 37, 45, 46, 47, 48, 49, 0, 69, 70, + 0, 0, 81, 82, 72, 0, 0, 0, 0, 61, + 63, 62, 0, 0, 0, 0, 0, 31, 0, 19, + 23, 0, 15, 16, 14, 10, 17, 11, 0, 50, + 0, 0, 80, 83, 0, 0, 75, 0, 100, 86, + 65, 58, 52, 0, 26, 20, 24, 28, 0, 0, + 0, 0, 32, 86, 36, 66, 67, 0, 0, 76, + 0, 0, 101, 0, 0, 84, 0, 51, 27, 21, + 25, 29, 84, 0, 73, 78, 77, 102, 104, 0, + 0, 0, 89, 0, 0, 89, 68, 79, 103, 105, + 0, 0, 85, 0, 96, 53, 0, 55, 0, 96, + 0, 86, 0, 0, 98, 0, 0, 98, 86, 87, + 93, 90, 92, 97, 0, 59, 54, 56, 60, 88, + 94, 95, 0, 99, 91 +}; /* YYPGOTO[NTERM-NUM]. */ -static const yytype_int16 yypgoto[] = { - -137, -137, -1, -46, -116, 7, -137, 176, 213, 137, -137, -43, -137, - 73, -137, -137, -45, 76, -136, 66, 39, -137, 67, 57, -110}; +static const yytype_int16 yypgoto[] = +{ + -136, -136, 16, -48, -18, -117, 24, -136, 177, 212, + 135, -136, -43, -136, 74, -136, -136, -56, -136, 77, + -135, 65, 34, -136, 61, 56, -111 +}; /* YYDEFGOTO[NTERM-NUM]. */ -static const yytype_uint8 yydefgoto[] = { - 0, 3, 54, 55, 14, 15, 130, 18, 19, 52, 53, 48, 144, - 145, 90, 49, 93, 168, 151, 180, 197, 198, 190, 201, 125}; +static const yytype_uint8 yydefgoto[] = +{ + 0, 3, 79, 80, 15, 16, 17, 133, 20, 21, + 54, 55, 50, 146, 147, 90, 51, 93, 94, 172, + 155, 184, 201, 202, 194, 205, 129 +}; /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If positive, shift that token. If negative, reduce the rule whose number is the opposite. If YYTABLE_NINF, syntax error. */ -static const yytype_uint8 yytable[] = { - 13, 140, 87, 56, 16, 143, 160, 63, 91, 24, 142, 26, 23, 102, 21, - 47, 20, 22, 25, 65, 66, 67, 57, 68, 92, 16, 91, 60, 61, 56, - 64, 51, 69, 70, 73, 76, 78, 62, 59, 17, 166, 119, 92, 79, 47, - 143, 80, 81, 82, 83, 84, 195, 126, 128, 147, 176, 169, 85, 205, 170, - 86, 135, 41, 42, 43, 108, 109, 1, 2, 94, 111, 112, 196, 113, 114, - 110, 115, 95, 116, 148, 96, 105, 4, 5, 6, 44, 39, 40, 41, 42, - 43, 8, 196, 97, 47, 100, 159, 4, 5, 6, 7, 103, 132, 133, 45, - 9, 8, 4, 5, 6, 7, 104, 134, 171, 10, 106, 8, 107, 174, 175, - 9, 120, 11, 46, 88, 89, 123, 124, 12, 10, 9, 149, 150, 71, 72, - 155, 156, 11, 121, 10, 181, 182, 74, 12, 75, 157, 122, 11, 4, 5, - 6, 7, 129, 12, 136, 183, 184, 8, 4, 5, 6, 7, 206, 207, 131, - 139, 178, 8, 137, 141, 117, 9, 146, 152, 22, 153, 187, 154, 158, 162, - 10, 9, 161, 163, 164, 194, 165, 177, 11, 118, 10, 167, 179, 77, 12, - 188, 189, 186, 11, 4, 5, 6, 44, 191, 12, 192, 199, 202, 8, 4, - 5, 6, 7, 200, 4, 5, 6, 7, 8, 138, 209, 203, 9, 8, 39, - 40, 41, 42, 43, 98, 50, 10, 9, 127, 173, 208, 172, 9, 185, 11, - 46, 10, 27, 28, 29, 12, 30, 210, 31, 11, 204, 0, 193, 0, 11, - 12, 0, 0, 0, 0, 12, 27, 28, 29, 0, 30, 0, 31, 0, 0, - 32, 33, 34, 35, 36, 37, 38, 0, 0, 0, 91, 27, 28, 29, 0, - 30, 99, 31, 0, 32, 33, 34, 35, 36, 37, 38, 92, 27, 28, 29, - 0, 30, 0, 31, 58, 149, 150, 0, 0, 32, 33, 34, 35, 36, 37, - 38, 0, 27, 28, 29, 0, 30, 0, 31, 0, 32, 33, 34, 35, 36, - 37, 38, 101, 27, 28, 29, 0, 30, 0, 31, 39, 40, 41, 42, 43, - 32, 33, 34, 35, 36, 37, 38, 27, 28, 29, 0, 30, 0, 31, 0, - 0, 32, 33, 34, 35, 36, 37, 38, 27, 28, 29, 0, 30, 0, 31, - 27, 28, 29, 0, 30, 32, 31, 34, 35, 36, 37, 38, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 32, 0, 0, 35, 36, 37, 38, 0, 0, - 0, 35, 36, 37, 38}; - -static const yytype_int16 yycheck[] = { - 1, 117, 45, 6, 14, 121, 142, 12, 6, 10, 120, 12, 49, 59, 49, 16, - 0, 52, 11, 7, 8, 9, 23, 11, 22, 14, 6, 28, 29, 6, 35, 27, - 33, 34, 35, 36, 37, 30, 49, 49, 150, 44, 22, 38, 45, 161, 39, 40, - 41, 42, 43, 187, 95, 99, 52, 165, 3, 49, 194, 6, 52, 107, 44, 45, - 46, 66, 67, 33, 34, 21, 71, 72, 188, 74, 75, 68, 77, 51, 79, 124, - 50, 37, 3, 4, 5, 6, 42, 43, 44, 45, 46, 12, 208, 28, 95, 50, - 141, 3, 4, 5, 6, 10, 103, 104, 25, 26, 12, 3, 4, 5, 6, 10, - 105, 158, 35, 12, 12, 49, 163, 164, 26, 21, 43, 44, 31, 32, 5, 6, - 49, 35, 26, 15, 16, 39, 40, 136, 137, 43, 49, 35, 50, 51, 38, 49, - 40, 138, 6, 43, 3, 4, 5, 6, 6, 49, 10, 50, 51, 12, 3, 4, - 5, 6, 23, 24, 50, 6, 167, 12, 10, 50, 25, 26, 52, 49, 52, 50, - 177, 50, 50, 50, 35, 26, 51, 6, 6, 186, 16, 18, 43, 44, 35, 17, - 19, 38, 49, 20, 29, 18, 43, 3, 4, 5, 6, 3, 49, 3, 3, 50, - 12, 3, 4, 5, 6, 30, 3, 4, 5, 6, 12, 37, 3, 50, 26, 12, - 42, 43, 44, 45, 46, 53, 17, 35, 26, 96, 161, 51, 160, 26, 172, 43, - 44, 35, 7, 8, 9, 49, 11, 208, 13, 43, 193, -1, 185, -1, 43, 49, - -1, -1, -1, -1, 49, 7, 8, 9, -1, 11, -1, 13, -1, -1, 35, 36, - 37, 38, 39, 40, 41, -1, -1, -1, 6, 7, 8, 9, -1, 11, 51, 13, - -1, 35, 36, 37, 38, 39, 40, 41, 22, 7, 8, 9, -1, 11, -1, 13, - 50, 15, 16, -1, -1, 35, 36, 37, 38, 39, 40, 41, -1, 7, 8, 9, - -1, 11, -1, 13, -1, 35, 36, 37, 38, 39, 40, 41, 22, 7, 8, 9, - -1, 11, -1, 13, 42, 43, 44, 45, 46, 35, 36, 37, 38, 39, 40, 41, - 7, 8, 9, -1, 11, -1, 13, -1, -1, 35, 36, 37, 38, 39, 40, 41, - 7, 8, 9, -1, 11, -1, 13, 7, 8, 9, -1, 11, 35, 13, 37, 38, - 39, 40, 41, -1, -1, -1, -1, -1, -1, -1, -1, -1, 35, -1, -1, 38, - 39, 40, 41, -1, -1, -1, 38, 39, 40, 41}; +static const yytype_uint8 yytable[] = +{ + 49, 18, 173, 87, 145, 7, 7, 148, 162, 143, + 103, 22, 127, 7, 62, 7, 39, 14, 95, 40, + 7, 96, 81, 43, 44, 45, 23, 24, 49, 26, + 92, 91, 10, 10, 48, 18, 25, 19, 63, 56, + 10, 58, 10, 170, 59, 60, 145, 10, 97, 68, + 69, 72, 75, 77, 61, 130, 199, 53, 180, 126, + 138, 7, 48, 209, 78, 82, 83, 84, 85, 86, + 142, 98, 152, 122, 99, 200, 100, 91, 81, 128, + 49, 109, 110, 104, 132, 105, 112, 113, 10, 114, + 115, 111, 116, 166, 117, 200, 4, 5, 6, 7, + 1, 2, 128, 144, 107, 8, 144, 108, 151, 177, + 92, 178, 179, 119, 48, 88, 89, 153, 154, 9, + 135, 136, 121, 64, 65, 66, 10, 67, 120, 11, + 137, 92, 167, 168, 185, 186, 128, 12, 174, 4, + 5, 6, 7, 13, 123, 144, 134, 92, 8, 92, + 92, 128, 187, 188, 139, 159, 160, 210, 211, 140, + 149, 46, 9, 150, 156, 161, 157, 169, 158, 10, + 164, 40, 11, 163, 144, 4, 5, 6, 7, 165, + 12, 47, 171, 106, 8, 181, 13, 182, 41, 42, + 43, 44, 45, 190, 144, 183, 195, 191, 9, 192, + 4, 5, 6, 7, 196, 10, 198, 193, 11, 8, + 203, 204, 70, 71, 206, 207, 12, 41, 42, 43, + 44, 45, 13, 9, 213, 212, 4, 5, 6, 7, + 10, 52, 101, 11, 131, 8, 73, 176, 74, 175, + 189, 12, 4, 5, 6, 7, 214, 13, 124, 9, + 197, 8, 0, 208, 0, 0, 10, 0, 0, 11, + 0, 4, 5, 6, 7, 9, 0, 12, 125, 0, + 8, 0, 10, 13, 0, 11, 0, 0, 76, 0, + 0, 0, 0, 12, 9, 0, 0, 0, 0, 13, + 0, 10, 0, 0, 11, 4, 5, 6, 7, 0, + 0, 0, 12, 47, 8, 27, 28, 29, 13, 30, + 0, 31, 0, 0, 0, 27, 28, 29, 9, 30, + 0, 31, 27, 28, 29, 10, 30, 0, 31, 0, + 0, 0, 0, 0, 32, 0, 12, 35, 36, 37, + 38, 0, 13, 0, 32, 33, 34, 35, 36, 37, + 38, 32, 33, 34, 35, 36, 37, 38, 0, 0, + 118, 0, 7, 27, 28, 29, 57, 30, 0, 31, + 0, 27, 28, 29, 0, 30, 0, 31, 91, 153, + 154, 0, 0, 0, 27, 28, 29, 0, 30, 10, + 31, 0, 32, 33, 34, 35, 36, 37, 38, 102, + 32, 33, 34, 35, 36, 37, 38, 0, 0, 0, + 0, 0, 0, 32, 33, 34, 35, 36, 37, 38, + 27, 28, 29, 0, 30, 0, 31, 27, 28, 29, + 0, 30, 0, 31, 27, 28, 29, 141, 30, 0, + 31, 0, 41, 42, 43, 44, 45, 0, 0, 32, + 33, 34, 35, 36, 37, 38, 32, 0, 34, 35, + 36, 37, 38, 0, 0, 0, 35, 36, 37, 38 +}; + +static const yytype_int16 yycheck[] = +{ + 18, 14, 3, 46, 121, 6, 6, 124, 143, 120, + 58, 0, 5, 6, 12, 6, 50, 1, 50, 53, + 6, 53, 40, 45, 46, 47, 50, 11, 46, 13, + 48, 22, 33, 33, 18, 14, 12, 50, 36, 23, + 33, 50, 33, 154, 28, 29, 163, 33, 21, 33, + 34, 35, 36, 37, 30, 98, 191, 27, 169, 45, + 108, 6, 46, 198, 39, 41, 42, 43, 44, 45, + 118, 52, 128, 91, 51, 192, 28, 22, 96, 97, + 98, 65, 66, 10, 102, 10, 70, 71, 33, 73, + 74, 67, 76, 149, 78, 212, 3, 4, 5, 6, + 34, 35, 120, 121, 12, 12, 124, 50, 53, 165, + 128, 167, 168, 51, 98, 31, 32, 15, 16, 26, + 104, 105, 50, 7, 8, 9, 33, 11, 21, 36, + 106, 149, 150, 151, 51, 52, 154, 44, 156, 3, + 4, 5, 6, 50, 33, 163, 51, 165, 12, 167, + 168, 169, 51, 52, 10, 139, 140, 23, 24, 10, + 51, 25, 26, 53, 50, 141, 51, 16, 51, 33, + 51, 53, 36, 52, 192, 3, 4, 5, 6, 51, + 44, 45, 17, 38, 12, 18, 50, 171, 43, 44, + 45, 46, 47, 18, 212, 19, 3, 181, 26, 20, + 3, 4, 5, 6, 3, 33, 190, 29, 36, 12, + 3, 30, 40, 41, 51, 51, 44, 43, 44, 45, + 46, 47, 50, 26, 3, 52, 3, 4, 5, 6, + 33, 19, 55, 36, 99, 12, 39, 163, 41, 162, + 175, 44, 3, 4, 5, 6, 212, 50, 25, 26, + 189, 12, -1, 197, -1, -1, 33, -1, -1, 36, + -1, 3, 4, 5, 6, 26, -1, 44, 45, -1, + 12, -1, 33, 50, -1, 36, -1, -1, 39, -1, + -1, -1, -1, 44, 26, -1, -1, -1, -1, 50, + -1, 33, -1, -1, 36, 3, 4, 5, 6, -1, + -1, -1, 44, 45, 12, 7, 8, 9, 50, 11, + -1, 13, -1, -1, -1, 7, 8, 9, 26, 11, + -1, 13, 7, 8, 9, 33, 11, -1, 13, -1, + -1, -1, -1, -1, 36, -1, 44, 39, 40, 41, + 42, -1, 50, -1, 36, 37, 38, 39, 40, 41, + 42, 36, 37, 38, 39, 40, 41, 42, -1, -1, + 52, -1, 6, 7, 8, 9, 51, 11, -1, 13, + -1, 7, 8, 9, -1, 11, -1, 13, 22, 15, + 16, -1, -1, -1, 7, 8, 9, -1, 11, 33, + 13, -1, 36, 37, 38, 39, 40, 41, 42, 22, + 36, 37, 38, 39, 40, 41, 42, -1, -1, -1, + -1, -1, -1, 36, 37, 38, 39, 40, 41, 42, + 7, 8, 9, -1, 11, -1, 13, 7, 8, 9, + -1, 11, -1, 13, 7, 8, 9, 38, 11, -1, + 13, -1, 43, 44, 45, 46, 47, -1, -1, 36, + 37, 38, 39, 40, 41, 42, 36, -1, 38, 39, + 40, 41, 42, -1, -1, -1, 39, 40, 41, 42 +}; /* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of state STATE-NUM. */ -static const yytype_int8 yystos[] = { - 0, 33, 34, 54, 3, 4, 5, 6, 12, 26, 35, 43, 49, 55, 57, 58, 14, 49, - 60, 61, 0, 49, 52, 49, 55, 58, 55, 7, 8, 9, 11, 13, 35, 36, 37, 38, - 39, 40, 41, 42, 43, 44, 45, 46, 6, 25, 44, 55, 64, 68, 61, 27, 62, 63, - 55, 56, 6, 55, 50, 49, 55, 55, 58, 12, 35, 7, 8, 9, 11, 55, 55, 39, - 40, 55, 38, 40, 55, 38, 55, 38, 58, 58, 58, 58, 58, 49, 52, 64, 31, 32, - 67, 6, 22, 69, 21, 51, 50, 28, 60, 51, 50, 22, 56, 10, 10, 37, 12, 49, - 55, 55, 58, 55, 55, 55, 55, 55, 55, 25, 44, 44, 21, 49, 6, 5, 6, 77, - 64, 62, 56, 6, 59, 50, 55, 55, 58, 56, 10, 10, 37, 6, 57, 50, 77, 57, - 65, 66, 52, 52, 69, 15, 16, 71, 49, 50, 50, 55, 55, 58, 50, 69, 71, 51, - 50, 6, 6, 16, 77, 17, 70, 3, 6, 69, 70, 66, 69, 69, 77, 18, 55, 19, - 72, 50, 51, 50, 51, 72, 18, 55, 20, 29, 75, 3, 3, 75, 55, 71, 57, 73, - 74, 3, 30, 76, 50, 50, 76, 71, 23, 24, 51, 3, 73}; +static const yytype_int8 yystos[] = +{ + 0, 34, 35, 55, 3, 4, 5, 6, 12, 26, + 33, 36, 44, 50, 56, 58, 59, 60, 14, 50, + 62, 63, 0, 50, 56, 60, 56, 7, 8, 9, + 11, 13, 36, 37, 38, 39, 40, 41, 42, 50, + 53, 43, 44, 45, 46, 47, 25, 45, 56, 58, + 66, 70, 63, 27, 64, 65, 56, 51, 50, 56, + 56, 60, 12, 36, 7, 8, 9, 11, 56, 56, + 40, 41, 56, 39, 41, 56, 39, 56, 39, 56, + 57, 58, 60, 60, 60, 60, 60, 66, 31, 32, + 69, 22, 58, 71, 72, 50, 53, 21, 52, 51, + 28, 62, 22, 57, 10, 10, 38, 12, 50, 56, + 56, 60, 56, 56, 56, 56, 56, 56, 52, 51, + 21, 50, 58, 33, 25, 45, 45, 5, 58, 80, + 66, 64, 58, 61, 51, 56, 56, 60, 57, 10, + 10, 38, 57, 80, 58, 59, 67, 68, 59, 51, + 53, 53, 71, 15, 16, 74, 50, 51, 51, 56, + 56, 60, 74, 52, 51, 51, 71, 58, 58, 16, + 80, 17, 73, 3, 58, 73, 68, 71, 71, 71, + 80, 18, 56, 19, 75, 51, 52, 51, 52, 75, + 18, 56, 20, 29, 78, 3, 3, 78, 56, 74, + 59, 76, 77, 3, 30, 79, 51, 51, 79, 74, + 23, 24, 52, 3, 76 +}; /* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */ -static const yytype_int8 yyr1[] = { - 0, 53, 54, 54, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, - 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 56, 56, - 57, 57, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 59, - 59, 59, 59, 59, 60, 60, 61, 61, 62, 62, 63, 64, 64, 65, 66, 66, 67, - 67, 68, 68, 68, 68, 68, 68, 68, 68, 68, 69, 69, 70, 70, 71, 71, 71, - 72, 72, 73, 73, 74, 74, 74, 75, 75, 76, 76, 77, 77, 77, 77, 77, 77}; +static const yytype_int8 yyr1[] = +{ + 0, 54, 55, 55, 55, 56, 56, 56, 56, 56, + 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, + 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, + 56, 56, 57, 57, 58, 58, 59, 59, 60, 60, + 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, + 60, 60, 61, 61, 61, 61, 61, 62, 62, 63, + 63, 64, 64, 65, 66, 66, 67, 68, 68, 69, + 69, 70, 70, 70, 70, 70, 70, 70, 70, 70, + 71, 71, 72, 72, 73, 73, 74, 74, 74, 75, + 75, 76, 76, 77, 77, 77, 78, 78, 79, 79, + 80, 80, 80, 80, 80, 80 +}; /* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */ -static const yytype_int8 yyr2[] = { - 0, 2, 0, 2, 2, 1, 3, 3, 2, 3, 4, 4, 3, 3, 4, 4, 4, 4, 3, 4, 5, - 6, 3, 4, 5, 6, 5, 6, 5, 6, 3, 4, 3, 1, 1, 3, 1, 1, 1, 1, 3, 1, - 2, 3, 3, 3, 3, 3, 4, 6, 1, 4, 6, 4, 6, 2, 4, 9, 10, 0, 2, 2, 1, - 3, 1, 1, 3, 1, 1, 1, 2, 5, 1, 3, 4, 5, 5, 6, 2, 1, 0, 2, 0, 5, - 6, 0, 3, 3, 1, 1, 2, 2, 0, 2, 0, 2, 1, 2, 3, 4, 3, 4}; - -enum +static const yytype_int8 yyr2[] = { - YYENOMEM = -2 + 0, 2, 0, 2, 2, 1, 3, 3, 2, 3, + 4, 4, 3, 3, 4, 4, 4, 4, 3, 4, + 5, 6, 3, 4, 5, 6, 5, 6, 5, 6, + 3, 4, 3, 1, 1, 1, 1, 3, 1, 1, + 1, 1, 3, 1, 2, 3, 3, 3, 3, 3, + 4, 6, 1, 4, 6, 4, 6, 2, 4, 9, + 10, 0, 2, 2, 1, 3, 1, 1, 3, 1, + 1, 1, 2, 5, 1, 3, 4, 5, 5, 6, + 2, 1, 1, 2, 0, 2, 0, 5, 6, 0, + 3, 3, 1, 1, 2, 2, 0, 2, 0, 2, + 1, 2, 3, 4, 3, 4 }; -#define yyerrok (yyerrstatus = 0) -#define yyclearin (yychar = YYEMPTY) - -#define YYACCEPT goto yyacceptlab -#define YYABORT goto yyabortlab -#define YYERROR goto yyerrorlab -#define YYNOMEM goto yyexhaustedlab - -#define YYRECOVERING() (!!yyerrstatus) - -#define YYBACKUP(Token, Value) \ - do \ - if (yychar == YYEMPTY) \ - { \ - yychar = (Token); \ - yylval = (Value); \ - YYPOPSTACK(yylen); \ - yystate = *yyssp; \ - goto yybackup; \ - } \ - else \ - { \ - yyerror(context, YY_("syntax error: cannot back up")); \ - YYERROR; \ - } \ - while (0) + +enum { YYENOMEM = -2 }; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab +#define YYNOMEM goto yyexhaustedlab + + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ + do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (context, YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ + while (0) /* Backward compatibility with an undocumented macro. Use YYerror or YYUNDEF. */ #define YYERRCODE YYUNDEF + /* Enable debugging if requested. */ #if YYDEBUG -#ifndef YYFPRINTF -#include /* INFRINGES ON USER NAME SPACE */ -#define YYFPRINTF fprintf -#endif +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + + + + +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Kind, Value, context); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) -#define YYDPRINTF(Args) \ - do \ - { \ - if (yydebug) \ - YYFPRINTF Args; \ - } while (0) - -#define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ - do \ - { \ - if (yydebug) \ - { \ - YYFPRINTF(stderr, "%s ", Title); \ - yy_symbol_print(stderr, Kind, Value, context); \ - YYFPRINTF(stderr, "\n"); \ - } \ - } while (0) /*-----------------------------------. | Print this symbol's value on YYO. | `-----------------------------------*/ -static void yy_symbol_value_print(FILE *yyo, yysymbol_kind_t yykind, - YYSTYPE const *const yyvaluep, - swq_parse_context *context) +static void +yy_symbol_value_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, swq_parse_context *context) { - FILE *yyoutput = yyo; - YY_USE(yyoutput); - YY_USE(context); - if (!yyvaluep) - return; - YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN - YY_USE(yykind); - YY_IGNORE_MAYBE_UNINITIALIZED_END + FILE *yyoutput = yyo; + YY_USE (yyoutput); + YY_USE (context); + if (!yyvaluep) + return; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YY_USE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END } + /*---------------------------. | Print this symbol on YYO. | `---------------------------*/ -static void yy_symbol_print(FILE *yyo, yysymbol_kind_t yykind, - YYSTYPE const *const yyvaluep, - swq_parse_context *context) +static void +yy_symbol_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep, swq_parse_context *context) { - YYFPRINTF(yyo, "%s %s (", yykind < YYNTOKENS ? "token" : "nterm", - yysymbol_name(yykind)); + YYFPRINTF (yyo, "%s %s (", + yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); - yy_symbol_value_print(yyo, yykind, yyvaluep, context); - YYFPRINTF(yyo, ")"); + yy_symbol_value_print (yyo, yykind, yyvaluep, context); + YYFPRINTF (yyo, ")"); } /*------------------------------------------------------------------. @@ -938,66 +1026,69 @@ static void yy_symbol_print(FILE *yyo, yysymbol_kind_t yykind, | TOP (included). | `------------------------------------------------------------------*/ -static void yy_stack_print(yy_state_t *yybottom, yy_state_t *yytop) +static void +yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) { - YYFPRINTF(stderr, "Stack now"); - for (; yybottom <= yytop; yybottom++) + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) { - int yybot = *yybottom; - YYFPRINTF(stderr, " %d", yybot); + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); } - YYFPRINTF(stderr, "\n"); + YYFPRINTF (stderr, "\n"); } -#define YY_STACK_PRINT(Bottom, Top) \ - do \ - { \ - if (yydebug) \ - yy_stack_print((Bottom), (Top)); \ - } while (0) +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + /*------------------------------------------------. | Report that the YYRULE is going to be reduced. | `------------------------------------------------*/ -static void yy_reduce_print(yy_state_t *yyssp, YYSTYPE *yyvsp, int yyrule, - swq_parse_context *context) +static void +yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, + int yyrule, swq_parse_context *context) { - int yylno = yyrline[yyrule]; - int yynrhs = yyr2[yyrule]; - int yyi; - YYFPRINTF(stderr, "Reducing stack by rule %d (line %d):\n", yyrule - 1, - yylno); - /* The symbols being reduced. */ - for (yyi = 0; yyi < yynrhs; yyi++) + int yylno = yyrline[yyrule]; + int yynrhs = yyr2[yyrule]; + int yyi; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) { - YYFPRINTF(stderr, " $%d = ", yyi + 1); - yy_symbol_print(stderr, YY_ACCESSING_SYMBOL(+yyssp[yyi + 1 - yynrhs]), - &yyvsp[(yyi + 1) - (yynrhs)], context); - YYFPRINTF(stderr, "\n"); + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, + YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]), + &yyvsp[(yyi + 1) - (yynrhs)], context); + YYFPRINTF (stderr, "\n"); } } -#define YY_REDUCE_PRINT(Rule) \ - do \ - { \ - if (yydebug) \ - yy_reduce_print(yyssp, yyvsp, Rule, context); \ - } while (0) +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule, context); \ +} while (0) /* Nonzero means print parse trace. It is left uninitialized so that multiple parsers can coexist. */ int yydebug; #else /* !YYDEBUG */ -#define YYDPRINTF(Args) ((void)0) -#define YY_SYMBOL_PRINT(Title, Kind, Value, Location) -#define YY_STACK_PRINT(Bottom, Top) -#define YY_REDUCE_PRINT(Rule) +# define YYDPRINTF(Args) ((void) 0) +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) #endif /* !YYDEBUG */ + /* YYINITDEPTH -- initial size of the parser's stacks. */ #ifndef YYINITDEPTH -#define YYINITDEPTH 200 +# define YYINITDEPTH 200 #endif /* YYMAXDEPTH -- maximum size the stacks can grow to (effective only @@ -1008,14 +1099,15 @@ int yydebug; evaluated with infinite-precision integer arithmetic. */ #ifndef YYMAXDEPTH -#define YYMAXDEPTH 10000 +# define YYMAXDEPTH 10000 #endif + /* Context of a parse error. */ typedef struct { - yy_state_t *yyssp; - yysymbol_kind_t yytoken; + yy_state_t *yyssp; + yysymbol_kind_t yytoken; } yypcontext_t; /* Put in YYARG at most YYARGN of the expected tokens given the @@ -1024,71 +1116,77 @@ typedef struct be less than YYNTOKENS). Return YYENOMEM on memory exhaustion. Return 0 if there are more than YYARGN expected tokens, yet fill YYARG up to YYARGN. */ -static int yypcontext_expected_tokens(const yypcontext_t *yyctx, - yysymbol_kind_t yyarg[], int yyargn) +static int +yypcontext_expected_tokens (const yypcontext_t *yyctx, + yysymbol_kind_t yyarg[], int yyargn) { - /* Actual size of YYARG. */ - int yycount = 0; - int yyn = yypact[+*yyctx->yyssp]; - if (!yypact_value_is_default(yyn)) + /* Actual size of YYARG. */ + int yycount = 0; + int yyn = yypact[+*yyctx->yyssp]; + if (!yypact_value_is_default (yyn)) { - /* Start YYX at -YYN if negative to avoid negative indexes in + /* Start YYX at -YYN if negative to avoid negative indexes in YYCHECK. In other words, skip the first -YYN actions for this state because they are default actions. */ - int yyxbegin = yyn < 0 ? -yyn : 0; - /* Stay within bounds of both yycheck and yytname. */ - int yychecklim = YYLAST - yyn + 1; - int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; - int yyx; - for (yyx = yyxbegin; yyx < yyxend; ++yyx) - if (yycheck[yyx + yyn] == yyx && yyx != YYSYMBOL_YYerror && - !yytable_value_is_error(yytable[yyx + yyn])) - { - if (!yyarg) - ++yycount; - else if (yycount == yyargn) - return 0; - else - yyarg[yycount++] = YY_CAST(yysymbol_kind_t, yyx); - } + int yyxbegin = yyn < 0 ? -yyn : 0; + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yyx; + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYSYMBOL_YYerror + && !yytable_value_is_error (yytable[yyx + yyn])) + { + if (!yyarg) + ++yycount; + else if (yycount == yyargn) + return 0; + else + yyarg[yycount++] = YY_CAST (yysymbol_kind_t, yyx); + } } - if (yyarg && yycount == 0 && 0 < yyargn) - yyarg[0] = YYSYMBOL_YYEMPTY; - return yycount; + if (yyarg && yycount == 0 && 0 < yyargn) + yyarg[0] = YYSYMBOL_YYEMPTY; + return yycount; } + + + #ifndef yystrlen -#if defined __GLIBC__ && defined _STRING_H -#define yystrlen(S) (YY_CAST(YYPTRDIFF_T, strlen(S))) -#else +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen(S) (YY_CAST (YYPTRDIFF_T, strlen (S))) +# else /* Return the length of YYSTR. */ -static YYPTRDIFF_T yystrlen(const char *yystr) +static YYPTRDIFF_T +yystrlen (const char *yystr) { - YYPTRDIFF_T yylen; - for (yylen = 0; yystr[yylen]; yylen++) - continue; - return yylen; + YYPTRDIFF_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; } -#endif +# endif #endif #ifndef yystpcpy -#if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE -#define yystpcpy stpcpy -#else +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else /* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in YYDEST. */ -static char *yystpcpy(char *yydest, const char *yysrc) +static char * +yystpcpy (char *yydest, const char *yysrc) { - char *yyd = yydest; - const char *yys = yysrc; + char *yyd = yydest; + const char *yys = yysrc; - while ((*yyd++ = *yys++) != '\0') - continue; + while ((*yyd++ = *yys++) != '\0') + continue; - return yyd - 1; + return yyd - 1; } -#endif +# endif #endif #ifndef yytnamerr @@ -1099,53 +1197,56 @@ static char *yystpcpy(char *yydest, const char *yysrc) backslash-backslash). YYSTR is taken from yytname. If YYRES is null, do not copy; instead, return the length of what the result would have been. */ -static YYPTRDIFF_T yytnamerr(char *yyres, const char *yystr) +static YYPTRDIFF_T +yytnamerr (char *yyres, const char *yystr) { - if (*yystr == '"') + if (*yystr == '"') { - YYPTRDIFF_T yyn = 0; - char const *yyp = yystr; - for (;;) - switch (*++yyp) - { - case '\'': - case ',': - goto do_not_strip_quotes; - - case '\\': - if (*++yyp != '\\') - goto do_not_strip_quotes; - else - goto append; - - append: - default: - if (yyres) - yyres[yyn] = *yyp; - yyn++; - break; - - case '"': - if (yyres) - yyres[yyn] = '\0'; - return yyn; - } - do_not_strip_quotes:; + YYPTRDIFF_T yyn = 0; + char const *yyp = yystr; + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + else + goto append; + + append: + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; } - if (yyres) - return yystpcpy(yyres, yystr) - yyres; - else - return yystrlen(yystr); + if (yyres) + return yystpcpy (yyres, yystr) - yyres; + else + return yystrlen (yystr); } #endif -static int yy_syntax_error_arguments(const yypcontext_t *yyctx, - yysymbol_kind_t yyarg[], int yyargn) + +static int +yy_syntax_error_arguments (const yypcontext_t *yyctx, + yysymbol_kind_t yyarg[], int yyargn) { - /* Actual size of YYARG. */ - int yycount = 0; - /* There are many possibilities here to consider: + /* Actual size of YYARG. */ + int yycount = 0; + /* There are many possibilities here to consider: - If this state is a consistent state with a default action, then the only way this function was invoked is if the default action is an error action. In that case, don't check for expected @@ -1168,20 +1269,20 @@ static int yy_syntax_error_arguments(const yypcontext_t *yyctx, one exception: it will still contain any token that will not be accepted due to an error action in a later state. */ - if (yyctx->yytoken != YYSYMBOL_YYEMPTY) + if (yyctx->yytoken != YYSYMBOL_YYEMPTY) { - int yyn; - if (yyarg) - yyarg[yycount] = yyctx->yytoken; - ++yycount; - yyn = yypcontext_expected_tokens(yyctx, yyarg ? yyarg + 1 : yyarg, - yyargn - 1); - if (yyn == YYENOMEM) - return YYENOMEM; - else - yycount += yyn; + int yyn; + if (yyarg) + yyarg[yycount] = yyctx->yytoken; + ++yycount; + yyn = yypcontext_expected_tokens (yyctx, + yyarg ? yyarg + 1 : yyarg, yyargn - 1); + if (yyn == YYENOMEM) + return YYENOMEM; + else + yycount += yyn; } - return yycount; + return yycount; } /* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message @@ -1192,187 +1293,175 @@ static int yy_syntax_error_arguments(const yypcontext_t *yyctx, not large enough to hold the message. In that case, also set *YYMSG_ALLOC to the required number of bytes. Return YYENOMEM if the required number of bytes is too large to store. */ -static int yysyntax_error(YYPTRDIFF_T *yymsg_alloc, char **yymsg, - const yypcontext_t *yyctx) +static int +yysyntax_error (YYPTRDIFF_T *yymsg_alloc, char **yymsg, + const yypcontext_t *yyctx) { - enum - { - YYARGS_MAX = 5 - }; - - /* Internationalized format string. */ - const char *yyformat = YY_NULLPTR; - /* Arguments of yyformat: reported tokens (one for the "unexpected", + enum { YYARGS_MAX = 5 }; + /* Internationalized format string. */ + const char *yyformat = YY_NULLPTR; + /* Arguments of yyformat: reported tokens (one for the "unexpected", one per "expected"). */ - yysymbol_kind_t yyarg[YYARGS_MAX]; - /* Cumulated lengths of YYARG. */ - YYPTRDIFF_T yysize = 0; + yysymbol_kind_t yyarg[YYARGS_MAX]; + /* Cumulated lengths of YYARG. */ + YYPTRDIFF_T yysize = 0; - /* Actual size of YYARG. */ - int yycount = yy_syntax_error_arguments(yyctx, yyarg, YYARGS_MAX); - if (yycount == YYENOMEM) - return YYENOMEM; + /* Actual size of YYARG. */ + int yycount = yy_syntax_error_arguments (yyctx, yyarg, YYARGS_MAX); + if (yycount == YYENOMEM) + return YYENOMEM; - switch (yycount) + switch (yycount) { -#define YYCASE_(N, S) \ - case N: \ - yyformat = S; \ +#define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ break - default: /* Avoid compiler warnings. */ - YYCASE_(0, YY_("syntax error")); - YYCASE_(1, YY_("syntax error, unexpected %s")); - YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); - YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); - YYCASE_( - 4, - YY_("syntax error, unexpected %s, expecting %s or %s or %s")); - YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or " - "%s or %s")); + default: /* Avoid compiler warnings. */ + YYCASE_(0, YY_("syntax error")); + YYCASE_(1, YY_("syntax error, unexpected %s")); + YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); #undef YYCASE_ } - /* Compute error message size. Don't count the "%s"s, but reserve + /* Compute error message size. Don't count the "%s"s, but reserve room for the terminator. */ - yysize = yystrlen(yyformat) - 2 * yycount + 1; - { - int yyi; - for (yyi = 0; yyi < yycount; ++yyi) - { - YYPTRDIFF_T yysize1 = - yysize + yytnamerr(YY_NULLPTR, yytname[yyarg[yyi]]); - if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) - yysize = yysize1; - else - return YYENOMEM; - } - } + yysize = yystrlen (yyformat) - 2 * yycount + 1; + { + int yyi; + for (yyi = 0; yyi < yycount; ++yyi) + { + YYPTRDIFF_T yysize1 + = yysize + yytnamerr (YY_NULLPTR, yytname[yyarg[yyi]]); + if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) + yysize = yysize1; + else + return YYENOMEM; + } + } - if (*yymsg_alloc < yysize) + if (*yymsg_alloc < yysize) { - *yymsg_alloc = 2 * yysize; - if (!(yysize <= *yymsg_alloc && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) - *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; - return -1; + *yymsg_alloc = 2 * yysize; + if (! (yysize <= *yymsg_alloc + && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) + *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; + return -1; } - /* Avoid sprintf, as that infringes on the user's name space. + /* Avoid sprintf, as that infringes on the user's name space. Don't have undefined behavior even if the translation produced a string with the wrong number of "%s"s. */ - { - char *yyp = *yymsg; - int yyi = 0; - while ((*yyp = *yyformat) != '\0') - if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) - { - yyp += yytnamerr(yyp, yytname[yyarg[yyi++]]); - yyformat += 2; - } - else - { - ++yyp; - ++yyformat; - } - } - return 0; + { + char *yyp = *yymsg; + int yyi = 0; + while ((*yyp = *yyformat) != '\0') + if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yytname[yyarg[yyi++]]); + yyformat += 2; + } + else + { + ++yyp; + ++yyformat; + } + } + return 0; } + /*-----------------------------------------------. | Release the memory associated to this symbol. | `-----------------------------------------------*/ -static void yydestruct(const char *yymsg, yysymbol_kind_t yykind, - YYSTYPE *yyvaluep, swq_parse_context *context) +static void +yydestruct (const char *yymsg, + yysymbol_kind_t yykind, YYSTYPE *yyvaluep, swq_parse_context *context) { - YY_USE(yyvaluep); - YY_USE(context); - if (!yymsg) - yymsg = "Deleting"; - YY_SYMBOL_PRINT(yymsg, yykind, yyvaluep, yylocationp); - - YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN - switch (yykind) + YY_USE (yyvaluep); + YY_USE (context); + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp); + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + switch (yykind) { - case YYSYMBOL_SWQT_INTEGER_NUMBER: /* "integer number" */ - { - delete (*yyvaluep); - } + case YYSYMBOL_SWQT_INTEGER_NUMBER: /* "integer number" */ + { delete (*yyvaluep); } break; - case YYSYMBOL_SWQT_FLOAT_NUMBER: /* "floating point number" */ - { - delete (*yyvaluep); - } + case YYSYMBOL_SWQT_FLOAT_NUMBER: /* "floating point number" */ + { delete (*yyvaluep); } break; - case YYSYMBOL_SWQT_STRING: /* "string" */ - { - delete (*yyvaluep); - } + case YYSYMBOL_SWQT_STRING: /* "string" */ + { delete (*yyvaluep); } break; - case YYSYMBOL_SWQT_IDENTIFIER: /* "identifier" */ - { - delete (*yyvaluep); - } + case YYSYMBOL_SWQT_IDENTIFIER: /* "identifier" */ + { delete (*yyvaluep); } break; - case YYSYMBOL_value_expr: /* value_expr */ - { - delete (*yyvaluep); - } + case YYSYMBOL_SWQT_HIDDEN: /* "HIDDEN" */ + { delete (*yyvaluep); } break; - case YYSYMBOL_value_expr_list: /* value_expr_list */ - { - delete (*yyvaluep); - } + case YYSYMBOL_value_expr: /* value_expr */ + { delete (*yyvaluep); } break; - case YYSYMBOL_field_value: /* field_value */ - { - delete (*yyvaluep); - } + case YYSYMBOL_value_expr_list: /* value_expr_list */ + { delete (*yyvaluep); } break; - case YYSYMBOL_value_expr_non_logical: /* value_expr_non_logical */ - { - delete (*yyvaluep); - } + case YYSYMBOL_field_value: /* field_value */ + { delete (*yyvaluep); } break; - case YYSYMBOL_type_def: /* type_def */ - { - delete (*yyvaluep); - } + case YYSYMBOL_value_expr_non_logical: /* value_expr_non_logical */ + { delete (*yyvaluep); } break; - case YYSYMBOL_table_def: /* table_def */ - { - delete (*yyvaluep); - } + case YYSYMBOL_type_def: /* type_def */ + { delete (*yyvaluep); } break; - default: - break; + case YYSYMBOL_table_def: /* table_def */ + { delete (*yyvaluep); } + break; + + default: + break; } - YY_IGNORE_MAYBE_UNINITIALIZED_END + YY_IGNORE_MAYBE_UNINITIALIZED_END } + + + + + /*----------. | yyparse. | `----------*/ -int yyparse(swq_parse_context *context) +int +yyparse (swq_parse_context *context) { - /* Lookahead token kind. */ - int yychar; +/* Lookahead token kind. */ +int yychar; + - /* The semantic value of the lookahead symbol. */ - /* Default value used for initialization, for pacifying older GCCs +/* The semantic value of the lookahead symbol. */ +/* Default value used for initialization, for pacifying older GCCs or non-GCC compilers. */ - YY_INITIAL_VALUE(static YYSTYPE yyval_default;) - YYSTYPE yylval YY_INITIAL_VALUE(= yyval_default); +YY_INITIAL_VALUE (static YYSTYPE yyval_default;) +YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default); /* Number of syntax errors so far. */ int yynerrs = 0; @@ -1397,207 +1486,215 @@ int yyparse(swq_parse_context *context) YYSTYPE *yyvs = yyvsa; YYSTYPE *yyvsp = yyvs; - int yyn; - /* The return value of yyparse. */ - int yyresult; - /* Lookahead symbol kind. */ - yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; - /* The variables used to return semantic value and location from the + int yyn; + /* The return value of yyparse. */ + int yyresult; + /* Lookahead symbol kind. */ + yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; + /* The variables used to return semantic value and location from the action routines. */ - YYSTYPE yyval; + YYSTYPE yyval; - /* Buffer for error messages, and its allocated size. */ - char yymsgbuf[128]; - char *yymsg = yymsgbuf; - YYPTRDIFF_T yymsg_alloc = sizeof yymsgbuf; + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYPTRDIFF_T yymsg_alloc = sizeof yymsgbuf; -#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) - /* The number of symbols on the RHS of the reduced rule. + /* The number of symbols on the RHS of the reduced rule. Keep to zero when no symbol should be popped. */ - int yylen = 0; + int yylen = 0; - YYDPRINTF((stderr, "Starting parse\n")); + YYDPRINTF ((stderr, "Starting parse\n")); - yychar = YYEMPTY; /* Cause a token to be read. */ + yychar = YYEMPTY; /* Cause a token to be read. */ + + goto yysetstate; - goto yysetstate; /*------------------------------------------------------------. | yynewstate -- push a new state, which is found in yystate. | `------------------------------------------------------------*/ yynewstate: - /* In all cases, when you get here, the value and location stacks + /* In all cases, when you get here, the value and location stacks have just been pushed. So pushing a state here evens the stacks. */ - yyssp++; + yyssp++; + /*--------------------------------------------------------------------. | yysetstate -- set current state (the top of the stack) to yystate. | `--------------------------------------------------------------------*/ yysetstate: - YYDPRINTF((stderr, "Entering state %d\n", yystate)); - YY_IGNORE_USELESS_CAST_BEGIN - *yyssp = YY_CAST(yy_state_t, yystate); - YY_IGNORE_USELESS_CAST_END - YY_STACK_PRINT(yyss, yyssp); - - if (yyss + yystacksize - 1 <= yyssp) + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + YY_ASSERT (0 <= yystate && yystate < YYNSTATES); + YY_IGNORE_USELESS_CAST_BEGIN + *yyssp = YY_CAST (yy_state_t, yystate); + YY_IGNORE_USELESS_CAST_END + YY_STACK_PRINT (yyss, yyssp); + + if (yyss + yystacksize - 1 <= yyssp) #if !defined yyoverflow && !defined YYSTACK_RELOCATE - YYNOMEM; + YYNOMEM; #else { - /* Get the current used size of the three stacks, in elements. */ - YYPTRDIFF_T yysize = yyssp - yyss + 1; + /* Get the current used size of the three stacks, in elements. */ + YYPTRDIFF_T yysize = yyssp - yyss + 1; -#if defined yyoverflow - { - /* Give user a chance to reallocate the stack. Use copies of +# if defined yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of these so that the &'s don't force the real ones into memory. */ - yy_state_t *yyss1 = yyss; - YYSTYPE *yyvs1 = yyvs; + yy_state_t *yyss1 = yyss; + YYSTYPE *yyvs1 = yyvs; - /* Each stack pointer address is followed by the size of the + /* Each stack pointer address is followed by the size of the data in use in that stack, in bytes. This used to be a conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ - yyoverflow(YY_("memory exhausted"), &yyss1, - yysize * YYSIZEOF(*yyssp), &yyvs1, - yysize * YYSIZEOF(*yyvsp), &yystacksize); - yyss = yyss1; - yyvs = yyvs1; - } -#else /* defined YYSTACK_RELOCATE */ - /* Extend the stack our own way. */ - if (YYMAXDEPTH <= yystacksize) - YYNOMEM; - yystacksize *= 2; - if (YYMAXDEPTH < yystacksize) - yystacksize = YYMAXDEPTH; - - { - yy_state_t *yyss1 = yyss; - union yyalloc *yyptr = YY_CAST( - union yyalloc *, - YYSTACK_ALLOC(YY_CAST(YYSIZE_T, YYSTACK_BYTES(yystacksize)))); - if (!yyptr) - YYNOMEM; - YYSTACK_RELOCATE(yyss_alloc, yyss); - YYSTACK_RELOCATE(yyvs_alloc, yyvs); -#undef YYSTACK_RELOCATE - if (yyss1 != yyssa) - YYSTACK_FREE(yyss1); - } -#endif - - yyssp = yyss + yysize - 1; - yyvsp = yyvs + yysize - 1; - - YY_IGNORE_USELESS_CAST_BEGIN - YYDPRINTF((stderr, "Stack size increased to %ld\n", - YY_CAST(long, yystacksize))); - YY_IGNORE_USELESS_CAST_END - - if (yyss + yystacksize - 1 <= yyssp) - YYABORT; + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * YYSIZEOF (*yyssp), + &yyvs1, yysize * YYSIZEOF (*yyvsp), + &yystacksize); + yyss = yyss1; + yyvs = yyvs1; + } +# else /* defined YYSTACK_RELOCATE */ + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + YYNOMEM; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yy_state_t *yyss1 = yyss; + union yyalloc *yyptr = + YY_CAST (union yyalloc *, + YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); + if (! yyptr) + YYNOMEM; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YY_IGNORE_USELESS_CAST_BEGIN + YYDPRINTF ((stderr, "Stack size increased to %ld\n", + YY_CAST (long, yystacksize))); + YY_IGNORE_USELESS_CAST_END + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; } #endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ - if (yystate == YYFINAL) - YYACCEPT; - goto yybackup; + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + /*-----------. | yybackup. | `-----------*/ yybackup: - /* Do appropriate processing given the current state. Read a + /* Do appropriate processing given the current state. Read a lookahead token if we need one and don't already have one. */ - /* First try to decide what to do without reference to lookahead token. */ - yyn = yypact[yystate]; - if (yypact_value_is_default(yyn)) - goto yydefault; + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; - /* Not known => get a lookahead token if don't already have one. */ + /* Not known => get a lookahead token if don't already have one. */ - /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ - if (yychar == YYEMPTY) + /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ + if (yychar == YYEMPTY) { - YYDPRINTF((stderr, "Reading a token\n")); - yychar = yylex(&yylval, context); + YYDPRINTF ((stderr, "Reading a token\n")); + yychar = yylex (&yylval, context); } - if (yychar <= END) + if (yychar <= END) { - yychar = END; - yytoken = YYSYMBOL_YYEOF; - YYDPRINTF((stderr, "Now at end of input.\n")); + yychar = END; + yytoken = YYSYMBOL_YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); } - else if (yychar == YYerror) + else if (yychar == YYerror) { - /* The scanner already issued an error message, process directly + /* The scanner already issued an error message, process directly to error recovery. But do not keep the error token as lookahead, it is too special and may lead us to an endless loop in error recovery. */ - yychar = YYUNDEF; - yytoken = YYSYMBOL_YYerror; - goto yyerrlab1; + yychar = YYUNDEF; + yytoken = YYSYMBOL_YYerror; + goto yyerrlab1; } - else + else { - yytoken = YYTRANSLATE(yychar); - YY_SYMBOL_PRINT("Next token is", yytoken, &yylval, &yylloc); + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); } - /* If the proper action on seeing token YYTOKEN is to reduce or to + /* If the proper action on seeing token YYTOKEN is to reduce or to detect an error, take that action. */ - yyn += yytoken; - if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) - goto yydefault; - yyn = yytable[yyn]; - if (yyn <= 0) + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) { - if (yytable_value_is_error(yyn)) - goto yyerrlab; - yyn = -yyn; - goto yyreduce; + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; } - /* Count tokens shifted since error; after three, turn off error + /* Count tokens shifted since error; after three, turn off error status. */ - if (yyerrstatus) - yyerrstatus--; + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END - /* Shift the lookahead token. */ - YY_SYMBOL_PRINT("Shifting", yytoken, &yylval, &yylloc); - yystate = yyn; - YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN - *++yyvsp = yylval; - YY_IGNORE_MAYBE_UNINITIALIZED_END + /* Discard the shifted token. */ + yychar = YYEMPTY; + goto yynewstate; - /* Discard the shifted token. */ - yychar = YYEMPTY; - goto yynewstate; /*-----------------------------------------------------------. | yydefault -- do the default action for the current state. | `-----------------------------------------------------------*/ yydefault: - yyn = yydefact[yystate]; - if (yyn == 0) - goto yyerrlab; - goto yyreduce; + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + /*-----------------------------. | yyreduce -- do a reduction. | `-----------------------------*/ yyreduce: - /* yyn is the number of a rule to reduce with. */ - yylen = yyr2[yyn]; + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; - /* If YYLEN is nonzero, implement the default value of the action: + /* If YYLEN is nonzero, implement the default value of the action: '$$ = $1'. Otherwise, the following line sets YYVAL to garbage. @@ -1605,316 +1702,317 @@ int yyparse(swq_parse_context *context) users should not rely upon it. Assigning to YYVAL unconditionally makes the parser a bit smaller, and it avoids a GCC warning that YYVAL may be used uninitialized. */ - yyval = yyvsp[1 - yylen]; + yyval = yyvsp[1-yylen]; + - YY_REDUCE_PRINT(yyn); - switch (yyn) + YY_REDUCE_PRINT (yyn); + switch (yyn) { - case 3: /* input: SWQT_VALUE_START value_expr */ + case 3: /* input: SWQT_VALUE_START value_expr */ { context->poRoot = yyvsp[0]; swq_fixup(context); } - break; + break; - case 4: /* input: SWQT_SELECT_START select_statement */ + case 4: /* input: SWQT_SELECT_START select_statement */ { context->poRoot = yyvsp[0]; // swq_fixup() must be done by caller } - break; + break; - case 5: /* value_expr: value_expr_non_logical */ + case 5: /* value_expr: value_expr_non_logical */ { yyval = yyvsp[0]; } - break; + break; - case 6: /* value_expr: value_expr "AND" value_expr */ + case 6: /* value_expr: value_expr "AND" value_expr */ { - yyval = swq_create_and_or_or(SWQ_AND, yyvsp[-2], yyvsp[0]); + yyval = swq_create_and_or_or( SWQ_AND, yyvsp[-2], yyvsp[0] ); } - break; + break; - case 7: /* value_expr: value_expr "OR" value_expr */ + case 7: /* value_expr: value_expr "OR" value_expr */ { - yyval = swq_create_and_or_or(SWQ_OR, yyvsp[-2], yyvsp[0]); + yyval = swq_create_and_or_or( SWQ_OR, yyvsp[-2], yyvsp[0] ); } - break; + break; - case 8: /* value_expr: "NOT" value_expr */ + case 8: /* value_expr: "NOT" value_expr */ { - yyval = new swq_expr_node(SWQ_NOT); + yyval = new swq_expr_node( SWQ_NOT ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 9: /* value_expr: value_expr '=' value_expr */ + case 9: /* value_expr: value_expr '=' value_expr */ { - yyval = new swq_expr_node(SWQ_EQ); + yyval = new swq_expr_node( SWQ_EQ ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 10: /* value_expr: value_expr '<' '>' value_expr */ + case 10: /* value_expr: value_expr '<' '>' value_expr */ { - yyval = new swq_expr_node(SWQ_NE); + yyval = new swq_expr_node( SWQ_NE ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-3]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-3] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 11: /* value_expr: value_expr '!' '=' value_expr */ + case 11: /* value_expr: value_expr '!' '=' value_expr */ { - yyval = new swq_expr_node(SWQ_NE); + yyval = new swq_expr_node( SWQ_NE ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-3]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-3] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 12: /* value_expr: value_expr '<' value_expr */ + case 12: /* value_expr: value_expr '<' value_expr */ { - yyval = new swq_expr_node(SWQ_LT); + yyval = new swq_expr_node( SWQ_LT ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 13: /* value_expr: value_expr '>' value_expr */ + case 13: /* value_expr: value_expr '>' value_expr */ { - yyval = new swq_expr_node(SWQ_GT); + yyval = new swq_expr_node( SWQ_GT ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 14: /* value_expr: value_expr '<' '=' value_expr */ + case 14: /* value_expr: value_expr '<' '=' value_expr */ { - yyval = new swq_expr_node(SWQ_LE); + yyval = new swq_expr_node( SWQ_LE ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-3]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-3] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 15: /* value_expr: value_expr '=' '<' value_expr */ + case 15: /* value_expr: value_expr '=' '<' value_expr */ { - yyval = new swq_expr_node(SWQ_LE); + yyval = new swq_expr_node( SWQ_LE ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-3]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-3] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 16: /* value_expr: value_expr '=' '>' value_expr */ + case 16: /* value_expr: value_expr '=' '>' value_expr */ { - yyval = new swq_expr_node(SWQ_LE); + yyval = new swq_expr_node( SWQ_LE ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-3]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-3] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 17: /* value_expr: value_expr '>' '=' value_expr */ + case 17: /* value_expr: value_expr '>' '=' value_expr */ { - yyval = new swq_expr_node(SWQ_GE); + yyval = new swq_expr_node( SWQ_GE ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-3]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-3] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 18: /* value_expr: value_expr "LIKE" value_expr */ + case 18: /* value_expr: value_expr "LIKE" value_expr */ { - yyval = new swq_expr_node(SWQ_LIKE); + yyval = new swq_expr_node( SWQ_LIKE ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 19: /* value_expr: value_expr "NOT" "LIKE" value_expr */ + case 19: /* value_expr: value_expr "NOT" "LIKE" value_expr */ { - swq_expr_node *like = new swq_expr_node(SWQ_LIKE); + swq_expr_node *like = new swq_expr_node( SWQ_LIKE ); like->field_type = SWQ_BOOLEAN; - like->PushSubExpression(yyvsp[-3]); - like->PushSubExpression(yyvsp[0]); + like->PushSubExpression( yyvsp[-3] ); + like->PushSubExpression( yyvsp[0] ); - yyval = new swq_expr_node(SWQ_NOT); + yyval = new swq_expr_node( SWQ_NOT ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(like); + yyval->PushSubExpression( like ); } - break; + break; - case 20: /* value_expr: value_expr "LIKE" value_expr "ESCAPE" value_expr */ + case 20: /* value_expr: value_expr "LIKE" value_expr "ESCAPE" value_expr */ { - yyval = new swq_expr_node(SWQ_LIKE); + yyval = new swq_expr_node( SWQ_LIKE ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-4]); - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-4] ); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 21: /* value_expr: value_expr "NOT" "LIKE" value_expr "ESCAPE" value_expr */ + case 21: /* value_expr: value_expr "NOT" "LIKE" value_expr "ESCAPE" value_expr */ { - swq_expr_node *like = new swq_expr_node(SWQ_LIKE); + swq_expr_node *like = new swq_expr_node( SWQ_LIKE ); like->field_type = SWQ_BOOLEAN; - like->PushSubExpression(yyvsp[-5]); - like->PushSubExpression(yyvsp[-2]); - like->PushSubExpression(yyvsp[0]); + like->PushSubExpression( yyvsp[-5] ); + like->PushSubExpression( yyvsp[-2] ); + like->PushSubExpression( yyvsp[0] ); - yyval = new swq_expr_node(SWQ_NOT); + yyval = new swq_expr_node( SWQ_NOT ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(like); + yyval->PushSubExpression( like ); } - break; + break; - case 22: /* value_expr: value_expr "ILIKE" value_expr */ + case 22: /* value_expr: value_expr "ILIKE" value_expr */ { - yyval = new swq_expr_node(SWQ_ILIKE); + yyval = new swq_expr_node( SWQ_ILIKE ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 23: /* value_expr: value_expr "NOT" "ILIKE" value_expr */ + case 23: /* value_expr: value_expr "NOT" "ILIKE" value_expr */ { - swq_expr_node *like = new swq_expr_node(SWQ_ILIKE); + swq_expr_node *like = new swq_expr_node( SWQ_ILIKE ); like->field_type = SWQ_BOOLEAN; - like->PushSubExpression(yyvsp[-3]); - like->PushSubExpression(yyvsp[0]); + like->PushSubExpression( yyvsp[-3] ); + like->PushSubExpression( yyvsp[0] ); - yyval = new swq_expr_node(SWQ_NOT); + yyval = new swq_expr_node( SWQ_NOT ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(like); + yyval->PushSubExpression( like ); } - break; + break; - case 24: /* value_expr: value_expr "ILIKE" value_expr "ESCAPE" value_expr */ + case 24: /* value_expr: value_expr "ILIKE" value_expr "ESCAPE" value_expr */ { - yyval = new swq_expr_node(SWQ_ILIKE); + yyval = new swq_expr_node( SWQ_ILIKE ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-4]); - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-4] ); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 25: /* value_expr: value_expr "NOT" "ILIKE" value_expr "ESCAPE" value_expr */ + case 25: /* value_expr: value_expr "NOT" "ILIKE" value_expr "ESCAPE" value_expr */ { - swq_expr_node *like = new swq_expr_node(SWQ_ILIKE); + swq_expr_node *like = new swq_expr_node( SWQ_ILIKE ); like->field_type = SWQ_BOOLEAN; - like->PushSubExpression(yyvsp[-5]); - like->PushSubExpression(yyvsp[-2]); - like->PushSubExpression(yyvsp[0]); + like->PushSubExpression( yyvsp[-5] ); + like->PushSubExpression( yyvsp[-2] ); + like->PushSubExpression( yyvsp[0] ); - yyval = new swq_expr_node(SWQ_NOT); + yyval = new swq_expr_node( SWQ_NOT ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(like); + yyval->PushSubExpression( like ); } - break; + break; - case 26: /* value_expr: value_expr "IN" '(' value_expr_list ')' */ + case 26: /* value_expr: value_expr "IN" '(' value_expr_list ')' */ { yyval = yyvsp[-1]; yyval->field_type = SWQ_BOOLEAN; yyval->nOperation = SWQ_IN; - yyval->PushSubExpression(yyvsp[-4]); + yyval->PushSubExpression( yyvsp[-4] ); yyval->ReverseSubExpressions(); } - break; + break; - case 27: /* value_expr: value_expr "NOT" "IN" '(' value_expr_list ')' */ + case 27: /* value_expr: value_expr "NOT" "IN" '(' value_expr_list ')' */ { swq_expr_node *in = yyvsp[-1]; in->field_type = SWQ_BOOLEAN; in->nOperation = SWQ_IN; - in->PushSubExpression(yyvsp[-5]); + in->PushSubExpression( yyvsp[-5] ); in->ReverseSubExpressions(); - yyval = new swq_expr_node(SWQ_NOT); + yyval = new swq_expr_node( SWQ_NOT ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(in); + yyval->PushSubExpression( in ); } - break; + break; - case 28: /* value_expr: value_expr "BETWEEN" value_expr_non_logical "AND" value_expr_non_logical */ + case 28: /* value_expr: value_expr "BETWEEN" value_expr_non_logical "AND" value_expr_non_logical */ { - yyval = new swq_expr_node(SWQ_BETWEEN); + yyval = new swq_expr_node( SWQ_BETWEEN ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-4]); - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval->PushSubExpression( yyvsp[-4] ); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 29: /* value_expr: value_expr "NOT" "BETWEEN" value_expr_non_logical "AND" value_expr_non_logical */ + case 29: /* value_expr: value_expr "NOT" "BETWEEN" value_expr_non_logical "AND" value_expr_non_logical */ { - swq_expr_node *between = new swq_expr_node(SWQ_BETWEEN); + swq_expr_node *between = new swq_expr_node( SWQ_BETWEEN ); between->field_type = SWQ_BOOLEAN; - between->PushSubExpression(yyvsp[-5]); - between->PushSubExpression(yyvsp[-2]); - between->PushSubExpression(yyvsp[0]); + between->PushSubExpression( yyvsp[-5] ); + between->PushSubExpression( yyvsp[-2] ); + between->PushSubExpression( yyvsp[0] ); - yyval = new swq_expr_node(SWQ_NOT); + yyval = new swq_expr_node( SWQ_NOT ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(between); + yyval->PushSubExpression( between ); } - break; + break; - case 30: /* value_expr: value_expr "IS" "NULL" */ + case 30: /* value_expr: value_expr "IS" "NULL" */ { - yyval = new swq_expr_node(SWQ_ISNULL); + yyval = new swq_expr_node( SWQ_ISNULL ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(yyvsp[-2]); + yyval->PushSubExpression( yyvsp[-2] ); } - break; + break; - case 31: /* value_expr: value_expr "IS" "NOT" "NULL" */ + case 31: /* value_expr: value_expr "IS" "NOT" "NULL" */ { - swq_expr_node *isnull = new swq_expr_node(SWQ_ISNULL); + swq_expr_node *isnull = new swq_expr_node( SWQ_ISNULL ); isnull->field_type = SWQ_BOOLEAN; - isnull->PushSubExpression(yyvsp[-3]); + isnull->PushSubExpression( yyvsp[-3] ); - yyval = new swq_expr_node(SWQ_NOT); + yyval = new swq_expr_node( SWQ_NOT ); yyval->field_type = SWQ_BOOLEAN; - yyval->PushSubExpression(isnull); + yyval->PushSubExpression( isnull ); } - break; + break; - case 32: /* value_expr_list: value_expr ',' value_expr_list */ + case 32: /* value_expr_list: value_expr ',' value_expr_list */ { yyval = yyvsp[0]; - yyvsp[0]->PushSubExpression(yyvsp[-2]); + yyvsp[0]->PushSubExpression( yyvsp[-2] ); } - break; + break; - case 33: /* value_expr_list: value_expr */ - { - yyval = new swq_expr_node(SWQ_ARGUMENT_LIST); /* temporary value */ - yyval->PushSubExpression(yyvsp[0]); + case 33: /* value_expr_list: value_expr */ + { + yyval = new swq_expr_node( SWQ_ARGUMENT_LIST ); /* temporary value */ + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 34: /* field_value: "identifier" */ + case 36: /* field_value: identifier */ { yyval = yyvsp[0]; // validation deferred. yyval->eNodeType = SNT_COLUMN; yyval->field_index = -1; yyval->table_index = -1; } - break; + break; - case 35: /* field_value: "identifier" '.' "identifier" */ + case 37: /* field_value: identifier '.' identifier */ { yyval = yyvsp[-2]; // validation deferred. yyval->eNodeType = SNT_COLUMN; @@ -1925,63 +2023,61 @@ int yyparse(swq_parse_context *context) delete yyvsp[0]; yyvsp[0] = nullptr; } - break; + break; - case 36: /* value_expr_non_logical: "integer number" */ + case 38: /* value_expr_non_logical: "integer number" */ { yyval = yyvsp[0]; } - break; + break; - case 37: /* value_expr_non_logical: "floating point number" */ + case 39: /* value_expr_non_logical: "floating point number" */ { yyval = yyvsp[0]; } - break; + break; - case 38: /* value_expr_non_logical: "string" */ + case 40: /* value_expr_non_logical: "string" */ { yyval = yyvsp[0]; } - break; + break; - case 39: /* value_expr_non_logical: field_value */ + case 41: /* value_expr_non_logical: field_value */ { yyval = yyvsp[0]; } - break; + break; - case 40: /* value_expr_non_logical: '(' value_expr ')' */ + case 42: /* value_expr_non_logical: '(' value_expr ')' */ { yyval = yyvsp[-1]; } - break; + break; - case 41: /* value_expr_non_logical: "NULL" */ + case 43: /* value_expr_non_logical: "NULL" */ { - yyval = new swq_expr_node(static_cast(nullptr)); + yyval = new swq_expr_node(static_cast(nullptr)); } - break; + break; - case 42: /* value_expr_non_logical: '-' value_expr_non_logical */ + case 44: /* value_expr_non_logical: '-' value_expr_non_logical */ { if (yyvsp[0]->eNodeType == SNT_CONSTANT) { - if (yyvsp[0]->field_type == SWQ_FLOAT && + if( yyvsp[0]->field_type == SWQ_FLOAT && yyvsp[0]->string_value && - strcmp(yyvsp[0]->string_value, "9223372036854775808") == 0) + strcmp(yyvsp[0]->string_value, "9223372036854775808") == 0 ) { yyval = yyvsp[0]; yyval->field_type = SWQ_INTEGER64; yyval->int_value = std::numeric_limits::min(); - yyval->float_value = static_cast( - std::numeric_limits::min()); + yyval->float_value = static_cast(std::numeric_limits::min()); } // - (-9223372036854775808) cannot be represented on int64 // the classic overflow is that its negation is itself. - else if (yyvsp[0]->field_type == SWQ_INTEGER64 && - yyvsp[0]->int_value == - std::numeric_limits::min()) + else if( yyvsp[0]->field_type == SWQ_INTEGER64 && + yyvsp[0]->int_value == std::numeric_limits::min() ) { yyval = yyvsp[0]; } @@ -1994,61 +2090,61 @@ int yyparse(swq_parse_context *context) } else { - yyval = new swq_expr_node(SWQ_MULTIPLY); - yyval->PushSubExpression(new swq_expr_node(-1)); - yyval->PushSubExpression(yyvsp[0]); + yyval = new swq_expr_node( SWQ_MULTIPLY ); + yyval->PushSubExpression( new swq_expr_node(-1) ); + yyval->PushSubExpression( yyvsp[0] ); } } - break; + break; - case 43: /* value_expr_non_logical: value_expr_non_logical '+' value_expr_non_logical */ + case 45: /* value_expr_non_logical: value_expr_non_logical '+' value_expr_non_logical */ { - yyval = new swq_expr_node(SWQ_ADD); - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval = new swq_expr_node( SWQ_ADD ); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 44: /* value_expr_non_logical: value_expr_non_logical '-' value_expr_non_logical */ + case 46: /* value_expr_non_logical: value_expr_non_logical '-' value_expr_non_logical */ { - yyval = new swq_expr_node(SWQ_SUBTRACT); - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval = new swq_expr_node( SWQ_SUBTRACT ); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 45: /* value_expr_non_logical: value_expr_non_logical '*' value_expr_non_logical */ + case 47: /* value_expr_non_logical: value_expr_non_logical '*' value_expr_non_logical */ { - yyval = new swq_expr_node(SWQ_MULTIPLY); - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval = new swq_expr_node( SWQ_MULTIPLY ); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 46: /* value_expr_non_logical: value_expr_non_logical '/' value_expr_non_logical */ + case 48: /* value_expr_non_logical: value_expr_non_logical '/' value_expr_non_logical */ { - yyval = new swq_expr_node(SWQ_DIVIDE); - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval = new swq_expr_node( SWQ_DIVIDE ); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 47: /* value_expr_non_logical: value_expr_non_logical '%' value_expr_non_logical */ + case 49: /* value_expr_non_logical: value_expr_non_logical '%' value_expr_non_logical */ { - yyval = new swq_expr_node(SWQ_MODULUS); - yyval->PushSubExpression(yyvsp[-2]); - yyval->PushSubExpression(yyvsp[0]); + yyval = new swq_expr_node( SWQ_MODULUS ); + yyval->PushSubExpression( yyvsp[-2] ); + yyval->PushSubExpression( yyvsp[0] ); } - break; + break; - case 48: /* value_expr_non_logical: "identifier" '(' value_expr_list ')' */ + case 50: /* value_expr_non_logical: identifier '(' value_expr_list ')' */ { const swq_operation *poOp = - swq_op_registrar::GetOperator(yyvsp[-3]->string_value); + swq_op_registrar::GetOperator( yyvsp[-3]->string_value ); - if (poOp == nullptr) + if( poOp == nullptr ) { - if (context->bAcceptCustomFuncs) + if( context->bAcceptCustomFuncs ) { yyval = yyvsp[-1]; yyval->eNodeType = SNT_OPERATION; @@ -2059,9 +2155,9 @@ int yyparse(swq_parse_context *context) } else { - CPLError(CE_Failure, CPLE_AppDefined, - "Undefined function '%s' used.", - yyvsp[-3]->string_value); + CPLError( CE_Failure, CPLE_AppDefined, + "Undefined function '%s' used.", + yyvsp[-3]->string_value ); delete yyvsp[-3]; delete yyvsp[-1]; YYERROR; @@ -2076,125 +2172,122 @@ int yyparse(swq_parse_context *context) delete yyvsp[-3]; } } - break; + break; - case 49: /* value_expr_non_logical: "CAST" '(' value_expr "AS" type_def ')' */ + case 51: /* value_expr_non_logical: "CAST" '(' value_expr "AS" type_def ')' */ { yyval = yyvsp[-1]; - yyval->PushSubExpression(yyvsp[-3]); + yyval->PushSubExpression( yyvsp[-3] ); yyval->ReverseSubExpressions(); } - break; + break; - case 50: /* type_def: "identifier" */ - { - yyval = new swq_expr_node(SWQ_CAST); - yyval->PushSubExpression(yyvsp[0]); - } - break; - - case 51: /* type_def: "identifier" '(' "integer number" ')' */ - { - yyval = new swq_expr_node(SWQ_CAST); - yyval->PushSubExpression(yyvsp[-1]); - yyval->PushSubExpression(yyvsp[-3]); - } - break; + case 52: /* type_def: identifier */ + { + yyval = new swq_expr_node( SWQ_CAST ); + yyval->PushSubExpression( yyvsp[0] ); + } + break; - case 52: /* type_def: "identifier" '(' "integer number" ',' "integer number" ')' */ - { - yyval = new swq_expr_node(SWQ_CAST); - yyval->PushSubExpression(yyvsp[-1]); - yyval->PushSubExpression(yyvsp[-3]); - yyval->PushSubExpression(yyvsp[-5]); - } - break; + case 53: /* type_def: identifier '(' "integer number" ')' */ + { + yyval = new swq_expr_node( SWQ_CAST ); + yyval->PushSubExpression( yyvsp[-1] ); + yyval->PushSubExpression( yyvsp[-3] ); + } + break; - case 53: /* type_def: "identifier" '(' "identifier" ')' */ - { - OGRwkbGeometryType eType = - OGRFromOGCGeomType(yyvsp[-1]->string_value); - if (!EQUAL(yyvsp[-3]->string_value, "GEOMETRY") || - (wkbFlatten(eType) == wkbUnknown && - !STARTS_WITH_CI(yyvsp[-1]->string_value, "GEOMETRY"))) - { - yyerror(context, "syntax error"); - delete yyvsp[-3]; - delete yyvsp[-1]; - YYERROR; - } - yyval = new swq_expr_node(SWQ_CAST); - yyval->PushSubExpression(yyvsp[-1]); - yyval->PushSubExpression(yyvsp[-3]); - } - break; + case 54: /* type_def: identifier '(' "integer number" ',' "integer number" ')' */ + { + yyval = new swq_expr_node( SWQ_CAST ); + yyval->PushSubExpression( yyvsp[-1] ); + yyval->PushSubExpression( yyvsp[-3] ); + yyval->PushSubExpression( yyvsp[-5] ); + } + break; - case 54: /* type_def: "identifier" '(' "identifier" ',' "integer number" ')' */ + case 55: /* type_def: identifier '(' identifier ')' */ + { + OGRwkbGeometryType eType = OGRFromOGCGeomType(yyvsp[-1]->string_value); + if( !EQUAL(yyvsp[-3]->string_value, "GEOMETRY") || + (wkbFlatten(eType) == wkbUnknown && + !STARTS_WITH_CI(yyvsp[-1]->string_value, "GEOMETRY")) ) { - OGRwkbGeometryType eType = - OGRFromOGCGeomType(yyvsp[-3]->string_value); - if (!EQUAL(yyvsp[-5]->string_value, "GEOMETRY") || - (wkbFlatten(eType) == wkbUnknown && - !STARTS_WITH_CI(yyvsp[-3]->string_value, "GEOMETRY"))) - { - yyerror(context, "syntax error"); - delete yyvsp[-5]; - delete yyvsp[-3]; - delete yyvsp[-1]; - YYERROR; - } - yyval = new swq_expr_node(SWQ_CAST); - yyval->PushSubExpression(yyvsp[-1]); - yyval->PushSubExpression(yyvsp[-3]); - yyval->PushSubExpression(yyvsp[-5]); + yyerror (context, "syntax error"); + delete yyvsp[-3]; + delete yyvsp[-1]; + YYERROR; } - break; + yyval = new swq_expr_node( SWQ_CAST ); + yyval->PushSubExpression( yyvsp[-1] ); + yyval->PushSubExpression( yyvsp[-3] ); + } + break; - case 57: /* select_core: "SELECT" select_field_list "FROM" table_def opt_joins opt_where opt_order_by opt_limit opt_offset */ + case 56: /* type_def: identifier '(' identifier ',' "integer number" ')' */ + { + OGRwkbGeometryType eType = OGRFromOGCGeomType(yyvsp[-3]->string_value); + if( !EQUAL(yyvsp[-5]->string_value, "GEOMETRY") || + (wkbFlatten(eType) == wkbUnknown && + !STARTS_WITH_CI(yyvsp[-3]->string_value, "GEOMETRY")) ) { + yyerror (context, "syntax error"); delete yyvsp[-5]; + delete yyvsp[-3]; + delete yyvsp[-1]; + YYERROR; } - break; + yyval = new swq_expr_node( SWQ_CAST ); + yyval->PushSubExpression( yyvsp[-1] ); + yyval->PushSubExpression( yyvsp[-3] ); + yyval->PushSubExpression( yyvsp[-5] ); + } + break; - case 58: /* select_core: "SELECT" "DISTINCT" select_field_list "FROM" table_def opt_joins opt_where opt_order_by opt_limit opt_offset */ - { - context->poCurSelect->query_mode = SWQM_DISTINCT_LIST; - delete yyvsp[-5]; - } - break; + case 59: /* select_core: "SELECT" select_field_list "FROM" table_def opt_joins opt_where opt_order_by opt_limit opt_offset */ + { + delete yyvsp[-5]; + } + break; - case 61: /* union_all: "UNION" "ALL" */ - { - swq_select *poNewSelect = new swq_select(); - context->poCurSelect->PushUnionAll(poNewSelect); - context->poCurSelect = poNewSelect; - } - break; + case 60: /* select_core: "SELECT" "DISTINCT" select_field_list "FROM" table_def opt_joins opt_where opt_order_by opt_limit opt_offset */ + { + context->poCurSelect->query_mode = SWQM_DISTINCT_LIST; + delete yyvsp[-5]; + } + break; - case 64: /* exclude_field: field_value */ + case 63: /* union_all: "UNION" "ALL" */ + { + swq_select* poNewSelect = new swq_select(); + context->poCurSelect->PushUnionAll(poNewSelect); + context->poCurSelect = poNewSelect; + } + break; + + case 66: /* exclude_field: field_value */ { - if (!context->poCurSelect->PushExcludeField(yyvsp[0])) + if ( !context->poCurSelect->PushExcludeField( yyvsp[0] ) ) { delete yyvsp[0]; YYERROR; } } - break; + break; - case 69: /* column_spec: value_expr */ + case 71: /* column_spec: value_expr */ { - if (!context->poCurSelect->PushField(yyvsp[0])) + if( !context->poCurSelect->PushField( yyvsp[0], nullptr, false, false ) ) { delete yyvsp[0]; YYERROR; } } - break; + break; - case 70: /* column_spec: value_expr as_clause */ + case 72: /* column_spec: value_expr as_clause_with_hidden */ { - if (!context->poCurSelect->PushField(yyvsp[-1], - yyvsp[0]->string_value)) + if( !context->poCurSelect->PushField( yyvsp[-1], yyvsp[0]->string_value, false, yyvsp[0]->bHidden ) ) { delete yyvsp[-1]; delete yyvsp[0]; @@ -2202,41 +2295,41 @@ int yyparse(swq_parse_context *context) } delete yyvsp[0]; } - break; + break; - case 71: /* column_spec: '*' except_or_exclude '(' exclude_field_list ')' */ + case 73: /* column_spec: '*' except_or_exclude '(' exclude_field_list ')' */ { swq_expr_node *poNode = new swq_expr_node(); poNode->eNodeType = SNT_COLUMN; - poNode->string_value = CPLStrdup("*"); + poNode->string_value = CPLStrdup( "*" ); poNode->table_index = -1; poNode->field_index = -1; - if (!context->poCurSelect->PushField(poNode)) + if( !context->poCurSelect->PushField( poNode, nullptr, false, false ) ) { delete poNode; YYERROR; } } - break; + break; - case 72: /* column_spec: '*' */ + case 74: /* column_spec: '*' */ { swq_expr_node *poNode = new swq_expr_node(); poNode->eNodeType = SNT_COLUMN; - poNode->string_value = CPLStrdup("*"); + poNode->string_value = CPLStrdup( "*" ); poNode->table_index = -1; poNode->field_index = -1; - if (!context->poCurSelect->PushField(poNode)) + if( !context->poCurSelect->PushField( poNode, nullptr, false, false ) ) { delete poNode; YYERROR; } } - break; + break; - case 73: /* column_spec: "identifier" '.' '*' */ + case 75: /* column_spec: identifier '.' '*' */ { CPLString osTableName = yyvsp[-2]->string_value; @@ -2245,26 +2338,27 @@ int yyparse(swq_parse_context *context) swq_expr_node *poNode = new swq_expr_node(); poNode->eNodeType = SNT_COLUMN; - poNode->table_name = CPLStrdup(osTableName); - poNode->string_value = CPLStrdup("*"); + poNode->table_name = CPLStrdup(osTableName ); + poNode->string_value = CPLStrdup( "*" ); poNode->table_index = -1; poNode->field_index = -1; - if (!context->poCurSelect->PushField(poNode)) + if( !context->poCurSelect->PushField( poNode, nullptr, false, false ) ) { delete poNode; YYERROR; } } - break; + break; - case 74: /* column_spec: "identifier" '(' '*' ')' */ + case 76: /* column_spec: identifier '(' '*' ')' */ { - // special case for COUNT(*), confirm it. - if (!EQUAL(yyvsp[-3]->string_value, "COUNT")) + // special case for COUNT(*), confirm it. + if( !EQUAL(yyvsp[-3]->string_value, "COUNT") ) { - CPLError(CE_Failure, CPLE_AppDefined, - "Syntax Error with %s(*).", yyvsp[-3]->string_value); + CPLError( CE_Failure, CPLE_AppDefined, + "Syntax Error with %s(*).", + yyvsp[-3]->string_value ); delete yyvsp[-3]; YYERROR; } @@ -2274,28 +2368,29 @@ int yyparse(swq_parse_context *context) swq_expr_node *poNode = new swq_expr_node(); poNode->eNodeType = SNT_COLUMN; - poNode->string_value = CPLStrdup("*"); + poNode->string_value = CPLStrdup( "*" ); poNode->table_index = -1; poNode->field_index = -1; - swq_expr_node *count = new swq_expr_node(SWQ_COUNT); - count->PushSubExpression(poNode); + swq_expr_node *count = new swq_expr_node( SWQ_COUNT ); + count->PushSubExpression( poNode ); - if (!context->poCurSelect->PushField(count)) + if( !context->poCurSelect->PushField( count, nullptr, false, false ) ) { delete count; YYERROR; } } - break; + break; - case 75: /* column_spec: "identifier" '(' '*' ')' as_clause */ + case 77: /* column_spec: identifier '(' '*' ')' as_clause */ { - // special case for COUNT(*), confirm it. - if (!EQUAL(yyvsp[-4]->string_value, "COUNT")) + // special case for COUNT(*), confirm it. + if( !EQUAL(yyvsp[-4]->string_value, "COUNT") ) { - CPLError(CE_Failure, CPLE_AppDefined, - "Syntax Error with %s(*).", yyvsp[-4]->string_value); + CPLError( CE_Failure, CPLE_AppDefined, + "Syntax Error with %s(*).", + yyvsp[-4]->string_value ); delete yyvsp[-4]; delete yyvsp[0]; YYERROR; @@ -2306,14 +2401,14 @@ int yyparse(swq_parse_context *context) swq_expr_node *poNode = new swq_expr_node(); poNode->eNodeType = SNT_COLUMN; - poNode->string_value = CPLStrdup("*"); + poNode->string_value = CPLStrdup( "*" ); poNode->table_index = -1; poNode->field_index = -1; - swq_expr_node *count = new swq_expr_node(SWQ_COUNT); - count->PushSubExpression(poNode); + swq_expr_node *count = new swq_expr_node( SWQ_COUNT ); + count->PushSubExpression( poNode ); - if (!context->poCurSelect->PushField(count, yyvsp[0]->string_value)) + if( !context->poCurSelect->PushField( count, yyvsp[0]->string_value, false, yyvsp[0]->bHidden ) ) { delete count; delete yyvsp[0]; @@ -2322,53 +2417,51 @@ int yyparse(swq_parse_context *context) delete yyvsp[0]; } - break; + break; - case 76: /* column_spec: "identifier" '(' "DISTINCT" field_value ')' */ + case 78: /* column_spec: identifier '(' "DISTINCT" field_value ')' */ { - // special case for COUNT(DISTINCT x), confirm it. - if (!EQUAL(yyvsp[-4]->string_value, "COUNT")) + // special case for COUNT(DISTINCT x), confirm it. + if( !EQUAL(yyvsp[-4]->string_value, "COUNT") ) { CPLError( CE_Failure, CPLE_AppDefined, - "DISTINCT keyword can only be used in COUNT() operator."); + "DISTINCT keyword can only be used in COUNT() operator." ); delete yyvsp[-4]; delete yyvsp[-1]; - YYERROR; + YYERROR; } delete yyvsp[-4]; - swq_expr_node *count = new swq_expr_node(SWQ_COUNT); - count->PushSubExpression(yyvsp[-1]); + swq_expr_node *count = new swq_expr_node( SWQ_COUNT ); + count->PushSubExpression( yyvsp[-1] ); - if (!context->poCurSelect->PushField(count, nullptr, TRUE)) + if( !context->poCurSelect->PushField( count, nullptr, true, false ) ) { delete count; YYERROR; } } - break; + break; - case 77: /* column_spec: "identifier" '(' "DISTINCT" field_value ')' as_clause */ + case 79: /* column_spec: identifier '(' "DISTINCT" field_value ')' as_clause */ { // special case for COUNT(DISTINCT x), confirm it. - if (!EQUAL(yyvsp[-5]->string_value, "COUNT")) + if( !EQUAL(yyvsp[-5]->string_value, "COUNT") ) { - CPLError( - CE_Failure, CPLE_AppDefined, - "DISTINCT keyword can only be used in COUNT() operator."); + CPLError( CE_Failure, CPLE_AppDefined, + "DISTINCT keyword can only be used in COUNT() operator." ); delete yyvsp[-5]; delete yyvsp[-2]; delete yyvsp[0]; YYERROR; } - swq_expr_node *count = new swq_expr_node(SWQ_COUNT); - count->PushSubExpression(yyvsp[-2]); + swq_expr_node *count = new swq_expr_node( SWQ_COUNT ); + count->PushSubExpression( yyvsp[-2] ); - if (!context->poCurSelect->PushField(count, yyvsp[0]->string_value, - TRUE)) + if( !context->poCurSelect->PushField( count, yyvsp[0]->string_value, true, yyvsp[0]->bHidden ) ) { delete yyvsp[-5]; delete count; @@ -2379,153 +2472,167 @@ int yyparse(swq_parse_context *context) delete yyvsp[-5]; delete yyvsp[0]; } - break; + break; - case 78: /* as_clause: "AS" "identifier" */ + case 80: /* as_clause: "AS" identifier */ { - delete yyvsp[-1]; yyval = yyvsp[0]; + yyvsp[0] = nullptr; } - break; + break; + + case 83: /* as_clause_with_hidden: as_clause "HIDDEN" */ + { + yyval = yyvsp[-1]; + yyvsp[-1] = nullptr; + delete yyvsp[0]; + yyvsp[0] = nullptr; + yyval->bHidden = true; + } + break; - case 81: /* opt_where: "WHERE" value_expr */ + case 85: /* opt_where: "WHERE" value_expr */ { context->poCurSelect->where_expr = yyvsp[0]; } - break; + break; - case 83: /* opt_joins: "JOIN" table_def "ON" value_expr opt_joins */ + case 87: /* opt_joins: "JOIN" table_def "ON" value_expr opt_joins */ { - context->poCurSelect->PushJoin( - static_cast(yyvsp[-3]->int_value), yyvsp[-1]); + context->poCurSelect->PushJoin( static_cast(yyvsp[-3]->int_value), + yyvsp[-1] ); delete yyvsp[-3]; } - break; + break; - case 84: /* opt_joins: "LEFT" "JOIN" table_def "ON" value_expr opt_joins */ + case 88: /* opt_joins: "LEFT" "JOIN" table_def "ON" value_expr opt_joins */ { - context->poCurSelect->PushJoin( - static_cast(yyvsp[-3]->int_value), yyvsp[-1]); + context->poCurSelect->PushJoin( static_cast(yyvsp[-3]->int_value), + yyvsp[-1] ); delete yyvsp[-3]; } - break; + break; - case 89: /* sort_spec: field_value */ + case 93: /* sort_spec: field_value */ { - context->poCurSelect->PushOrderBy(yyvsp[0]->table_name, - yyvsp[0]->string_value, TRUE); + context->poCurSelect->PushOrderBy( yyvsp[0]->table_name, yyvsp[0]->string_value, TRUE ); delete yyvsp[0]; yyvsp[0] = nullptr; } - break; + break; - case 90: /* sort_spec: field_value "ASC" */ + case 94: /* sort_spec: field_value "ASC" */ { - context->poCurSelect->PushOrderBy(yyvsp[-1]->table_name, - yyvsp[-1]->string_value, TRUE); + context->poCurSelect->PushOrderBy( yyvsp[-1]->table_name, yyvsp[-1]->string_value, TRUE ); delete yyvsp[-1]; yyvsp[-1] = nullptr; } - break; + break; - case 91: /* sort_spec: field_value "DESC" */ + case 95: /* sort_spec: field_value "DESC" */ { - context->poCurSelect->PushOrderBy(yyvsp[-1]->table_name, - yyvsp[-1]->string_value, FALSE); + context->poCurSelect->PushOrderBy( yyvsp[-1]->table_name, yyvsp[-1]->string_value, FALSE ); delete yyvsp[-1]; yyvsp[-1] = nullptr; } - break; + break; - case 93: /* opt_limit: "LIMIT" "integer number" */ - { - context->poCurSelect->SetLimit(yyvsp[0]->int_value); - delete yyvsp[0]; - yyvsp[0] = nullptr; - } - break; + case 97: /* opt_limit: "LIMIT" "integer number" */ + { + context->poCurSelect->SetLimit( yyvsp[0]->int_value ); + delete yyvsp[0]; + yyvsp[0] = nullptr; + } + break; - case 95: /* opt_offset: "OFFSET" "integer number" */ - { - context->poCurSelect->SetOffset(yyvsp[0]->int_value); - delete yyvsp[0]; - yyvsp[0] = nullptr; - } - break; + case 99: /* opt_offset: "OFFSET" "integer number" */ + { + context->poCurSelect->SetOffset( yyvsp[0]->int_value ); + delete yyvsp[0]; + yyvsp[0] = nullptr; + } + break; - case 96: /* table_def: "identifier" */ - { - const int iTable = context->poCurSelect->PushTableDef( - nullptr, yyvsp[0]->string_value, nullptr); - delete yyvsp[0]; + case 100: /* table_def: identifier */ + { + const int iTable = + context->poCurSelect->PushTableDef( nullptr, yyvsp[0]->string_value, + nullptr ); + delete yyvsp[0]; - yyval = new swq_expr_node(iTable); - } - break; + yyval = new swq_expr_node( iTable ); + } + break; - case 97: /* table_def: "identifier" as_clause */ - { - const int iTable = context->poCurSelect->PushTableDef( - nullptr, yyvsp[-1]->string_value, yyvsp[0]->string_value); - delete yyvsp[-1]; - delete yyvsp[0]; + case 101: /* table_def: identifier as_clause */ + { + const int iTable = + context->poCurSelect->PushTableDef( nullptr, yyvsp[-1]->string_value, + yyvsp[0]->string_value ); + delete yyvsp[-1]; + delete yyvsp[0]; - yyval = new swq_expr_node(iTable); - } - break; + yyval = new swq_expr_node( iTable ); + } + break; - case 98: /* table_def: "string" '.' "identifier" */ - { - const int iTable = context->poCurSelect->PushTableDef( - yyvsp[-2]->string_value, yyvsp[0]->string_value, nullptr); - delete yyvsp[-2]; - delete yyvsp[0]; + case 102: /* table_def: "string" '.' identifier */ + { + const int iTable = + context->poCurSelect->PushTableDef( yyvsp[-2]->string_value, + yyvsp[0]->string_value, nullptr ); + delete yyvsp[-2]; + delete yyvsp[0]; - yyval = new swq_expr_node(iTable); - } - break; + yyval = new swq_expr_node( iTable ); + } + break; - case 99: /* table_def: "string" '.' "identifier" as_clause */ - { - const int iTable = context->poCurSelect->PushTableDef( - yyvsp[-3]->string_value, yyvsp[-1]->string_value, - yyvsp[0]->string_value); - delete yyvsp[-3]; - delete yyvsp[-1]; - delete yyvsp[0]; + case 103: /* table_def: "string" '.' identifier as_clause */ + { + const int iTable = + context->poCurSelect->PushTableDef( yyvsp[-3]->string_value, + yyvsp[-1]->string_value, + yyvsp[0]->string_value ); + delete yyvsp[-3]; + delete yyvsp[-1]; + delete yyvsp[0]; + + yyval = new swq_expr_node( iTable ); + } + break; - yyval = new swq_expr_node(iTable); - } - break; + case 104: /* table_def: identifier '.' identifier */ + { + const int iTable = + context->poCurSelect->PushTableDef( yyvsp[-2]->string_value, + yyvsp[0]->string_value, nullptr ); + delete yyvsp[-2]; + delete yyvsp[0]; - case 100: /* table_def: "identifier" '.' "identifier" */ - { - const int iTable = context->poCurSelect->PushTableDef( - yyvsp[-2]->string_value, yyvsp[0]->string_value, nullptr); - delete yyvsp[-2]; - delete yyvsp[0]; + yyval = new swq_expr_node( iTable ); + } + break; - yyval = new swq_expr_node(iTable); - } - break; + case 105: /* table_def: identifier '.' identifier as_clause */ + { + const int iTable = + context->poCurSelect->PushTableDef( yyvsp[-3]->string_value, + yyvsp[-1]->string_value, + yyvsp[0]->string_value ); + delete yyvsp[-3]; + delete yyvsp[-1]; + delete yyvsp[0]; + + yyval = new swq_expr_node( iTable ); + } + break; - case 101: /* table_def: "identifier" '.' "identifier" as_clause */ - { - const int iTable = context->poCurSelect->PushTableDef( - yyvsp[-3]->string_value, yyvsp[-1]->string_value, - yyvsp[0]->string_value); - delete yyvsp[-3]; - delete yyvsp[-1]; - delete yyvsp[0]; - yyval = new swq_expr_node(iTable); - } - break; - default: - break; + default: break; } - /* User semantic actions sometimes alter yychar, and that requires + /* User semantic actions sometimes alter yychar, and that requires that yytoken be updated with the new translation. We take the approach of translating immediately before every use of yytoken. One alternative is translating here after every semantic action, @@ -2536,203 +2643,212 @@ int yyparse(swq_parse_context *context) case of YYERROR or YYBACKUP, subsequent parser actions might lead to an incorrect destructor call or verbose syntax error message before the lookahead is translated. */ - YY_SYMBOL_PRINT("-> $$ =", YY_CAST(yysymbol_kind_t, yyr1[yyn]), &yyval, - &yyloc); + YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc); - YYPOPSTACK(yylen); - yylen = 0; + YYPOPSTACK (yylen); + yylen = 0; - *++yyvsp = yyval; + *++yyvsp = yyval; - /* Now 'shift' the result of the reduction. Determine what state + /* Now 'shift' the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ - { - const int yylhs = yyr1[yyn] - YYNTOKENS; - const int yyi = yypgoto[yylhs] + *yyssp; - yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp - ? yytable[yyi] - : yydefgoto[yylhs]); - } + { + const int yylhs = yyr1[yyn] - YYNTOKENS; + const int yyi = yypgoto[yylhs] + *yyssp; + yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp + ? yytable[yyi] + : yydefgoto[yylhs]); + } + + goto yynewstate; - goto yynewstate; /*--------------------------------------. | yyerrlab -- here on detecting error. | `--------------------------------------*/ yyerrlab: - /* Make sure we have latest lookahead translation. See comments at + /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ - yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE(yychar); - /* If not already recovering from an error, report this error. */ - if (!yyerrstatus) + yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar); + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) { - ++yynerrs; - (void)yynerrs; - { - yypcontext_t yyctx = {yyssp, yytoken}; - char const *yymsgp = YY_("syntax error"); - int yysyntax_error_status; - yysyntax_error_status = - yysyntax_error(&yymsg_alloc, &yymsg, &yyctx); - if (yysyntax_error_status == 0) + ++yynerrs; (void)yynerrs; + { + yypcontext_t yyctx + = {yyssp, yytoken}; + char const *yymsgp = YY_("syntax error"); + int yysyntax_error_status; + yysyntax_error_status = yysyntax_error (&yymsg_alloc, &yymsg, &yyctx); + if (yysyntax_error_status == 0) + yymsgp = yymsg; + else if (yysyntax_error_status == -1) + { + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = YY_CAST (char *, + YYSTACK_ALLOC (YY_CAST (YYSIZE_T, yymsg_alloc))); + if (yymsg) + { + yysyntax_error_status + = yysyntax_error (&yymsg_alloc, &yymsg, &yyctx); yymsgp = yymsg; - else if (yysyntax_error_status == -1) - { - if (yymsg != yymsgbuf) - YYSTACK_FREE(yymsg); - yymsg = YY_CAST(char *, - YYSTACK_ALLOC(YY_CAST(YYSIZE_T, yymsg_alloc))); - if (yymsg) - { - yysyntax_error_status = - yysyntax_error(&yymsg_alloc, &yymsg, &yyctx); - yymsgp = yymsg; - } - else - { - yymsg = yymsgbuf; - yymsg_alloc = sizeof yymsgbuf; - yysyntax_error_status = YYENOMEM; - } - } - yyerror(context, yymsgp); - if (yysyntax_error_status == YYENOMEM) - YYNOMEM; - } + } + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + yysyntax_error_status = YYENOMEM; + } + } + yyerror (context, yymsgp); + if (yysyntax_error_status == YYENOMEM) + YYNOMEM; + } } - if (yyerrstatus == 3) + if (yyerrstatus == 3) { - /* If just tried and failed to reuse lookahead token after an + /* If just tried and failed to reuse lookahead token after an error, discard it. */ - if (yychar <= END) + if (yychar <= END) { - /* Return failure if at end of input. */ - if (yychar == END) - YYABORT; + /* Return failure if at end of input. */ + if (yychar == END) + YYABORT; } - else + else { - yydestruct("Error: discarding", yytoken, &yylval, context); - yychar = YYEMPTY; + yydestruct ("Error: discarding", + yytoken, &yylval, context); + yychar = YYEMPTY; } } - /* Else will try to reuse lookahead token after shifting the error + /* Else will try to reuse lookahead token after shifting the error token. */ - goto yyerrlab1; + goto yyerrlab1; + /*---------------------------------------------------. | yyerrorlab -- error raised explicitly by YYERROR. | `---------------------------------------------------*/ yyerrorlab: - /* Pacify compilers when the user code never invokes YYERROR and the + /* Pacify compilers when the user code never invokes YYERROR and the label yyerrorlab therefore never appears in user code. */ - if (0) - YYERROR; - ++yynerrs; - (void)yynerrs; + if (0) + YYERROR; + ++yynerrs; (void)yynerrs; - /* Do not reclaim the symbols of the rule whose action triggered + /* Do not reclaim the symbols of the rule whose action triggered this YYERROR. */ - YYPOPSTACK(yylen); - yylen = 0; - YY_STACK_PRINT(yyss, yyssp); - yystate = *yyssp; - goto yyerrlab1; + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + /*-------------------------------------------------------------. | yyerrlab1 -- common code for both syntax error and YYERROR. | `-------------------------------------------------------------*/ yyerrlab1: - yyerrstatus = 3; /* Each real token shifted decrements this. */ + yyerrstatus = 3; /* Each real token shifted decrements this. */ - /* Pop stack until we find a state that shifts the error token. */ - for (;;) + /* Pop stack until we find a state that shifts the error token. */ + for (;;) { - yyn = yypact[yystate]; - if (!yypact_value_is_default(yyn)) + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) { - yyn += YYSYMBOL_YYerror; - if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) + yyn += YYSYMBOL_YYerror; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) { - yyn = yytable[yyn]; - if (0 < yyn) - break; + yyn = yytable[yyn]; + if (0 < yyn) + break; } } - /* Pop the current state because it cannot handle the error token. */ - if (yyssp == yyss) - YYABORT; + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + - yydestruct("Error: popping", YY_ACCESSING_SYMBOL(yystate), yyvsp, - context); - YYPOPSTACK(1); - yystate = *yyssp; - YY_STACK_PRINT(yyss, yyssp); + yydestruct ("Error: popping", + YY_ACCESSING_SYMBOL (yystate), yyvsp, context); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); } - YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN - *++yyvsp = yylval; - YY_IGNORE_MAYBE_UNINITIALIZED_END + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END - /* Shift the error token. */ - YY_SYMBOL_PRINT("Shifting", YY_ACCESSING_SYMBOL(yyn), yyvsp, yylsp); - yystate = yyn; - goto yynewstate; + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + /*-------------------------------------. | yyacceptlab -- YYACCEPT comes here. | `-------------------------------------*/ yyacceptlab: - yyresult = 0; - goto yyreturnlab; + yyresult = 0; + goto yyreturnlab; + /*-----------------------------------. | yyabortlab -- YYABORT comes here. | `-----------------------------------*/ yyabortlab: - yyresult = 1; - goto yyreturnlab; + yyresult = 1; + goto yyreturnlab; + /*-----------------------------------------------------------. | yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. | `-----------------------------------------------------------*/ yyexhaustedlab: - yyerror(context, YY_("memory exhausted")); - yyresult = 2; - goto yyreturnlab; + yyerror (context, YY_("memory exhausted")); + yyresult = 2; + goto yyreturnlab; + /*----------------------------------------------------------. | yyreturnlab -- parsing is finished, clean up and return. | `----------------------------------------------------------*/ yyreturnlab: - if (yychar != YYEMPTY) + if (yychar != YYEMPTY) { - /* Make sure we have latest lookahead translation. See comments at + /* Make sure we have latest lookahead translation. See comments at user semantic actions for why this is necessary. */ - yytoken = YYTRANSLATE(yychar); - yydestruct("Cleanup: discarding lookahead", yytoken, &yylval, context); + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, context); } - /* Do not reclaim the symbols of the rule whose action triggered + /* Do not reclaim the symbols of the rule whose action triggered this YYABORT or YYACCEPT. */ - YYPOPSTACK(yylen); - YY_STACK_PRINT(yyss, yyssp); - while (yyssp != yyss) + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) { - yydestruct("Cleanup: popping", YY_ACCESSING_SYMBOL(+*yyssp), yyvsp, - context); - YYPOPSTACK(1); + yydestruct ("Cleanup: popping", + YY_ACCESSING_SYMBOL (+*yyssp), yyvsp, context); + YYPOPSTACK (1); } #ifndef yyoverflow - if (yyss != yyssa) - YYSTACK_FREE(yyss); + if (yyss != yyssa) + YYSTACK_FREE (yyss); #endif - if (yymsg != yymsgbuf) - YYSTACK_FREE(yymsg); - return yyresult; + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + return yyresult; } + diff --git a/ogr/swq_parser.hpp b/ogr/swq_parser.hpp index ebb35ea1dfa1..7de2f1a4d6c9 100644 --- a/ogr/swq_parser.hpp +++ b/ogr/swq_parser.hpp @@ -36,10 +36,10 @@ private implementation details that can be changed or removed. */ #ifndef YY_SWQ_SWQ_PARSER_HPP_INCLUDED -#define YY_SWQ_SWQ_PARSER_HPP_INCLUDED +# define YY_SWQ_SWQ_PARSER_HPP_INCLUDED /* Debug traces. */ #ifndef YYDEBUG -#define YYDEBUG 0 +# define YYDEBUG 0 #endif #if YYDEBUG extern int swqdebug; @@ -47,62 +47,66 @@ extern int swqdebug; /* Token kinds. */ #ifndef YYTOKENTYPE -#define YYTOKENTYPE - -enum yytokentype -{ +# define YYTOKENTYPE + enum yytokentype + { YYEMPTY = -2, - END = 0, /* "end of string" */ - YYerror = 256, /* error */ - YYUNDEF = 257, /* "invalid token" */ - SWQT_INTEGER_NUMBER = 258, /* "integer number" */ - SWQT_FLOAT_NUMBER = 259, /* "floating point number" */ - SWQT_STRING = 260, /* "string" */ - SWQT_IDENTIFIER = 261, /* "identifier" */ - SWQT_IN = 262, /* "IN" */ - SWQT_LIKE = 263, /* "LIKE" */ - SWQT_ILIKE = 264, /* "ILIKE" */ - SWQT_ESCAPE = 265, /* "ESCAPE" */ - SWQT_BETWEEN = 266, /* "BETWEEN" */ - SWQT_NULL = 267, /* "NULL" */ - SWQT_IS = 268, /* "IS" */ - SWQT_SELECT = 269, /* "SELECT" */ - SWQT_LEFT = 270, /* "LEFT" */ - SWQT_JOIN = 271, /* "JOIN" */ - SWQT_WHERE = 272, /* "WHERE" */ - SWQT_ON = 273, /* "ON" */ - SWQT_ORDER = 274, /* "ORDER" */ - SWQT_BY = 275, /* "BY" */ - SWQT_FROM = 276, /* "FROM" */ - SWQT_AS = 277, /* "AS" */ - SWQT_ASC = 278, /* "ASC" */ - SWQT_DESC = 279, /* "DESC" */ - SWQT_DISTINCT = 280, /* "DISTINCT" */ - SWQT_CAST = 281, /* "CAST" */ - SWQT_UNION = 282, /* "UNION" */ - SWQT_ALL = 283, /* "ALL" */ - SWQT_LIMIT = 284, /* "LIMIT" */ - SWQT_OFFSET = 285, /* "OFFSET" */ - SWQT_EXCEPT = 286, /* "EXCEPT" */ - SWQT_EXCLUDE = 287, /* "EXCLUDE" */ - SWQT_VALUE_START = 288, /* SWQT_VALUE_START */ - SWQT_SELECT_START = 289, /* SWQT_SELECT_START */ - SWQT_NOT = 290, /* "NOT" */ - SWQT_OR = 291, /* "OR" */ - SWQT_AND = 292, /* "AND" */ - SWQT_UMINUS = 293, /* SWQT_UMINUS */ - SWQT_RESERVED_KEYWORD = 294 /* "reserved keyword" */ -}; -typedef enum yytokentype yytoken_kind_t; + END = 0, /* "end of string" */ + YYerror = 256, /* error */ + YYUNDEF = 257, /* "invalid token" */ + SWQT_INTEGER_NUMBER = 258, /* "integer number" */ + SWQT_FLOAT_NUMBER = 259, /* "floating point number" */ + SWQT_STRING = 260, /* "string" */ + SWQT_IDENTIFIER = 261, /* "identifier" */ + SWQT_IN = 262, /* "IN" */ + SWQT_LIKE = 263, /* "LIKE" */ + SWQT_ILIKE = 264, /* "ILIKE" */ + SWQT_ESCAPE = 265, /* "ESCAPE" */ + SWQT_BETWEEN = 266, /* "BETWEEN" */ + SWQT_NULL = 267, /* "NULL" */ + SWQT_IS = 268, /* "IS" */ + SWQT_SELECT = 269, /* "SELECT" */ + SWQT_LEFT = 270, /* "LEFT" */ + SWQT_JOIN = 271, /* "JOIN" */ + SWQT_WHERE = 272, /* "WHERE" */ + SWQT_ON = 273, /* "ON" */ + SWQT_ORDER = 274, /* "ORDER" */ + SWQT_BY = 275, /* "BY" */ + SWQT_FROM = 276, /* "FROM" */ + SWQT_AS = 277, /* "AS" */ + SWQT_ASC = 278, /* "ASC" */ + SWQT_DESC = 279, /* "DESC" */ + SWQT_DISTINCT = 280, /* "DISTINCT" */ + SWQT_CAST = 281, /* "CAST" */ + SWQT_UNION = 282, /* "UNION" */ + SWQT_ALL = 283, /* "ALL" */ + SWQT_LIMIT = 284, /* "LIMIT" */ + SWQT_OFFSET = 285, /* "OFFSET" */ + SWQT_EXCEPT = 286, /* "EXCEPT" */ + SWQT_EXCLUDE = 287, /* "EXCLUDE" */ + SWQT_HIDDEN = 288, /* "HIDDEN" */ + SWQT_VALUE_START = 289, /* SWQT_VALUE_START */ + SWQT_SELECT_START = 290, /* SWQT_SELECT_START */ + SWQT_NOT = 291, /* "NOT" */ + SWQT_OR = 292, /* "OR" */ + SWQT_AND = 293, /* "AND" */ + SWQT_UMINUS = 294, /* SWQT_UMINUS */ + SWQT_RESERVED_KEYWORD = 295 /* "reserved keyword" */ + }; + typedef enum yytokentype yytoken_kind_t; #endif /* Value type. */ -#if !defined YYSTYPE && !defined YYSTYPE_IS_DECLARED +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED typedef int YYSTYPE; -#define YYSTYPE_IS_TRIVIAL 1 -#define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 #endif -int swqparse(swq_parse_context *context); + + + +int swqparse (swq_parse_context *context); + #endif /* !YY_SWQ_SWQ_PARSER_HPP_INCLUDED */ diff --git a/ogr/swq_parser.y b/ogr/swq_parser.y index 48cbcabe651e..6383b25827d8 100644 --- a/ogr/swq_parser.y +++ b/ogr/swq_parser.y @@ -91,6 +91,7 @@ %token SWQT_OFFSET "OFFSET" %token SWQT_EXCEPT "EXCEPT" %token SWQT_EXCLUDE "EXCLUDE" +%token SWQT_HIDDEN "HIDDEN" %token SWQT_VALUE_START %token SWQT_SELECT_START @@ -115,8 +116,8 @@ %token SWQT_RESERVED_KEYWORD "reserved keyword" /* Any grammar rule that does $$ = must be listed afterwards */ -/* as well as SWQT_INTEGER_NUMBER SWQT_FLOAT_NUMBER SWQT_STRING SWQT_IDENTIFIER that are allocated by swqlex() */ -%destructor { delete $$; } SWQT_INTEGER_NUMBER SWQT_FLOAT_NUMBER SWQT_STRING SWQT_IDENTIFIER +/* as well as SWQT_INTEGER_NUMBER SWQT_FLOAT_NUMBER SWQT_STRING SWQT_IDENTIFIER SWQT_HIDDEN that are allocated by swqlex() */ +%destructor { delete $$; } SWQT_INTEGER_NUMBER SWQT_FLOAT_NUMBER SWQT_STRING SWQT_IDENTIFIER SWQT_HIDDEN %destructor { delete $$; } value_expr_list field_value value_expr value_expr_non_logical type_def table_def %% @@ -388,8 +389,13 @@ value_expr_list: $$->PushSubExpression( $1 ); } -field_value: +identifier: SWQT_IDENTIFIER + + | SWQT_HIDDEN + +field_value: + identifier { $$ = $1; // validation deferred. $$->eNodeType = SNT_COLUMN; @@ -397,7 +403,7 @@ field_value: $$->table_index = -1; } - | SWQT_IDENTIFIER '.' SWQT_IDENTIFIER + | identifier '.' identifier { $$ = $1; // validation deferred. $$->eNodeType = SNT_COLUMN; @@ -509,7 +515,7 @@ value_expr_non_logical: $$->PushSubExpression( $3 ); } - | SWQT_IDENTIFIER '(' value_expr_list ')' + | identifier '(' value_expr_list ')' { const swq_operation *poOp = swq_op_registrar::GetOperator( $1->string_value ); @@ -553,20 +559,20 @@ value_expr_non_logical: } type_def: - SWQT_IDENTIFIER + identifier { $$ = new swq_expr_node( SWQ_CAST ); $$->PushSubExpression( $1 ); } - | SWQT_IDENTIFIER '(' SWQT_INTEGER_NUMBER ')' + | identifier '(' SWQT_INTEGER_NUMBER ')' { $$ = new swq_expr_node( SWQ_CAST ); $$->PushSubExpression( $3 ); $$->PushSubExpression( $1 ); } - | SWQT_IDENTIFIER '(' SWQT_INTEGER_NUMBER ',' SWQT_INTEGER_NUMBER ')' + | identifier '(' SWQT_INTEGER_NUMBER ',' SWQT_INTEGER_NUMBER ')' { $$ = new swq_expr_node( SWQ_CAST ); $$->PushSubExpression( $5 ); @@ -575,7 +581,7 @@ type_def: } /* e.g. GEOMETRY(POINT) */ - | SWQT_IDENTIFIER '(' SWQT_IDENTIFIER ')' + | identifier '(' identifier ')' { OGRwkbGeometryType eType = OGRFromOGCGeomType($3->string_value); if( !EQUAL($1->string_value, "GEOMETRY") || @@ -593,7 +599,7 @@ type_def: } /* e.g. GEOMETRY(POINT,4326) */ - | SWQT_IDENTIFIER '(' SWQT_IDENTIFIER ',' SWQT_INTEGER_NUMBER ')' + | identifier '(' identifier ',' SWQT_INTEGER_NUMBER ')' { OGRwkbGeometryType eType = OGRFromOGCGeomType($3->string_value); if( !EQUAL($1->string_value, "GEOMETRY") || @@ -663,16 +669,16 @@ except_or_exclude: column_spec: value_expr { - if( !context->poCurSelect->PushField( $1 ) ) + if( !context->poCurSelect->PushField( $1, nullptr, false, false ) ) { delete $1; YYERROR; } } - | value_expr as_clause + | value_expr as_clause_with_hidden { - if( !context->poCurSelect->PushField( $1, $2->string_value ) ) + if( !context->poCurSelect->PushField( $1, $2->string_value, false, $2->bHidden ) ) { delete $1; delete $2; @@ -689,7 +695,7 @@ column_spec: poNode->table_index = -1; poNode->field_index = -1; - if( !context->poCurSelect->PushField( poNode ) ) + if( !context->poCurSelect->PushField( poNode, nullptr, false, false ) ) { delete poNode; YYERROR; @@ -704,14 +710,14 @@ column_spec: poNode->table_index = -1; poNode->field_index = -1; - if( !context->poCurSelect->PushField( poNode ) ) + if( !context->poCurSelect->PushField( poNode, nullptr, false, false ) ) { delete poNode; YYERROR; } } - | SWQT_IDENTIFIER '.' '*' + | identifier '.' '*' { CPLString osTableName = $1->string_value; @@ -725,14 +731,14 @@ column_spec: poNode->table_index = -1; poNode->field_index = -1; - if( !context->poCurSelect->PushField( poNode ) ) + if( !context->poCurSelect->PushField( poNode, nullptr, false, false ) ) { delete poNode; YYERROR; } } - | SWQT_IDENTIFIER '(' '*' ')' + | identifier '(' '*' ')' { // special case for COUNT(*), confirm it. if( !EQUAL($1->string_value, "COUNT") ) @@ -756,14 +762,14 @@ column_spec: swq_expr_node *count = new swq_expr_node( SWQ_COUNT ); count->PushSubExpression( poNode ); - if( !context->poCurSelect->PushField( count ) ) + if( !context->poCurSelect->PushField( count, nullptr, false, false ) ) { delete count; YYERROR; } } - | SWQT_IDENTIFIER '(' '*' ')' as_clause + | identifier '(' '*' ')' as_clause { // special case for COUNT(*), confirm it. if( !EQUAL($1->string_value, "COUNT") ) @@ -788,7 +794,7 @@ column_spec: swq_expr_node *count = new swq_expr_node( SWQ_COUNT ); count->PushSubExpression( poNode ); - if( !context->poCurSelect->PushField( count, $5->string_value ) ) + if( !context->poCurSelect->PushField( count, $5->string_value, false, $5->bHidden ) ) { delete count; delete $5; @@ -798,7 +804,7 @@ column_spec: delete $5; } - | SWQT_IDENTIFIER '(' SWQT_DISTINCT field_value ')' + | identifier '(' SWQT_DISTINCT field_value ')' { // special case for COUNT(DISTINCT x), confirm it. if( !EQUAL($1->string_value, "COUNT") ) @@ -816,14 +822,14 @@ column_spec: swq_expr_node *count = new swq_expr_node( SWQ_COUNT ); count->PushSubExpression( $4 ); - if( !context->poCurSelect->PushField( count, nullptr, TRUE ) ) + if( !context->poCurSelect->PushField( count, nullptr, true, false ) ) { delete count; YYERROR; } } - | SWQT_IDENTIFIER '(' SWQT_DISTINCT field_value ')' as_clause + | identifier '(' SWQT_DISTINCT field_value ')' as_clause { // special case for COUNT(DISTINCT x), confirm it. if( !EQUAL($1->string_value, "COUNT") ) @@ -839,7 +845,7 @@ column_spec: swq_expr_node *count = new swq_expr_node( SWQ_COUNT ); count->PushSubExpression( $4 ); - if( !context->poCurSelect->PushField( count, $6->string_value, TRUE ) ) + if( !context->poCurSelect->PushField( count, $6->string_value, true, $6->bHidden ) ) { delete $1; delete count; @@ -852,13 +858,25 @@ column_spec: } as_clause: - SWQT_AS SWQT_IDENTIFIER + SWQT_AS identifier { - delete $1; $$ = $2; + $2 = nullptr; } - | SWQT_IDENTIFIER + | identifier + +as_clause_with_hidden: + as_clause + + | as_clause SWQT_HIDDEN + { + $$ = $1; + $1 = nullptr; + delete $2; + $2 = nullptr; + $$->bHidden = true; + } opt_where: | SWQT_WHERE value_expr @@ -924,7 +942,7 @@ opt_offset: } table_def: - SWQT_IDENTIFIER + identifier { const int iTable = context->poCurSelect->PushTableDef( nullptr, $1->string_value, @@ -934,7 +952,7 @@ table_def: $$ = new swq_expr_node( iTable ); } - | SWQT_IDENTIFIER as_clause + | identifier as_clause { const int iTable = context->poCurSelect->PushTableDef( nullptr, $1->string_value, @@ -945,7 +963,7 @@ table_def: $$ = new swq_expr_node( iTable ); } - | SWQT_STRING '.' SWQT_IDENTIFIER + | SWQT_STRING '.' identifier { const int iTable = context->poCurSelect->PushTableDef( $1->string_value, @@ -956,7 +974,7 @@ table_def: $$ = new swq_expr_node( iTable ); } - | SWQT_STRING '.' SWQT_IDENTIFIER as_clause + | SWQT_STRING '.' identifier as_clause { const int iTable = context->poCurSelect->PushTableDef( $1->string_value, @@ -969,7 +987,7 @@ table_def: $$ = new swq_expr_node( iTable ); } - | SWQT_IDENTIFIER '.' SWQT_IDENTIFIER + | identifier '.' identifier { const int iTable = context->poCurSelect->PushTableDef( $1->string_value, @@ -980,7 +998,7 @@ table_def: $$ = new swq_expr_node( iTable ); } - | SWQT_IDENTIFIER '.' SWQT_IDENTIFIER as_clause + | identifier '.' identifier as_clause { const int iTable = context->poCurSelect->PushTableDef( $1->string_value, diff --git a/ogr/swq_select.cpp b/ogr/swq_select.cpp index becd1221d1a9..db91efe48552 100644 --- a/ogr/swq_select.cpp +++ b/ogr/swq_select.cpp @@ -322,7 +322,7 @@ char *swq_select::Unparse() /************************************************************************/ int swq_select::PushField(swq_expr_node *poExpr, const char *pszAlias, - int distinct_flag) + bool distinct_flag, bool bHidden) { if (query_mode == SWQM_DISTINCT_LIST && distinct_flag) @@ -401,6 +401,26 @@ int swq_select::PushField(swq_expr_node *poExpr, const char *pszAlias, "%s_%s", op->pszName, poExpr->papoSubExpr[0]->string_value)); } + if (bHidden) + { + const char *pszDstFieldName = + col_def->field_alias ? col_def->field_alias : col_def->field_name; + if (!EQUAL(pszDstFieldName, "OGR_STYLE")) + { + CPLError( + CE_Failure, CPLE_AppDefined, + "HIDDEN keyword only supported on a column named OGR_STYLE"); + CPLFree(col_def->table_name); + col_def->table_name = nullptr; + CPLFree(col_def->field_name); + col_def->field_name = nullptr; + CPLFree(col_def->field_alias); + col_def->field_alias = nullptr; + column_defs.pop_back(); + return FALSE; + } + } + col_def->table_index = -1; col_def->field_index = -1; col_def->field_type = SWQ_OTHER; @@ -409,6 +429,7 @@ int swq_select::PushField(swq_expr_node *poExpr, const char *pszAlias, col_def->target_subtype = OFSTNone; col_def->col_func = SWQCF_NONE; col_def->distinct_flag = distinct_flag; + col_def->bHidden = bHidden; /* -------------------------------------------------------------------- */ /* Do we have a CAST operator in play? */ diff --git a/scripts/cppcheck.sh b/scripts/cppcheck.sh index 474e87d79697..2201450e7cad 100755 --- a/scripts/cppcheck.sh +++ b/scripts/cppcheck.sh @@ -172,6 +172,12 @@ mv ${LOG_FILE}.tmp ${LOG_FILE} grep -v -e "stlIfStrFind" ${LOG_FILE} > ${LOG_FILE}.tmp mv ${LOG_FILE}.tmp ${LOG_FILE} +# False positive in swq_parser.cpp +grep -v -e "The expression '0 <= yystate' is always true" ${LOG_FILE} > ${LOG_FILE}.tmp +mv ${LOG_FILE}.tmp ${LOG_FILE} +grep -v -e "The comparison '0 <= yystate' is always true" ${LOG_FILE} > ${LOG_FILE}.tmp +mv ${LOG_FILE}.tmp ${LOG_FILE} + if grep "null pointer" ${LOG_FILE} ; then echo "Null pointer check failed" ret_code=1 From a577383ef51210b6c5c55401b1636311d27ad90e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 21 Jun 2024 00:33:22 +0200 Subject: [PATCH 0210/1119] swq_parser.cpp: manual change to make MSVC happy --- ogr/swq_parser.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ogr/swq_parser.cpp b/ogr/swq_parser.cpp index 2c3659644f95..f12ed64fa289 100644 --- a/ogr/swq_parser.cpp +++ b/ogr/swq_parser.cpp @@ -639,10 +639,11 @@ static const yytype_int16 yyrline[] = /** Accessing symbol of state STATE. */ #define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) -#if 1 +#if 0 /* The user-facing name of the symbol whose (internal) number is YYSYMBOL. No bounds checking. */ static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; +#endif /* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. First, the terminals, then, starting at YYNTOKENS, nonterminals. */ @@ -667,6 +668,7 @@ static const char *const yytname[] = "opt_offset", "table_def", YY_NULLPTR }; +#if 0 static const char * yysymbol_name (yysymbol_kind_t yysymbol) { From ceeeeaf26ecbea257c437b40ea451dddba0a421f Mon Sep 17 00:00:00 2001 From: Lucian Plesea Date: Sat, 22 Jun 2024 03:47:47 -0700 Subject: [PATCH 0211/1119] MRF: Add 64bit int support (#10265) Add 64bit integer support to MRF, for NONE, DEFLATE, ZSTD, TIF and QB3 formats --- autotest/gdrivers/mrf.py | 4 ++++ frmts/mrf/LERC_band.cpp | 18 ++++++++++++++++-- frmts/mrf/QB3_band.cpp | 9 ++++++++- frmts/mrf/Tif_band.cpp | 2 ++ frmts/mrf/mrf_band.cpp | 9 ++++++++- frmts/mrf/mrf_overview.cpp | 12 ++++++++++++ frmts/mrf/mrfdrivercore.cpp | 5 +++-- 7 files changed, 53 insertions(+), 6 deletions(-) diff --git a/autotest/gdrivers/mrf.py b/autotest/gdrivers/mrf.py index 24bd8a6738fe..7e3685c84888 100755 --- a/autotest/gdrivers/mrf.py +++ b/autotest/gdrivers/mrf.py @@ -77,6 +77,10 @@ def module_disable_exceptions(): ("../../gcore/data/uint32.tif", 4672, [4672], ["COMPRESS=LERC"]), ("../../gcore/data/uint32.tif", 4672, [4672], ["COMPRESS=QB3"]), ("../../gcore/data/uint32.tif", 4672, [4672], ["COMPRESS=LERC", "OPTIONS=V1:YES"]), + ("../../gcore/data/int64.tif", 4672, [4672], ["COMPRESS=DEFLATE"]), + ("../../gcore/data/int64.tif", 4672, [4672], ["COMPRESS=ZSTD"]), + ("../../gcore/data/int64.tif", 4672, [4672], ["COMPRESS=TIF"]), + ("../../gcore/data/int64.tif", 4672, [4672], ["COMPRESS=QB3"]), ("float32.tif", 4672, [4672], ["COMPRESS=DEFLATE"]), ("float32.tif", 4672, [4672], ["COMPRESS=ZSTD"]), ("float32.tif", 4672, [4672], ["COMPRESS=TIF"]), diff --git a/frmts/mrf/LERC_band.cpp b/frmts/mrf/LERC_band.cpp index aba3d549b0c1..83680359fdce 100644 --- a/frmts/mrf/LERC_band.cpp +++ b/frmts/mrf/LERC_band.cpp @@ -303,6 +303,9 @@ static CPLErr DecompressLERC1(buf_mgr &dst, const buf_mgr &src, case GDT_Byte: UFILL(GByte); break; + case GDT_Int8: + UFILL(GInt8); + break; case GDT_UInt16: UFILL(GUInt16); break; @@ -361,8 +364,11 @@ static GDALDataType L2toGDT(L2NS::DataType L2type) case L2NS::DataType::dt_double: dt = GDT_Float64; break; - default: - dt = GDT_Byte; // GDAL doesn't have a signed char type + case L2NS::DataType::dt_char: + dt = GDT_Int8; + break; + default: // Unsigned byte + dt = GDT_Byte; } return dt; } @@ -707,6 +713,14 @@ CPLXMLNode *LERC_Band::GetMRFConfig(GDALOpenInfo *poOpenInfo) LERC_Band::LERC_Band(MRFDataset *pDS, const ILImage &image, int b, int level) : MRFRasterBand(pDS, image, b, level) { + // Lerc doesn't handle 64bit int types + if (image.dt == GDT_UInt64 || image.dt == GDT_Int64) + { + CPLError(CE_Failure, CPLE_NotSupported, + "Lerc compression of 64 bit integers is not supported"); + return; + } + // Pick 1/1000 for floats and 0.5 losless for integers. if (eDataType == GDT_Float32 || eDataType == GDT_Float64) precision = strtod(GetOptionValue("LERC_PREC", ".001"), nullptr); diff --git a/frmts/mrf/QB3_band.cpp b/frmts/mrf/QB3_band.cpp index 338779572a61..23fe11bcf639 100644 --- a/frmts/mrf/QB3_band.cpp +++ b/frmts/mrf/QB3_band.cpp @@ -43,6 +43,12 @@ CPLErr QB3_Band::Compress(buf_mgr &dst, buf_mgr &src) case (GDT_UInt32): pQB3 = CREATE_QB3(QB3_U32); break; + case (GDT_Int64): + pQB3 = CREATE_QB3(QB3_I64); + break; + case (GDT_UInt64): + pQB3 = CREATE_QB3(QB3_U64); + break; default: CPLError(CE_Failure, CPLE_AssertionFailed, "MRF:QB3 Data type not supported"); @@ -174,7 +180,8 @@ QB3_Band::QB3_Band(MRFDataset *pDS, const ILImage &image, int b, int level) if (image.dt != GDT_Byte && image.dt != GDT_Int16 && image.dt != GDT_UInt16 && image.dt != GDT_Int32 && - image.dt != GDT_UInt32) + image.dt != GDT_UInt32 && image.dt != GDT_Int64 && + image.dt != GDT_UInt64) { CPLError(CE_Failure, CPLE_NotSupported, "Data type not supported by QB3 compression"); diff --git a/frmts/mrf/Tif_band.cpp b/frmts/mrf/Tif_band.cpp index c54f5bd2b06c..1d69ecabad9b 100644 --- a/frmts/mrf/Tif_band.cpp +++ b/frmts/mrf/Tif_band.cpp @@ -242,6 +242,8 @@ TIF_Band::TIF_Band(MRFDataset *pDS, const ILImage &image, int b, int level) // ZLEVEL 8, which is fine. if (q > 2) q -= 2; + if (q == 0) // TIF does not accept ZLEVEL of zero + q = 6; papszOptions = CSLAddNameValue(papszOptions, "ZLEVEL", CPLOPrintf("%d", q)); } diff --git a/frmts/mrf/mrf_band.cpp b/frmts/mrf/mrf_band.cpp index 3bb645355b91..cdc51d9be3ef 100644 --- a/frmts/mrf/mrf_band.cpp +++ b/frmts/mrf/mrf_band.cpp @@ -129,10 +129,13 @@ static int isAllVal(GDALDataType gt, void *b, size_t bytecount, double ndv) switch (gt) { TEST_T(GDT_Byte, GByte); + TEST_T(GDT_Int8, GInt8); TEST_T(GDT_UInt16, GUInt16); TEST_T(GDT_Int16, GInt16); TEST_T(GDT_UInt32, GUInt32); TEST_T(GDT_Int32, GInt32); + TEST_T(GDT_UInt64, GUInt64); + TEST_T(GDT_Int64, GInt64); TEST_T(GDT_Float32, float); TEST_T(GDT_Float64, double); default: @@ -573,7 +576,7 @@ CPLErr MRFRasterBand::FillBlock(void *buffer) size_t bsb = blockSizeBytes(); // use memset for speed for bytes, or if nodata is zeros - if (eDataType == GDT_Byte || 0.0L == ndv) + if (0.0 == ndv || eDataType == GDT_Byte || eDataType == GDT_Int8) { memset(buffer, int(ndv), bsb); return CE_None; @@ -590,6 +593,10 @@ CPLErr MRFRasterBand::FillBlock(void *buffer) return bf(GUInt32); case GDT_Int32: return bf(GInt32); + case GDT_UInt64: + return bf(GUInt64); + case GDT_Int64: + return bf(GInt64); case GDT_Float32: return bf(float); case GDT_Float64: diff --git a/frmts/mrf/mrf_overview.cpp b/frmts/mrf/mrf_overview.cpp index ff03e2726191..8055cf5be1f6 100644 --- a/frmts/mrf/mrf_overview.cpp +++ b/frmts/mrf/mrf_overview.cpp @@ -479,6 +479,8 @@ CPLErr MRFDataset::PatchOverview(int BlockX, int BlockY, int Width, int Height, { case GDT_Byte: resample(GByte); + case GDT_Int8: + resample(GInt8); case GDT_UInt16: resample(GUInt16); case GDT_Int16: @@ -487,6 +489,10 @@ CPLErr MRFDataset::PatchOverview(int BlockX, int BlockY, int Width, int Height, resample(GUInt32); case GDT_Int32: resample(GInt32); + case GDT_UInt64: + resample(GUInt64); + case GDT_Int64: + resample(GInt64); case GDT_Float32: resample(float); case GDT_Float64: @@ -516,6 +522,8 @@ CPLErr MRFDataset::PatchOverview(int BlockX, int BlockY, int Width, int Height, { case GDT_Byte: resample(GByte); + case GDT_Int8: + resample(GInt8); case GDT_UInt16: resample(GUInt16); case GDT_Int16: @@ -524,6 +532,10 @@ CPLErr MRFDataset::PatchOverview(int BlockX, int BlockY, int Width, int Height, resample(GUInt32); case GDT_Int32: resample(GInt32); + case GDT_UInt64: + resample(GUInt64); + case GDT_Int64: + resample(GInt64); case GDT_Float32: resample(float); case GDT_Float64: diff --git a/frmts/mrf/mrfdrivercore.cpp b/frmts/mrf/mrfdrivercore.cpp index a8527ff64ef8..1406fc180010 100644 --- a/frmts/mrf/mrfdrivercore.cpp +++ b/frmts/mrf/mrfdrivercore.cpp @@ -123,8 +123,9 @@ void MRFDriverSetCommonMetadata(GDALDriver *poDriver) ""); // These will need to be revisited, do we support complex data types too? - poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, - "Byte UInt16 Int16 Int32 UInt32 Float32 Float64"); + poDriver->SetMetadataItem( + GDAL_DMD_CREATIONDATATYPES, + "Byte Int8 Int16 UInt16 Int32 UInt32 Int64 UInt64 Float32 Float64"); poDriver->pfnIdentify = MRFDriverIdentify; poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES"); From 3a4919237bf1e0e5294b448a980b9e1dc92a3a96 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 13:52:01 +0200 Subject: [PATCH 0212/1119] gpkg.py: do not call gdal.Open() with invalid argument --- autotest/gdrivers/gpkg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autotest/gdrivers/gpkg.py b/autotest/gdrivers/gpkg.py index 6a5237b52d8b..a0986fc5c85b 100755 --- a/autotest/gdrivers/gpkg.py +++ b/autotest/gdrivers/gpkg.py @@ -4336,7 +4336,7 @@ def test_gpkg_rename_raster_table(data_type, tmp_vsimem): ds.ExecuteSQL("VACUUM") ds = None - ds = gdal.Open(test_layer_path, gdal.OF_RASTER) + ds = gdal.Open(test_layer_path) layer_name = ds.GetMetadataItem("IDENTIFIER") assert layer_name == "bar" assert ds.GetRasterBand(1).Checksum() == checksum From f54924fabb29bbf9f259b905a2295a1f3e5f7e7b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 13:53:35 +0200 Subject: [PATCH 0213/1119] OGRFeature::SetField(int, double): avoid UndefinedBehavior when passing NaN --- ogr/ogrfeature.cpp | 55 ++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/ogr/ogrfeature.cpp b/ogr/ogrfeature.cpp index 7f8fd5d588cf..8185c239f880 100644 --- a/ogr/ogrfeature.cpp +++ b/ogr/ogrfeature.cpp @@ -3988,40 +3988,57 @@ void OGRFeature::SetField(int iField, double dfValue) } else if (eType == OFTInteger) { - const int nMin = std::numeric_limits::min(); - const int nMax = std::numeric_limits::max(); - const int nVal = dfValue < nMin ? nMin - : dfValue > nMax ? nMax - : static_cast(dfValue); - pauFields[iField].Integer = OGRFeatureGetIntegerValue(poFDefn, nVal); - if (!(nVal == dfValue)) + constexpr int nMin = std::numeric_limits::min(); + if (std::isnan(dfValue)) { - if (std::isnan(dfValue)) - pauFields[iField].Integer = nMin; + pauFields[iField].Integer = nMin; CPLError(CE_Warning, CPLE_AppDefined, "Lossy conversion occurred when trying to set " "32 bit integer field from a real value."); } + else + { + constexpr int nMax = std::numeric_limits::max(); + const int nVal = dfValue < nMin ? nMin + : dfValue > nMax ? nMax + : static_cast(dfValue); + pauFields[iField].Integer = + OGRFeatureGetIntegerValue(poFDefn, nVal); + if (!(nVal == dfValue)) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Lossy conversion occurred when trying to set " + "32 bit integer field from a real value."); + } + } pauFields[iField].Set.nMarker2 = 0; pauFields[iField].Set.nMarker3 = 0; } else if (eType == OFTInteger64) { - const auto nMin = std::numeric_limits::min(); - const auto nMax = std::numeric_limits::max(); - const auto nVal = dfValue < static_cast(nMin) ? nMin - : dfValue > static_cast(nMax) - ? nMax - : static_cast(dfValue); - pauFields[iField].Integer64 = nVal; - if (!(static_cast(nVal) == dfValue)) + constexpr auto nMin = std::numeric_limits::min(); + if (std::isnan(dfValue)) { - if (std::isnan(dfValue)) - pauFields[iField].Integer64 = nMin; + pauFields[iField].Integer64 = nMin; CPLError(CE_Warning, CPLE_AppDefined, "Lossy conversion occurred when trying to set " "64 bit integer field from a real value."); } + else + { + constexpr auto nMax = std::numeric_limits::max(); + const auto nVal = dfValue < static_cast(nMin) ? nMin + : dfValue > static_cast(nMax) + ? nMax + : static_cast(dfValue); + pauFields[iField].Integer64 = nVal; + if (!(static_cast(nVal) == dfValue)) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Lossy conversion occurred when trying to set " + "64 bit integer field from a real value."); + } + } pauFields[iField].Set.nMarker3 = 0; } else if (eType == OFTRealList) From 7c931fde2da10135122bc00ed6178ed7be7c55cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A9=8D=E4=B8=B9=E5=B0=BC=20Dan=20Jacobson?= Date: Sat, 22 Jun 2024 20:54:25 +0800 Subject: [PATCH 0214/1119] Update ogr_layer_algebra.py avoiding unintended misinterpretation Else it sounds like "that town has not (even) a single taxi." --- swig/python/gdal-utils/osgeo_utils/ogr_layer_algebra.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swig/python/gdal-utils/osgeo_utils/ogr_layer_algebra.py b/swig/python/gdal-utils/osgeo_utils/ogr_layer_algebra.py index 587d501ffe11..b92ea1f9824c 100644 --- a/swig/python/gdal-utils/osgeo_utils/ogr_layer_algebra.py +++ b/swig/python/gdal-utils/osgeo_utils/ogr_layer_algebra.py @@ -372,7 +372,7 @@ def main(argv=sys.argv): cnt = input_ds.GetLayerCount() if cnt != 1: print( - "Input datasource has not a single layer, so you should specify its name with -input_lyr", + "Input datasource has more than just a single layer, so you should specify which layer with -input_lyr", file=sys.stderr, ) return 1 From 9e94bb3d8dd8133fc8490eb849248ec829d6bec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A9=8D=E4=B8=B9=E5=B0=BC=20Dan=20Jacobson?= Date: Sat, 22 Jun 2024 21:00:11 +0800 Subject: [PATCH 0215/1119] Update ogr_layer_algebra.rst grammar fixes --- doc/source/programs/ogr_layer_algebra.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/source/programs/ogr_layer_algebra.rst b/doc/source/programs/ogr_layer_algebra.rst index edc39cd6f93b..20ccefa3d702 100644 --- a/doc/source/programs/ogr_layer_algebra.rst +++ b/doc/source/programs/ogr_layer_algebra.rst @@ -31,7 +31,7 @@ Description ----------- The :program:`ogr_layer_algebra` provides a command line utility to perform various vector layer algebraic operations. The utility takes a vector -input source , a method source and generates the output of the operation in the specified output file +input source and a method source and generates the output of the operation in the specified output file. .. note:: @@ -79,8 +79,8 @@ input source , a method source and generates the output of the operation in the .. option:: -input_ds - Input data set path for the operation to be performed. - For operations involving 2 datasets, this is one of the dataset. + Input dataset path for the operation to be performed. + For operations involving two datasets, this is one of the datasets. .. option:: -input_lyr @@ -98,7 +98,7 @@ input source , a method source and generates the output of the operation in the .. option:: -output_ds - Output data set path for writing the result of the operations performed by ogr_layer_algebra + Output data set path for writing the result of the operations performed by ``ogr_layer_algebra``. .. option:: -output_lyr_name @@ -106,11 +106,11 @@ input source , a method source and generates the output of the operation in the .. option:: -overwrite - Indicates whether the ``output_ds`` have to be overwritten with the generated result of ogr_layer_algebra + Indicates whether the ``output_ds`` have to be overwritten with the generated result of ``ogr_layer_algebra``. .. option:: -opt = - Attributes for which the operation has to run on ``input_ds`` and ``method_ds`` + Attributes for which the operation has to run on ``input_ds`` and ``method_ds``. .. option:: -f @@ -120,21 +120,21 @@ input source , a method source and generates the output of the operation in the .. option:: -dsco = - Dataset creation option (format specific) + Dataset creation option (format specific). .. option:: -lco = - Layer creation option (format specific) + Layer creation option (format specific). .. option:: -input_fields {NONE|ALL|,,...} Comma-delimited list of fields from input layer to copy to the output layer , - if eligible according to the operation + if eligible according to the operation. .. option:: -method_fields {NONE|ALL|,,...} Comma-delimited list of fields from method layer to copy to the output layer , - if eligible according to the operation + if eligible according to the operation. .. option:: -nlt From 993e0bf0c7f73693db776edcb8f47295527ffc39 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 15:10:17 +0200 Subject: [PATCH 0216/1119] OGRFeature::SetField(int, GIntBig): avoid UndefinedBehavior when passing value in [INT64_MAX - 512, INT64_MAX - 1] range --- ogr/ogrfeature.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ogr/ogrfeature.cpp b/ogr/ogrfeature.cpp index 8185c239f880..7680b1c5fa99 100644 --- a/ogr/ogrfeature.cpp +++ b/ogr/ogrfeature.cpp @@ -3832,7 +3832,12 @@ void OGRFeature::SetField(int iField, GIntBig nValue) else if (eType == OFTReal) { pauFields[iField].Real = static_cast(nValue); - if (static_cast(pauFields[iField].Real) != nValue) + // Values in the range [INT64_MAX - 1023, INT64_MAX - 1] + // get converted to a double that once cast to int64_t is + // INT64_MAX + 1 ... + if (pauFields[iField].Real >= + static_cast(std::numeric_limits::max()) || + static_cast(pauFields[iField].Real) != nValue) { CPLError(CE_Warning, CPLE_AppDefined, "Lossy conversion occurred when trying to set " From 90ed471744179c907d0091e71c0d35f15e550280 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 13:54:16 +0200 Subject: [PATCH 0217/1119] OGRLayer::WriteArrowBatch(): avoid UndefinedBehavior when trying to convert NaN to Int64 --- ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp b/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp index 3ccdaf488df4..085610b02356 100644 --- a/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp +++ b/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp @@ -7585,6 +7585,22 @@ bool OGRLayer::WriteArrowBatch(const struct ArrowSchema *schema, oLayerDefnTmp.GetFieldDefnUnsafe(i)->GetType(); const auto eDstType = poLayerDefn->GetFieldDefnUnsafe(i)->GetType(); + + const auto IsDoubleCastToInt64EqualTInt64 = + [](double dfVal, int64_t nOtherVal) + { + // Values in the range [INT64_MAX - 1023, INT64_MAX - 1] + // get converted to a double that once cast to int64_t + // is INT64_MAX + 1, hence the strict < comparison + return dfVal >= + static_cast( + std::numeric_limits::min()) && + dfVal < + static_cast( + std::numeric_limits::max()) && + static_cast(dfVal) == nOtherVal; + }; + if (eSrcType == OFTInteger64 && eDstType == OFTInteger && oFeatureTarget.GetFieldAsIntegerUnsafe(i) != oFeature.GetFieldAsInteger64Unsafe(i)) @@ -7605,9 +7621,9 @@ bool OGRLayer::WriteArrowBatch(const struct ArrowSchema *schema, bLossyConversion = true; } else if (eSrcType == OFTInteger64 && eDstType == OFTReal && - static_cast( - oFeatureTarget.GetFieldAsDoubleUnsafe(i)) != - oFeature.GetFieldAsInteger64Unsafe(i)) + !IsDoubleCastToInt64EqualTInt64( + oFeatureTarget.GetFieldAsDoubleUnsafe(i), + oFeature.GetFieldAsInteger64Unsafe(i))) { bLossyConversion = true; } From 0ef2d6ea88838d2cb325f95e37548ef4ff72aeb7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 16:48:13 +0200 Subject: [PATCH 0218/1119] gdal_priv_templates.hpp: implement GDALIsValueInRange<[u]int64_t]>, and add GDALIsValueExactAs<> --- autotest/cpp/test_gdal.cpp | 100 +++++++++++++++++++++++++++++++++- gcore/gdal_priv_templates.hpp | 52 ++++++++++++++++++ 2 files changed, 149 insertions(+), 3 deletions(-) diff --git a/autotest/cpp/test_gdal.cpp b/autotest/cpp/test_gdal.cpp index 30d217d8e7f5..459105bdb9ec 100644 --- a/autotest/cpp/test_gdal.cpp +++ b/autotest/cpp/test_gdal.cpp @@ -702,21 +702,115 @@ TEST_F(test_gdal, GDALIsValueInRange) EXPECT_TRUE(GDALIsValueInRange(255)); EXPECT_TRUE(!GDALIsValueInRange(-1)); EXPECT_TRUE(!GDALIsValueInRange(256)); + EXPECT_TRUE( + !GDALIsValueInRange(std::numeric_limits::quiet_NaN())); + EXPECT_TRUE(GDALIsValueInRange(-128)); EXPECT_TRUE(GDALIsValueInRange(127)); EXPECT_TRUE(!GDALIsValueInRange(-129)); EXPECT_TRUE(!GDALIsValueInRange(128)); + + // -(1 << 63) + EXPECT_TRUE(GDALIsValueInRange(-9223372036854775808.0)); + // (1 << 63) - 1024 + EXPECT_TRUE(GDALIsValueInRange(9223372036854774784.0)); + EXPECT_TRUE(GDALIsValueInRange(0.5)); + // (1 << 63) - 512 + EXPECT_TRUE(!GDALIsValueInRange(9223372036854775296.0)); + + EXPECT_TRUE(GDALIsValueInRange(0.0)); + EXPECT_TRUE(GDALIsValueInRange(0.5)); + // (1 << 64) - 2048 + EXPECT_TRUE(GDALIsValueInRange(18446744073709549568.0)); + // (1 << 64) + EXPECT_TRUE(!GDALIsValueInRange(18446744073709551616.0)); + EXPECT_TRUE(!GDALIsValueInRange(-0.5)); + + EXPECT_TRUE(GDALIsValueInRange(-std::numeric_limits::max())); EXPECT_TRUE(GDALIsValueInRange(std::numeric_limits::max())); + EXPECT_TRUE( + GDALIsValueInRange(-std::numeric_limits::infinity())); EXPECT_TRUE( GDALIsValueInRange(std::numeric_limits::infinity())); + EXPECT_TRUE( + !GDALIsValueInRange(std::numeric_limits::quiet_NaN())); + EXPECT_TRUE( + !GDALIsValueInRange(-std::numeric_limits::max())); EXPECT_TRUE(!GDALIsValueInRange(std::numeric_limits::max())); + + EXPECT_TRUE( + GDALIsValueInRange(-std::numeric_limits::infinity())); EXPECT_TRUE( GDALIsValueInRange(std::numeric_limits::infinity())); - EXPECT_TRUE(!GDALIsValueInRange(CPLAtof("nan"))); - EXPECT_TRUE(!GDALIsValueInRange(CPLAtof("nan"))); - EXPECT_TRUE(!GDALIsValueInRange(CPLAtof("nan"))); + EXPECT_TRUE( + GDALIsValueInRange(-std::numeric_limits::max())); + EXPECT_TRUE(GDALIsValueInRange(std::numeric_limits::max())); + EXPECT_TRUE( + !GDALIsValueInRange(std::numeric_limits::quiet_NaN())); } +#ifdef _MSC_VER +#pragma warning(push) +// overflow in constant arithmetic +#pragma warning(disable : 4756) +#endif + +// Test GDALIsValueExactAs() +TEST_F(test_gdal, GDALIsValueExactAs) +{ + EXPECT_TRUE(GDALIsValueExactAs(0)); + EXPECT_TRUE(GDALIsValueExactAs(255)); + EXPECT_TRUE(!GDALIsValueExactAs(0.5)); + EXPECT_TRUE(!GDALIsValueExactAs(-1)); + EXPECT_TRUE(!GDALIsValueExactAs(-0.5)); + EXPECT_TRUE(!GDALIsValueExactAs(255.5)); + EXPECT_TRUE(!GDALIsValueExactAs(256)); + EXPECT_TRUE( + !GDALIsValueExactAs(std::numeric_limits::quiet_NaN())); + + // -(1 << 63) + EXPECT_TRUE(GDALIsValueExactAs(-9223372036854775808.0)); + // (1 << 63) - 1024 + EXPECT_TRUE(GDALIsValueExactAs(9223372036854774784.0)); + EXPECT_TRUE(!GDALIsValueExactAs(0.5)); + // (1 << 63) - 512 + EXPECT_TRUE(!GDALIsValueExactAs(9223372036854775296.0)); + + EXPECT_TRUE(GDALIsValueExactAs(0.0)); + EXPECT_TRUE(!GDALIsValueExactAs(0.5)); + // (1 << 64) - 2048 + EXPECT_TRUE(GDALIsValueExactAs(18446744073709549568.0)); + // (1 << 64) + EXPECT_TRUE(!GDALIsValueExactAs(18446744073709551616.0)); + EXPECT_TRUE(!GDALIsValueExactAs(-0.5)); + + EXPECT_TRUE(GDALIsValueExactAs(-std::numeric_limits::max())); + EXPECT_TRUE(GDALIsValueExactAs(std::numeric_limits::max())); + EXPECT_TRUE( + GDALIsValueExactAs(-std::numeric_limits::infinity())); + EXPECT_TRUE( + GDALIsValueExactAs(std::numeric_limits::infinity())); + EXPECT_TRUE( + GDALIsValueExactAs(std::numeric_limits::quiet_NaN())); + EXPECT_TRUE( + !GDALIsValueExactAs(-std::numeric_limits::max())); + EXPECT_TRUE(!GDALIsValueExactAs(std::numeric_limits::max())); + + EXPECT_TRUE( + GDALIsValueExactAs(-std::numeric_limits::infinity())); + EXPECT_TRUE( + GDALIsValueExactAs(std::numeric_limits::infinity())); + EXPECT_TRUE( + GDALIsValueExactAs(-std::numeric_limits::max())); + EXPECT_TRUE(GDALIsValueExactAs(std::numeric_limits::max())); + EXPECT_TRUE( + GDALIsValueExactAs(std::numeric_limits::quiet_NaN())); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + // Test GDALDataTypeIsInteger() TEST_F(test_gdal, GDALDataTypeIsInteger) { diff --git a/gcore/gdal_priv_templates.hpp b/gcore/gdal_priv_templates.hpp index 3d928c256d76..dcb2ceb9c8c0 100644 --- a/gcore/gdal_priv_templates.hpp +++ b/gcore/gdal_priv_templates.hpp @@ -32,6 +32,7 @@ #include "cpl_port.h" +#include #include #include @@ -158,6 +159,57 @@ template <> inline bool GDALIsValueInRange(double dfValue) dfValue <= std::numeric_limits::max()); } +template <> inline bool GDALIsValueInRange(double dfValue) +{ + // Values in the range [INT64_MAX - 1023, INT64_MAX - 1] + // get converted to a double that once cast to int64_t is + // INT64_MAX + 1, hence the < strict comparison. + return dfValue >= + static_cast(std::numeric_limits::min()) && + dfValue < static_cast(std::numeric_limits::max()); +} + +template <> inline bool GDALIsValueInRange(double dfValue) +{ + // Values in the range [UINT64_MAX - 2047, UINT64_MAX - 1] + // get converted to a double that once cast to uint64_t is + // UINT64_MAX + 1, hence the < strict comparison. + return dfValue >= 0 && + dfValue < static_cast(std::numeric_limits::max()); +} + +/************************************************************************/ +/* GDALIsValueExactAs() */ +/************************************************************************/ +/** + * Returns whether a value can be exactly represented on type T. + * + * That is static_cast\(static_cast\(dfValue)) is legal and is + * equal to dfValue. + * + * Note: for T=float or double, a NaN input leads to true + * + * @param dfValue the value + * @return whether the value can be exactly represented on type T. + */ +template inline bool GDALIsValueExactAs(double dfValue) +{ + return GDALIsValueInRange(dfValue) && + static_cast(static_cast(dfValue)) == dfValue; +} + +template <> inline bool GDALIsValueExactAs(double dfValue) +{ + return std::isnan(dfValue) || + (GDALIsValueInRange(dfValue) && + static_cast(static_cast(dfValue)) == dfValue); +} + +template <> inline bool GDALIsValueExactAs(double) +{ + return true; +} + /************************************************************************/ /* GDALCopyWord() */ /************************************************************************/ From 20019dc5f04b48168f988b017af06ed3a7ea9368 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 16:48:38 +0200 Subject: [PATCH 0219/1119] gdal_translate: use GDALIsValueExactAs<> to check range of [u]int64 nodata --- apps/gdal_translate_lib.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/apps/gdal_translate_lib.cpp b/apps/gdal_translate_lib.cpp index 45d079c61a25..0dbbd9193253 100644 --- a/apps/gdal_translate_lib.cpp +++ b/apps/gdal_translate_lib.cpp @@ -2385,12 +2385,7 @@ GDALDatasetH GDALTranslate(const char *pszDest, GDALDatasetH hSrcDataset, { const double dfNoData = CPLAtof(psOptions->osNoData.c_str()); - if (dfNoData >= static_cast( - std::numeric_limits::min()) && - dfNoData <= static_cast( - std::numeric_limits::max()) && - dfNoData == - static_cast(static_cast(dfNoData))) + if (GDALIsValueExactAs(dfNoData)) { poVRTBand->SetNoDataValueAsInt64( static_cast(dfNoData)); @@ -2428,12 +2423,7 @@ GDALDatasetH GDALTranslate(const char *pszDest, GDALDatasetH hSrcDataset, { const double dfNoData = CPLAtof(psOptions->osNoData.c_str()); - if (dfNoData >= static_cast( - std::numeric_limits::min()) && - dfNoData <= static_cast( - std::numeric_limits::max()) && - dfNoData == static_cast( - static_cast(dfNoData))) + if (GDALIsValueExactAs(dfNoData)) { poVRTBand->SetNoDataValueAsUInt64( static_cast(dfNoData)); From 7b93c0eec4aace9ffb5c788fe3462cfe6dfc5f21 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 16:49:04 +0200 Subject: [PATCH 0220/1119] GTiff: DiscardLsb(): use GDALIsValueExactAs<> to check range of nodata --- frmts/gtiff/gtiffdataset_write.cpp | 45 ++++++------------------------ 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/frmts/gtiff/gtiffdataset_write.cpp b/frmts/gtiff/gtiffdataset_write.cpp index c4c17ecfba55..062ba5cd1bfc 100644 --- a/frmts/gtiff/gtiffdataset_write.cpp +++ b/frmts/gtiff/gtiffdataset_write.cpp @@ -1721,10 +1721,7 @@ static void DiscardLsb(GByte *pabyBuffer, GPtrDiff_t nBytes, int iBand, if (nBitsPerSample == 8 && nSampleFormat == SAMPLEFORMAT_UINT) { uint8_t nNoDataValue = 0; - if (bHasNoData && - dfNoDataValue >= std::numeric_limits::min() && - dfNoDataValue <= std::numeric_limits::max() && - dfNoDataValue == static_cast(dfNoDataValue)) + if (bHasNoData && GDALIsValueExactAs(dfNoDataValue)) { nNoDataValue = static_cast(dfNoDataValue); } @@ -1806,9 +1803,7 @@ static void DiscardLsb(GByte *pabyBuffer, GPtrDiff_t nBytes, int iBand, else if (nBitsPerSample == 8 && nSampleFormat == SAMPLEFORMAT_INT) { int8_t nNoDataValue = 0; - if (bHasNoData && dfNoDataValue >= std::numeric_limits::min() && - dfNoDataValue <= std::numeric_limits::max() && - dfNoDataValue == static_cast(dfNoDataValue)) + if (bHasNoData && GDALIsValueExactAs(dfNoDataValue)) { nNoDataValue = static_cast(dfNoDataValue); } @@ -1823,10 +1818,7 @@ static void DiscardLsb(GByte *pabyBuffer, GPtrDiff_t nBytes, int iBand, else if (nBitsPerSample == 16 && nSampleFormat == SAMPLEFORMAT_INT) { int16_t nNoDataValue = 0; - if (bHasNoData && - dfNoDataValue >= std::numeric_limits::min() && - dfNoDataValue <= std::numeric_limits::max() && - dfNoDataValue == static_cast(dfNoDataValue)) + if (bHasNoData && GDALIsValueExactAs(dfNoDataValue)) { nNoDataValue = static_cast(dfNoDataValue); } @@ -1841,10 +1833,7 @@ static void DiscardLsb(GByte *pabyBuffer, GPtrDiff_t nBytes, int iBand, else if (nBitsPerSample == 16 && nSampleFormat == SAMPLEFORMAT_UINT) { uint16_t nNoDataValue = 0; - if (bHasNoData && - dfNoDataValue >= std::numeric_limits::min() && - dfNoDataValue <= std::numeric_limits::max() && - dfNoDataValue == static_cast(dfNoDataValue)) + if (bHasNoData && GDALIsValueExactAs(dfNoDataValue)) { nNoDataValue = static_cast(dfNoDataValue); } @@ -1859,10 +1848,7 @@ static void DiscardLsb(GByte *pabyBuffer, GPtrDiff_t nBytes, int iBand, else if (nBitsPerSample == 32 && nSampleFormat == SAMPLEFORMAT_INT) { int32_t nNoDataValue = 0; - if (bHasNoData && - dfNoDataValue >= std::numeric_limits::min() && - dfNoDataValue <= std::numeric_limits::max() && - dfNoDataValue == static_cast(dfNoDataValue)) + if (bHasNoData && GDALIsValueExactAs(dfNoDataValue)) { nNoDataValue = static_cast(dfNoDataValue); } @@ -1877,10 +1863,7 @@ static void DiscardLsb(GByte *pabyBuffer, GPtrDiff_t nBytes, int iBand, else if (nBitsPerSample == 32 && nSampleFormat == SAMPLEFORMAT_UINT) { uint32_t nNoDataValue = 0; - if (bHasNoData && - dfNoDataValue >= std::numeric_limits::min() && - dfNoDataValue <= std::numeric_limits::max() && - dfNoDataValue == static_cast(dfNoDataValue)) + if (bHasNoData && GDALIsValueExactAs(dfNoDataValue)) { nNoDataValue = static_cast(dfNoDataValue); } @@ -1897,13 +1880,7 @@ static void DiscardLsb(GByte *pabyBuffer, GPtrDiff_t nBytes, int iBand, // FIXME: we should not rely on dfNoDataValue when we support native // data type for nodata int64_t nNoDataValue = 0; - if (bHasNoData && - dfNoDataValue >= - static_cast(std::numeric_limits::min()) && - dfNoDataValue <= - static_cast(std::numeric_limits::max()) && - dfNoDataValue == - static_cast(static_cast(dfNoDataValue))) + if (bHasNoData && GDALIsValueExactAs(dfNoDataValue)) { nNoDataValue = static_cast(dfNoDataValue); } @@ -1920,13 +1897,7 @@ static void DiscardLsb(GByte *pabyBuffer, GPtrDiff_t nBytes, int iBand, // FIXME: we should not rely on dfNoDataValue when we support native // data type for nodata uint64_t nNoDataValue = 0; - if (bHasNoData && - dfNoDataValue >= - static_cast(std::numeric_limits::min()) && - dfNoDataValue <= - static_cast(std::numeric_limits::max()) && - dfNoDataValue == - static_cast(static_cast(dfNoDataValue))) + if (bHasNoData && GDALIsValueExactAs(dfNoDataValue)) { nNoDataValue = static_cast(dfNoDataValue); } From 93d9df44a897be22a9882a29baef81b66d12398b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 16:49:37 +0200 Subject: [PATCH 0221/1119] netCDF: use GDALIsValueExactAs<> to check range of [u]int64 nodata --- frmts/netcdf/netcdfdataset.cpp | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/frmts/netcdf/netcdfdataset.cpp b/frmts/netcdf/netcdfdataset.cpp index b95c316528ec..2960870dccf4 100644 --- a/frmts/netcdf/netcdfdataset.cpp +++ b/frmts/netcdf/netcdfdataset.cpp @@ -70,6 +70,7 @@ #include "cpl_time.h" #include "gdal.h" #include "gdal_frmts.h" +#include "gdal_priv_templates.hpp" #include "ogr_core.h" #include "ogr_srs_api.h" @@ -759,22 +760,12 @@ netCDFRasterBand::netCDFRasterBand(const netCDFRasterBand::CONSTRUCTOR_OPEN &, #ifdef NCDF_DEBUG CPLDebug("GDAL_netCDF", "SetNoDataValue(%f) read", dfNoData); #endif - if (eDataType == GDT_Int64 && - dfNoData >= - static_cast(std::numeric_limits::min()) && - dfNoData <= - static_cast(std::numeric_limits::max()) && - dfNoData == static_cast(static_cast(dfNoData))) + if (eDataType == GDT_Int64 && GDALIsValueExactAs(dfNoData)) { SetNoDataValueNoUpdate(static_cast(dfNoData)); } else if (eDataType == GDT_UInt64 && - dfNoData >= static_cast( - std::numeric_limits::min()) && - dfNoData <= static_cast( - std::numeric_limits::max()) && - dfNoData == - static_cast(static_cast(dfNoData))) + GDALIsValueExactAs(dfNoData)) { SetNoDataValueNoUpdate(static_cast(dfNoData)); } From 6ee77e93298a9ea8dcb12b250bdad5e66dfb3291 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 16:50:22 +0200 Subject: [PATCH 0222/1119] Zarr: SerializeNumericNoData(): use CPLJSonObject::Add(uint64_t) to avoid potential undefined behavior casts --- frmts/zarr/zarr_array.cpp | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/frmts/zarr/zarr_array.cpp b/frmts/zarr/zarr_array.cpp index a8df5696947a..afb2da159046 100644 --- a/frmts/zarr/zarr_array.cpp +++ b/frmts/zarr/zarr_array.cpp @@ -557,20 +557,7 @@ void ZarrArray::SerializeNumericNoData(CPLJSONObject &oRoot) const else if (m_oType.GetNumericDataType() == GDT_UInt64) { const auto nVal = GetNoDataValueAsUInt64(); - if (nVal <= static_cast(std::numeric_limits::max())) - { - oRoot.Add("fill_value", static_cast(nVal)); - } - else if (nVal == static_cast(static_cast(nVal))) - { - oRoot.Add("fill_value", static_cast(nVal)); - } - else - { - // not really compliant... - oRoot.Add("fill_value", - CPLSPrintf(CPL_FRMT_GUIB, static_cast(nVal))); - } + oRoot.Add("fill_value", static_cast(nVal)); } else { From 8005cf1a5124163b15f3c4b0ca89c8e3bfad3fff Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 16:50:49 +0200 Subject: [PATCH 0223/1119] GPKG: use GDALIsValueInRange<> to avoid potential undefined behavior casts --- ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp index 22e8fc1fb658..ad16578e40d5 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp @@ -34,6 +34,7 @@ #include "cpl_time.h" #include "ogr_p.h" #include "sqlite_rtree_bulk_load/wrapper.h" +#include "gdal_priv_templates.hpp" #include #include @@ -2215,8 +2216,7 @@ static bool CheckFIDAndFIDColumnConsistency(const OGRFeature *poFeature, { const double dfFID = poFeature->GetFieldAsDouble(iFIDAsRegularColumnIndex); - if (dfFID >= static_cast(std::numeric_limits::min()) && - dfFID <= static_cast(std::numeric_limits::max())) + if (GDALIsValueInRange(dfFID)) { const auto nFID = static_cast(dfFID); if (nFID == poFeature->GetFID()) From 6138375f3c20a1bb7454264813b7d915295b1af2 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 16:51:00 +0200 Subject: [PATCH 0224/1119] OpenFileGDB: use GDALIsValueInRange<> to avoid potential undefined behavior casts --- .../openfilegdb/filegdbtable_write.cpp | 16 ++++------------ .../openfilegdb/ogropenfilegdblayer_write.cpp | 4 ++-- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp index bb5d8f5e6b54..458b842f015c 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_write.cpp @@ -52,6 +52,7 @@ #include "cpl_time.h" #include "cpl_vsi.h" #include "filegdbtable_priv.h" +#include "gdal_priv_templates.hpp" #include "ogr_api.h" #include "ogr_core.h" #include "ogr_geometry.h" @@ -379,24 +380,15 @@ bool FileGDBTable::Sync(VSILFILE *fpTable, VSILFILE *fpTableX) /************************************************************************/ #define CHECK_CAN_BE_ENCODED_ON_VARUINT(v, msg) \ - if (!((v) >= 0 && \ - (v) <= static_cast(std::numeric_limits::max()))) \ + if (!GDALIsValueInRange(v)) \ { \ CPLError(CE_Failure, CPLE_AppDefined, msg); \ return false; \ } #define CHECK_CAN_BE_ENCODED_ON_VARINT(v, oldV, msg) \ - if (!((v) >= static_cast(std::numeric_limits::min()) && \ - (v) <= static_cast(std::numeric_limits::max()))) \ - { \ - CPLError(CE_Failure, CPLE_AppDefined, msg); \ - return false; \ - } \ - if (!(((v) - (oldV)) >= \ - static_cast(std::numeric_limits::min()) && \ - ((v) - (oldV)) <= \ - static_cast(std::numeric_limits::max()))) \ + if (!GDALIsValueInRange(v) || \ + !GDALIsValueInRange((v) - (oldV))) \ { \ CPLError(CE_Failure, CPLE_AppDefined, msg); \ return false; \ diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp index 6df7b3b7fde9..22053cf8c98d 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp @@ -45,6 +45,7 @@ #include "cpl_error.h" #include "cpl_minixml.h" #include "cpl_string.h" +#include "gdal_priv_templates.hpp" #include "ogr_api.h" #include "ogr_core.h" #include "ogr_feature.h" @@ -2423,8 +2424,7 @@ static bool CheckFIDAndFIDColumnConsistency(const OGRFeature *poFeature, { const double dfFID = poFeature->GetFieldAsDouble(iFIDAsRegularColumnIndex); - if (dfFID >= static_cast(std::numeric_limits::min()) && - dfFID <= static_cast(std::numeric_limits::max())) + if (GDALIsValueInRange(dfFID)) { const auto nFID = static_cast(dfFID); if (nFID == poFeature->GetFID()) From 66f9a52689555fb23f6592d26452d67a3b4a9092 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 13:54:59 +0200 Subject: [PATCH 0225/1119] GDALRasterBand::ComputeStatistics(): fix bad progress percentage computation (recent regression caused by 42adada1f1ff55a5109de17565d3c107de48c531) --- gcore/gdalrasterband.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gcore/gdalrasterband.cpp b/gcore/gdalrasterband.cpp index 1495e75720e4..2333c9a667ab 100644 --- a/gcore/gdalrasterband.cpp +++ b/gcore/gdalrasterband.cpp @@ -5995,8 +5995,8 @@ CPLErr GDALRasterBand::ComputeStatistics(int bApproxOK, double *pdfMin, poBlock->DropLock(); if (!pfnProgress(static_cast(iSampleBlock) / - static_cast(nBlocksPerRow) * - nBlocksPerColumn, + (static_cast(nBlocksPerRow) * + nBlocksPerColumn), "Compute Statistics", pProgressData)) { ReportError(CE_Failure, CPLE_UserInterrupt, @@ -6147,10 +6147,10 @@ CPLErr GDALRasterBand::ComputeStatistics(int bApproxOK, double *pdfMin, poBlock->DropLock(); - if (!pfnProgress(static_cast(iSampleBlock) / - static_cast(nBlocksPerRow) * - nBlocksPerColumn, - "Compute Statistics", pProgressData)) + if (!pfnProgress( + static_cast(iSampleBlock) / + (static_cast(nBlocksPerRow) * nBlocksPerColumn), + "Compute Statistics", pProgressData)) { ReportError(CE_Failure, CPLE_UserInterrupt, "User terminated"); CPLFree(pabyMaskData); From e6599bfc6a8428438b6203e1bbdc483542ab0ca1 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 19:22:14 +0200 Subject: [PATCH 0226/1119] OGRSpatialReference::importFromUSGS(): avoid out-of-bounds access to array (fix regression of cab1cc95a3e9343d003a7e21acea4c3efe56b88d) --- ogr/ogr_srs_usgs.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ogr/ogr_srs_usgs.cpp b/ogr/ogr_srs_usgs.cpp index 52faad8db080..f506aa9ce360 100644 --- a/ogr/ogr_srs_usgs.cpp +++ b/ogr/ogr_srs_usgs.cpp @@ -777,7 +777,8 @@ OGRErr OGRSpatialReference::importFromUSGS(long iProjSys, long iZone, if (IsLocal() || IsProjected()) SetLinearUnits(SRS_UL_METER, 1.0); - if (iDatum < NUMBER_OF_USGS_ELLIPSOIDS && aoEllipsUSGS[iDatum] == 7030) + if (iDatum >= 0 && iDatum < NUMBER_OF_USGS_ELLIPSOIDS && + aoEllipsUSGS[iDatum] == 7030) { if (AutoIdentifyEPSG() == OGRERR_NONE) { From c2eca8cc44009baa097ae5f7d89156bc959092bb Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 21:11:55 +0200 Subject: [PATCH 0227/1119] test_ogr_pmtiles_read_corrupted_min_zoom_larger_than_30: skip on s390x --- autotest/ogr/ogr_pmtiles.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/autotest/ogr/ogr_pmtiles.py b/autotest/ogr/ogr_pmtiles.py index 81361f82def5..d554a41e1c79 100755 --- a/autotest/ogr/ogr_pmtiles.py +++ b/autotest/ogr/ogr_pmtiles.py @@ -29,6 +29,7 @@ ############################################################################### import json +import os import gdaltest import ogrtest @@ -603,7 +604,12 @@ def test_ogr_pmtiles_read_corrupted_min_zoom_larger_than_max_zoom(): ############################################################################### - +# Test started to fail on Travis s390x starting with https://github.com/OSGeo/gdal/pull/10274 +# which is totally unrelated... +@pytest.mark.skipif( + os.environ.get("BUILD_NAME", "") == "s390x", + reason="Fails randomly on that platform", +) def test_ogr_pmtiles_read_corrupted_min_zoom_larger_than_30(): tmpfilename = "/vsimem/tmp.pmtiles" From 4c0e1ce40b6091d8b18282d82145b34b2c4454bd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 20:00:53 +0200 Subject: [PATCH 0228/1119] OGR SQL: fix crash when the ON expression of a JOIN contains OGR special fields (in particular feature id) --- autotest/ogr/ogr_join_test.py | 66 ++++++++++++++ ogr/ogrsf_frmts/generic/ogr_gensql.cpp | 114 +++++++++++++++++-------- 2 files changed, 146 insertions(+), 34 deletions(-) diff --git a/autotest/ogr/ogr_join_test.py b/autotest/ogr/ogr_join_test.py index 3ff1e9084178..d2bea80434de 100755 --- a/autotest/ogr/ogr_join_test.py +++ b/autotest/ogr/ogr_join_test.py @@ -481,3 +481,69 @@ def test_ogr_join_23(): ds.ReleaseResultSet(sql_lyr) ds = None + + +############################################################################### +# Test join on special fields (FID) + + +def test_ogr_join_on_special_field(): + + ds = ogr.GetDriverByName("Memory").CreateDataSource("") + lyr1 = ds.CreateLayer("lyr1", options=["FID=fid1"]) + lyr1.CreateField(ogr.FieldDefn("a")) + lyr2 = ds.CreateLayer("lyr2", options=["FID=fid2"]) + lyr2.CreateField(ogr.FieldDefn("b")) + f = ogr.Feature(lyr1.GetLayerDefn()) + f.SetFID(1) + f["a"] = "a1" + f.SetGeometry(ogr.CreateGeometryFromWkt("POLYGON((0 0,0 1,1 1,0 0))")) + f.SetStyleString("dummy") + lyr1.CreateFeature(f) + f = ogr.Feature(lyr1.GetLayerDefn()) + f.SetFID(2) + f["a"] = "a2" + lyr1.CreateFeature(f) + f = ogr.Feature(lyr2.GetLayerDefn()) + f.SetFID(1) + f["b"] = "b1" + lyr2.CreateFeature(f) + f = ogr.Feature(lyr2.GetLayerDefn()) + f.SetFID(2) + f["b"] = "b2" + lyr2.CreateFeature(f) + + with ds.ExecuteSQL( + "SELECT a, b FROM lyr1 LEFT JOIN lyr2 ON lyr1.fid1 = lyr2.fid2" + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f["a"] == "a1" + assert f["b"] == "b1" + f = sql_lyr.GetNextFeature() + assert f["a"] == "a2" + assert f["b"] == "b2" + assert sql_lyr.GetNextFeature() is None + + # Kind of dummy, but testing Real special field ... + with ds.ExecuteSQL( + "SELECT a, b FROM lyr1 LEFT JOIN lyr2 ON lyr1.OGR_GEOM_AREA = 0.5" + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f["a"] == "a1" + assert f["b"] == "b1" + f = sql_lyr.GetNextFeature() + assert f["a"] == "a2" + assert f["b"] is None + assert sql_lyr.GetNextFeature() is None + + # Kind of dummy, but testing String special field ... + with ds.ExecuteSQL( + "SELECT a, b FROM lyr1 LEFT JOIN lyr2 ON lyr1.OGR_STYLE = 'dummy'" + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f["a"] == "a1" + assert f["b"] == "b1" + f = sql_lyr.GetNextFeature() + assert f["a"] == "a2" + assert f["b"] is None + assert sql_lyr.GetNextFeature() is None diff --git a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp index b67645c67f77..cef9d3aa07c7 100644 --- a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp +++ b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp @@ -1181,51 +1181,97 @@ static CPLString GetFilterForJoin(swq_expr_node *poExpr, OGRFeature *poSrcFeat, { return ""; } - OGRFieldType ePrimaryFieldType = - poSrcFeat->GetFieldDefnRef(poExpr->field_index)->GetType(); - OGRField *psSrcField = - poSrcFeat->GetRawFieldRef(poExpr->field_index); - - switch (ePrimaryFieldType) + const auto poSrcFDefn = poSrcFeat->GetDefnRef(); + if (poExpr->field_index >= poSrcFDefn->GetFieldCount()) { - case OFTInteger: - return CPLString().Printf("%d", psSrcField->Integer); - break; + CPLAssert(poExpr->field_index < + poSrcFDefn->GetFieldCount() + SPECIAL_FIELD_COUNT); + switch (SpecialFieldTypes[poExpr->field_index - + poSrcFDefn->GetFieldCount()]) + { + case SWQ_INTEGER: + case SWQ_INTEGER64: + return CPLString().Printf( + CPL_FRMT_GIB, poSrcFeat->GetFieldAsInteger64( + poExpr->field_index)); + break; + case SWQ_FLOAT: + return CPLString().Printf( + "%.18g", + poSrcFeat->GetFieldAsDouble(poExpr->field_index)); + break; + default: + { + char *pszEscaped = CPLEscapeString( + poSrcFeat->GetFieldAsString(poExpr->field_index), + -1, CPLES_SQL); + CPLString osRes = "'"; + osRes += pszEscaped; + osRes += "'"; + CPLFree(pszEscaped); + return osRes; + } + } + } + else + { + const OGRFieldType ePrimaryFieldType = + poSrcFeat->GetFieldDefnRef(poExpr->field_index)->GetType(); + const OGRField *psSrcField = + poSrcFeat->GetRawFieldRef(poExpr->field_index); - case OFTInteger64: - return CPLString().Printf(CPL_FRMT_GIB, - psSrcField->Integer64); - break; + switch (ePrimaryFieldType) + { + case OFTInteger: + return CPLString().Printf("%d", psSrcField->Integer); + break; + + case OFTInteger64: + return CPLString().Printf(CPL_FRMT_GIB, + psSrcField->Integer64); + break; + + case OFTReal: + return CPLString().Printf("%.18g", psSrcField->Real); + break; - case OFTReal: - return CPLString().Printf("%.16g", psSrcField->Real); + case OFTString: + { + char *pszEscaped = CPLEscapeString( + psSrcField->String, + static_cast(strlen(psSrcField->String)), + CPLES_SQL); + CPLString osRes = "'"; + osRes += pszEscaped; + osRes += "'"; + CPLFree(pszEscaped); + return osRes; + } break; - case OFTString: - { - char *pszEscaped = CPLEscapeString( - psSrcField->String, - static_cast(strlen(psSrcField->String)), - CPLES_SQL); - CPLString osRes = "'"; - osRes += pszEscaped; - osRes += "'"; - CPLFree(pszEscaped); - return osRes; + default: + CPLAssert(false); + return ""; } - break; - - default: - CPLAssert(false); - return ""; } } if (poExpr->table_index == secondary_table) { - OGRFieldDefn *poSecondaryFieldDefn = - poJoinLayer->GetLayerDefn()->GetFieldDefn(poExpr->field_index); - return CPLSPrintf("\"%s\"", poSecondaryFieldDefn->GetNameRef()); + const auto poJoinFDefn = poJoinLayer->GetLayerDefn(); + if (poExpr->field_index >= poJoinFDefn->GetFieldCount()) + { + CPLAssert(poExpr->field_index < + poJoinFDefn->GetFieldCount() + SPECIAL_FIELD_COUNT); + return SpecialFieldNames[poExpr->field_index - + poJoinFDefn->GetFieldCount()]; + } + else + { + const OGRFieldDefn *poSecondaryFieldDefn = + poJoinFDefn->GetFieldDefn(poExpr->field_index); + return CPLSPrintf("\"%s\"", poSecondaryFieldDefn->GetNameRef()); + } } CPLAssert(false); From 3d8b1e07052df8ebf528a897db06bb5e892a3771 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 23:39:30 +0200 Subject: [PATCH 0229/1119] CSV: allow inf, -inf and nan as numeric values --- autotest/ogr/data/csv/inf_nan.csv | 5 +++++ autotest/ogr/ogr_csv.py | 21 +++++++++++++++++++++ ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp | 20 +++++++++++++------- 3 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 autotest/ogr/data/csv/inf_nan.csv diff --git a/autotest/ogr/data/csv/inf_nan.csv b/autotest/ogr/data/csv/inf_nan.csv new file mode 100644 index 000000000000..7ab5a1f7cd1e --- /dev/null +++ b/autotest/ogr/data/csv/inf_nan.csv @@ -0,0 +1,5 @@ +id,v +1,10 +2,inf +3,-inf +4,NaN diff --git a/autotest/ogr/ogr_csv.py b/autotest/ogr/ogr_csv.py index 22d9111f4cc7..2c315d484bf5 100755 --- a/autotest/ogr/ogr_csv.py +++ b/autotest/ogr/ogr_csv.py @@ -27,6 +27,7 @@ # Boston, MA 02111-1307, USA. ############################################################################### +import math import pathlib import sys @@ -3133,6 +3134,26 @@ def test_ogr_csv_force_opening(tmp_vsimem): assert ds.GetDriver().GetDescription() == "CSV" +############################################################################### +# Test opening a CSV file with inf/nan numeric values + + +@gdaltest.enable_exceptions() +def test_ogr_csv_inf_nan(): + + ds = gdal.OpenEx("data/csv/inf_nan.csv", open_options=["AUTODETECT_TYPE=YES"]) + lyr = ds.GetLayer(0) + assert lyr.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTReal + f = lyr.GetNextFeature() + assert f["v"] == 10.0 + f = lyr.GetNextFeature() + assert f["v"] == float("inf") + f = lyr.GetNextFeature() + assert f["v"] == float("-inf") + f = lyr.GetNextFeature() + assert math.isnan(f["v"]) + + ############################################################################### diff --git a/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp b/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp index cee5cbcd4916..14e76fae890a 100644 --- a/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp +++ b/ogr/ogrsf_frmts/csv/ogrcsvlayer.cpp @@ -1049,7 +1049,10 @@ char **OGRCSVLayer::AutodetectFieldTypes(CSLConstList papszOpenOptions, else eOGRFieldType = OFTInteger; } - else if (eType == CPL_VALUE_REAL) + else if (eType == CPL_VALUE_REAL || + EQUAL(papszTokens[iField], "inf") || + EQUAL(papszTokens[iField], "-inf") || + EQUAL(papszTokens[iField], "nan")) { eOGRFieldType = OFTReal; } @@ -1466,14 +1469,16 @@ OGRFeature *OGRCSVLayer::GetNextUnfilteredFeature() if (chComma) *chComma = '.'; } - CPLValueType eType = CPLGetValueType(papszTokens[iAttr]); - if (eType == CPL_VALUE_INTEGER || eType == CPL_VALUE_REAL) + char *endptr = nullptr; + const double dfVal = + CPLStrtodDelim(papszTokens[iAttr], &endptr, '.'); + if (endptr == papszTokens[iAttr] + strlen(papszTokens[iAttr])) { - poFeature->SetField(iOGRField, papszTokens[iAttr]); + poFeature->SetField(iOGRField, dfVal); if (!bWarningBadTypeOrWidth && (eFieldType == OFTInteger || eFieldType == OFTInteger64) && - eType == CPL_VALUE_REAL) + CPLGetValueType(papszTokens[iAttr]) == CPL_VALUE_REAL) { bWarningBadTypeOrWidth = true; CPLError(CE_Warning, CPLE_AppDefined, @@ -1495,8 +1500,9 @@ OGRFeature *OGRCSVLayer::GetNextUnfilteredFeature() nNextFID, poFieldDefn->GetNameRef()); } else if (!bWarningBadTypeOrWidth && - eType == CPL_VALUE_REAL && - poFieldDefn->GetWidth() > 0) + poFieldDefn->GetWidth() > 0 && + CPLGetValueType(papszTokens[iAttr]) == + CPL_VALUE_REAL) { const char *pszDot = strchr(papszTokens[iAttr], '.'); const int nPrecision = From 58dfa4a431c218e45eef53e41317a4294b61c4d7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 23:40:30 +0200 Subject: [PATCH 0230/1119] Python bindings: check validity of GDALAccess flag passed to gdal.Open() --- autotest/gcore/basic_test.py | 9 +++++++++ swig/include/python/typemaps_python.i | 14 ++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/autotest/gcore/basic_test.py b/autotest/gcore/basic_test.py index 2d2e2a91eb61..2719fc963dcc 100755 --- a/autotest/gcore/basic_test.py +++ b/autotest/gcore/basic_test.py @@ -68,6 +68,15 @@ def test_basic_test_1(): pytest.fail("did not get expected error message, got %s" % gdal.GetLastErrorMsg()) +def test_basic_test_invalid_open_flag(): + with pytest.raises(Exception, match="invalid value for GDALAccess"): + gdal.Open("data/byte.tif", "invalid") + + assert gdal.OF_RASTER not in (gdal.GA_ReadOnly, gdal.GA_Update) + with pytest.raises(Exception, match="invalid value for GDALAccess"): + gdal.Open("data/byte.tif", gdal.OF_RASTER) + + @pytest.mark.skipif(sys.platform != "linux", reason="Incorrect platform") def test_basic_test_strace_non_existing_file(): diff --git a/swig/include/python/typemaps_python.i b/swig/include/python/typemaps_python.i index 5ff42d4f31c3..ef5276f3cc78 100644 --- a/swig/include/python/typemaps_python.i +++ b/swig/include/python/typemaps_python.i @@ -3065,6 +3065,20 @@ OBJECT_LIST_INPUT(GDALEDTComponentHS) %#endif } +%typemap(in) GDALAccess +{ + // %typemap(in) GDALAccess + int val = 0; + int ecode = SWIG_AsVal_int($input, &val); + if (!SWIG_IsOK(ecode)) { + SWIG_exception_fail(SWIG_ArgError(ecode), "invalid value for GDALAccess"); + } + if( val != GA_ReadOnly && val != GA_Update ) + { + SWIG_exception_fail(SWIG_ValueError, "invalid value for GDALAccess"); + } + $1 = static_cast(val); +} %typemap(in) GDALRIOResampleAlg { From dc95162f236a1ffbb1c5109273cf423420d5fa38 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 22 Jun 2024 23:36:53 +0200 Subject: [PATCH 0231/1119] OGR SQL: use Kahan-Babuska-Neumaier algorithm for accurate SUM(), like in recent SQLite versions --- autotest/ogr/ogr_sql_test.py | 30 ++++++++++++++++++++++ ogr/ogr_swq.h | 13 +++++++++- ogr/ogrsf_frmts/generic/ogr_gensql.cpp | 11 ++++---- ogr/swq.cpp | 35 +++++++++++++++++++++++--- 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/autotest/ogr/ogr_sql_test.py b/autotest/ogr/ogr_sql_test.py index a328f8b36bda..4d68b46c2409 100755 --- a/autotest/ogr/ogr_sql_test.py +++ b/autotest/ogr/ogr_sql_test.py @@ -27,6 +27,7 @@ # Boston, MA 02111-1307, USA. ############################################################################### +import math import os import shutil @@ -2102,3 +2103,32 @@ def test_ogr_sql_identifier_hidden(): with ds.ExecuteSQL("SELECT 'foo' AS hidden FROM hidden") as sql_lyr: f = sql_lyr.GetNextFeature() assert f["hidden"] == "foo" + + +@pytest.mark.parametrize( + "input,expected_output", + [ + [(1, 1e100, 1, -1e100), 2], + [(float("inf"), 1), float("inf")], + [(1, float("-inf")), float("-inf")], + [(1, float("nan")), float("nan")], + [(float("inf"), float("-inf")), float("nan")], + ], +) +def test_ogr_sql_kahan_babuska_eumaier_summation(input, expected_output): + """Test accurate SUM() implementation using Kahan-Babuska-Neumaier algorithm""" + + ds = ogr.GetDriverByName("Memory").CreateDataSource("") + lyr = ds.CreateLayer("test") + lyr.CreateField(ogr.FieldDefn("v", ogr.OFTReal)) + for v in input: + feat = ogr.Feature(lyr.GetLayerDefn()) + feat["v"] = v + lyr.CreateFeature(feat) + + with ds.ExecuteSQL("SELECT SUM(v) FROM test") as sql_lyr: + f = sql_lyr.GetNextFeature() + if math.isnan(expected_output): + assert math.isnan(f["SUM_v"]) + else: + assert f["SUM_v"] == expected_output diff --git a/ogr/ogr_swq.h b/ogr/ogr_swq.h index d2461e4b0482..e8686baf728c 100644 --- a/ogr/ogr_swq.h +++ b/ogr/ogr_swq.h @@ -356,11 +356,22 @@ class CPL_UNSTABLE_API swq_summary bool operator()(const CPLString &, const CPLString &) const; }; + //! Return the sum, using Kahan-Babuska-Neumaier algorithm. + // Cf cf KahanBabushkaNeumaierSum of https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Further_enhancements + double sum() const + { + return sum_only_finite_terms ? sum_acc + sum_correction : sum_acc; + } + GIntBig count = 0; std::vector oVectorDistinctValues{}; std::set oSetDistinctValues{}; - double sum = 0.0; + bool sum_only_finite_terms = true; + // Sum accumulator. To get the accurate sum, use the sum() method + double sum_acc = 0.0; + // Sum correction term. + double sum_correction = 0.0; double min = 0.0; double max = 0.0; CPLString osMin{}; diff --git a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp index ac961137e95c..9c9e4972c6ae 100644 --- a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp +++ b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp @@ -992,7 +992,7 @@ bool OGRGenSQLResultsLayer::PrepareSummary() for (int iField = 0; iField < psSelectInfo->result_columns(); iField++) { - swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; + const swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; if (!psSelectInfo->column_summary.empty()) { const swq_summary &oSummary = @@ -1000,12 +1000,12 @@ bool OGRGenSQLResultsLayer::PrepareSummary() if (psColDef->col_func == SWQCF_AVG && oSummary.count > 0) { + const double dfAvg = oSummary.sum() / oSummary.count; if (psColDef->field_type == SWQ_DATE || psColDef->field_type == SWQ_TIME || psColDef->field_type == SWQ_TIMESTAMP) { struct tm brokendowntime; - double dfAvg = oSummary.sum / oSummary.count; CPLUnixTimeToYMDHMS(static_cast(dfAvg), &brokendowntime); m_poSummaryFeature->SetField( @@ -1017,8 +1017,9 @@ bool OGRGenSQLResultsLayer::PrepareSummary() 0); } else - m_poSummaryFeature->SetField( - iField, oSummary.sum / oSummary.count); + { + m_poSummaryFeature->SetField(iField, dfAvg); + } } else if (psColDef->col_func == SWQCF_MIN && oSummary.count > 0) { @@ -1045,7 +1046,7 @@ bool OGRGenSQLResultsLayer::PrepareSummary() else if (psColDef->col_func == SWQCF_COUNT) m_poSummaryFeature->SetField(iField, oSummary.count); else if (psColDef->col_func == SWQCF_SUM && oSummary.count > 0) - m_poSummaryFeature->SetField(iField, oSummary.sum); + m_poSummaryFeature->SetField(iField, oSummary.sum()); } else if (psColDef->col_func == SWQCF_COUNT) m_poSummaryFeature->SetField(iField, 0); diff --git a/ogr/swq.cpp b/ogr/swq.cpp index c4cd3d2edc84..5132f9de128d 100644 --- a/ogr/swq.cpp +++ b/ogr/swq.cpp @@ -497,15 +497,44 @@ const char *swq_select_summarize(swq_select *select_info, int dest_column, brokendowntime.tm_sec = static_cast(sField.Date.Second); summary.count++; - summary.sum += CPLYMDHMSToUnixTime(&brokendowntime); - summary.sum += + summary.sum_acc += CPLYMDHMSToUnixTime(&brokendowntime); + summary.sum_acc += fmod(static_cast(sField.Date.Second), 1.0); } } else { summary.count++; - summary.sum += CPLAtof(value); + + // Cf KahanBabushkaNeumaierSum of + // https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Further_enhancements + // We set a number of temporary variables as volatile, to + // prevent potential undesired compiler optimizations. + + const double dfNewVal = CPLAtof(value); + const volatile double new_sum_acc = + summary.sum_acc + dfNewVal; + if (summary.sum_only_finite_terms && + std::isfinite(dfNewVal)) + { + if (std::fabs(summary.sum_acc) >= std::fabs(dfNewVal)) + { + const volatile double diff = + (summary.sum_acc - new_sum_acc); + summary.sum_correction += (diff + dfNewVal); + } + else + { + const volatile double diff = + (dfNewVal - new_sum_acc); + summary.sum_correction += (diff + summary.sum_acc); + } + } + else + { + summary.sum_only_finite_terms = false; + } + summary.sum_acc = new_sum_acc; } } break; From f404ca3d4135942622712246584fc090c2da077c Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 23 Jun 2024 15:52:44 +0200 Subject: [PATCH 0232/1119] OGR SQL: avoid going through string serialization for MIN(), MAX(), SUM(), AVG() on numeric fields --- ogr/ogr_swq.h | 8 +- ogr/ogrsf_frmts/generic/ogr_gensql.cpp | 124 +++++++++++++++++------- ogr/swq.cpp | 128 ++++++++++++------------- 3 files changed, 153 insertions(+), 107 deletions(-) diff --git a/ogr/ogr_swq.h b/ogr/ogr_swq.h index e8686baf728c..a61cd0cc4c18 100644 --- a/ogr/ogr_swq.h +++ b/ogr/ogr_swq.h @@ -487,9 +487,15 @@ class CPL_UNSTABLE_API swq_select std::map> m_exclude_fields{}; }; +/* This method should generally be invoked with pszValue set, except when + * called on a non-DISTINCT column definition of numeric type (SWQ_BOOLEAN, + * SWQ_INTEGER, SWQ_INTEGER64, SWQ_FLOAT), in which case pdfValue should + * rather be set. + */ const char CPL_UNSTABLE_API *swq_select_summarize(swq_select *select_info, int dest_column, - const char *value); + const char *pszValue, + const double *pdfValue); int CPL_UNSTABLE_API swq_is_reserved_keyword(const char *pszStr); diff --git a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp index 9c9e4972c6ae..3c2838a6ccab 100644 --- a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp +++ b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp @@ -827,44 +827,70 @@ bool OGRGenSQLResultsLayer::PrepareSummary() /* the where clause and no column references OGR_GEOMETRY, */ /* OGR_GEOM_WKT or OGR_GEOM_AREA special fields. */ /* -------------------------------------------------------------------- */ - int bSaveIsGeomIgnored = m_poSrcLayer->GetLayerDefn()->IsGeometryIgnored(); + + struct TempGeomIgnoredSetter + { + OGRFeatureDefn &m_oDefn; + const int m_bSaveIsGeomIgnored; + + explicit TempGeomIgnoredSetter(OGRFeatureDefn *poDefn) + : m_oDefn(*poDefn), + m_bSaveIsGeomIgnored(poDefn->IsGeometryIgnored()) + { + m_oDefn.SetGeometryIgnored(true); + } + + ~TempGeomIgnoredSetter() + { + m_oDefn.SetGeometryIgnored(m_bSaveIsGeomIgnored); + } + }; + + auto poSrcLayerDefn = m_poSrcLayer->GetLayerDefn(); + std::unique_ptr oTempGeomIgnoredSetter; + if (m_poFilterGeom == nullptr && (psSelectInfo->where_expr == nullptr || !ContainGeomSpecialField(psSelectInfo->where_expr))) { - int bFoundGeomExpr = FALSE; + bool bFoundGeomExpr = false; for (int iField = 0; iField < psSelectInfo->result_columns(); iField++) { - swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; + const swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; if (psColDef->table_index == 0 && psColDef->field_index != -1) { OGRLayer *poLayer = m_apoTableLayers[psColDef->table_index]; - int nSpecialFieldIdx = psColDef->field_index - - poLayer->GetLayerDefn()->GetFieldCount(); + const int nSpecialFieldIdx = + psColDef->field_index - + poLayer->GetLayerDefn()->GetFieldCount(); if (nSpecialFieldIdx == SPF_OGR_GEOMETRY || nSpecialFieldIdx == SPF_OGR_GEOM_WKT || nSpecialFieldIdx == SPF_OGR_GEOM_AREA) { - bFoundGeomExpr = TRUE; + bFoundGeomExpr = true; break; } if (psColDef->field_index == GEOM_FIELD_INDEX_TO_ALL_FIELD_INDEX(poLayer->GetLayerDefn(), 0)) { - bFoundGeomExpr = TRUE; + bFoundGeomExpr = true; break; } } if (psColDef->expr != nullptr && ContainGeomSpecialField(psColDef->expr)) { - bFoundGeomExpr = TRUE; + bFoundGeomExpr = true; break; } } if (!bFoundGeomExpr) - m_poSrcLayer->GetLayerDefn()->SetGeometryIgnored(TRUE); + { + // cppcheck-suppress unreadVariable + oTempGeomIgnoredSetter = + std::make_unique(poSrcLayerDefn); + } } /* -------------------------------------------------------------------- */ @@ -888,7 +914,6 @@ bool OGRGenSQLResultsLayer::PrepareSummary() m_poSummaryFeature->SetField(0, static_cast(nRes)); } - m_poSrcLayer->GetLayerDefn()->SetGeometryIgnored(bSaveIsGeomIgnored); return TRUE; } @@ -896,64 +921,89 @@ bool OGRGenSQLResultsLayer::PrepareSummary() /* Otherwise, process all source feature through the summary */ /* building facilities of SWQ. */ /* -------------------------------------------------------------------- */ - const char *pszError = nullptr; for (auto &&poSrcFeature : *m_poSrcLayer) { for (int iField = 0; iField < psSelectInfo->result_columns(); iField++) { - swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; + const swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; + const char *pszError = nullptr; if (psColDef->col_func == SWQCF_COUNT) { /* psColDef->field_index can be -1 in the case of a COUNT(*) */ if (psColDef->field_index < 0) - pszError = swq_select_summarize(psSelectInfo, iField, ""); - else if (IS_GEOM_FIELD_INDEX(m_poSrcLayer->GetLayerDefn(), + pszError = + swq_select_summarize(psSelectInfo, iField, "", nullptr); + else if (IS_GEOM_FIELD_INDEX(poSrcLayerDefn, psColDef->field_index)) { - int iSrcGeomField = ALL_FIELD_INDEX_TO_GEOM_FIELD_INDEX( - m_poSrcLayer->GetLayerDefn(), psColDef->field_index); - OGRGeometry *poGeom = + const int iSrcGeomField = + ALL_FIELD_INDEX_TO_GEOM_FIELD_INDEX( + poSrcLayerDefn, psColDef->field_index); + const OGRGeometry *poGeom = poSrcFeature->GetGeomFieldRef(iSrcGeomField); if (poGeom != nullptr) - pszError = - swq_select_summarize(psSelectInfo, iField, ""); - else - pszError = nullptr; + pszError = swq_select_summarize(psSelectInfo, iField, + "", nullptr); } else if (poSrcFeature->IsFieldSetAndNotNull( psColDef->field_index)) - pszError = swq_select_summarize( - psSelectInfo, iField, - poSrcFeature->GetFieldAsString(psColDef->field_index)); - else - pszError = nullptr; + { + if (!psColDef->distinct_flag) + { + pszError = swq_select_summarize(psSelectInfo, iField, + "", nullptr); + } + else + { + const char *pszVal = poSrcFeature->GetFieldAsString( + psColDef->field_index); + pszError = swq_select_summarize(psSelectInfo, iField, + pszVal, nullptr); + } + } } else { - const char *pszVal = nullptr; if (poSrcFeature->IsFieldSetAndNotNull(psColDef->field_index)) - pszVal = - poSrcFeature->GetFieldAsString(psColDef->field_index); - pszError = swq_select_summarize(psSelectInfo, iField, pszVal); + { + if (!psColDef->distinct_flag && + (psColDef->field_type == SWQ_BOOLEAN || + psColDef->field_type == SWQ_INTEGER || + psColDef->field_type == SWQ_INTEGER64 || + psColDef->field_type == SWQ_FLOAT)) + { + const double dfValue = poSrcFeature->GetFieldAsDouble( + psColDef->field_index); + pszError = swq_select_summarize(psSelectInfo, iField, + nullptr, &dfValue); + } + else + { + const char *pszVal = poSrcFeature->GetFieldAsString( + psColDef->field_index); + pszError = swq_select_summarize(psSelectInfo, iField, + pszVal, nullptr); + } + } + else + { + pszError = swq_select_summarize(psSelectInfo, iField, + nullptr, nullptr); + } } - if (pszError != nullptr) + if (pszError) { m_poSummaryFeature.reset(); - m_poSrcLayer->GetLayerDefn()->SetGeometryIgnored( - bSaveIsGeomIgnored); - CPLError(CE_Failure, CPLE_AppDefined, "%s", pszError); return false; } } } - m_poSrcLayer->GetLayerDefn()->SetGeometryIgnored(bSaveIsGeomIgnored); - /* -------------------------------------------------------------------- */ /* Clear away the filters we have installed till a next run through*/ /* the features. */ @@ -968,7 +1018,7 @@ bool OGRGenSQLResultsLayer::PrepareSummary() { for (int iField = 0; iField < psSelectInfo->result_columns(); iField++) { - swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; + const swq_col_def *psColDef = &psSelectInfo->column_defs[iField]; if (!psSelectInfo->column_summary.empty()) { const swq_summary &oSummary = diff --git a/ogr/swq.cpp b/ogr/swq.cpp index 5132f9de128d..123cc43198b2 100644 --- a/ogr/swq.cpp +++ b/ogr/swq.cpp @@ -323,7 +323,7 @@ int swqlex(YYSTYPE *ppNode, swq_parse_context *context) /************************************************************************/ const char *swq_select_summarize(swq_select *select_info, int dest_column, - const char *value) + const char *pszValue, const double *pdfValue) { /* -------------------------------------------------------------------- */ @@ -336,7 +336,7 @@ const char *swq_select_summarize(swq_select *select_info, int dest_column, dest_column >= static_cast(select_info->column_defs.size())) return "dest_column out of range in swq_select_summarize()."; - swq_col_def *def = &select_info->column_defs[dest_column]; + const swq_col_def *def = &select_info->column_defs[dest_column]; if (def->col_func == SWQCF_NONE && !def->distinct_flag) return nullptr; @@ -403,18 +403,18 @@ const char *swq_select_summarize(swq_select *select_info, int dest_column, if (def->distinct_flag) { - if (value == nullptr) - value = SZ_OGR_NULL; + if (pszValue == nullptr) + pszValue = SZ_OGR_NULL; try { - if (summary.oSetDistinctValues.find(value) == + if (summary.oSetDistinctValues.find(pszValue) == summary.oSetDistinctValues.end()) { - summary.oSetDistinctValues.insert(value); + summary.oSetDistinctValues.insert(pszValue); if (select_info->order_specs == 0) { // If not sorted, keep values in their original order - summary.oVectorDistinctValues.emplace_back(value); + summary.oVectorDistinctValues.emplace_back(pszValue); } summary.count++; } @@ -434,59 +434,78 @@ const char *swq_select_summarize(swq_select *select_info, int dest_column, switch (def->col_func) { case SWQCF_MIN: - if (value != nullptr && value[0] != '\0') + if (pdfValue) { - if (def->field_type == SWQ_DATE || - def->field_type == SWQ_TIME || - def->field_type == SWQ_TIMESTAMP || - def->field_type == SWQ_STRING) + if (*pdfValue < summary.min) + summary.min = *pdfValue; + summary.count++; + } + else if (pszValue && pszValue[0] != '\0') + { + if (summary.count == 0 || strcmp(pszValue, summary.osMin) < 0) { - if (summary.count == 0 || strcmp(value, summary.osMin) < 0) - { - summary.osMin = value; - } + summary.osMin = pszValue; } - else + summary.count++; + } + break; + case SWQCF_MAX: + if (pdfValue) + { + if (*pdfValue > summary.max) + summary.max = *pdfValue; + summary.count++; + } + else if (pszValue && pszValue[0] != '\0') + { + if (summary.count == 0 || strcmp(pszValue, summary.osMax) > 0) { - double df_val = CPLAtof(value); - if (df_val < summary.min) - summary.min = df_val; + summary.osMax = pszValue; } summary.count++; } break; - case SWQCF_MAX: - if (value != nullptr && value[0] != '\0') + case SWQCF_AVG: + case SWQCF_SUM: + if (pdfValue) { - if (def->field_type == SWQ_DATE || - def->field_type == SWQ_TIME || - def->field_type == SWQ_TIMESTAMP || - def->field_type == SWQ_STRING) + summary.count++; + + // Cf KahanBabushkaNeumaierSum of + // https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Further_enhancements + // We set a number of temporary variables as volatile, to + // prevent potential undesired compiler optimizations. + + const double dfNewVal = *pdfValue; + const volatile double new_sum_acc = summary.sum_acc + dfNewVal; + if (summary.sum_only_finite_terms && std::isfinite(dfNewVal)) { - if (summary.count == 0 || strcmp(value, summary.osMax) > 0) + if (std::fabs(summary.sum_acc) >= std::fabs(dfNewVal)) + { + const volatile double diff = + (summary.sum_acc - new_sum_acc); + summary.sum_correction += (diff + dfNewVal); + } + else { - summary.osMax = value; + const volatile double diff = (dfNewVal - new_sum_acc); + summary.sum_correction += (diff + summary.sum_acc); } } else { - double df_val = CPLAtof(value); - if (df_val > summary.max) - summary.max = df_val; + summary.sum_only_finite_terms = false; } - summary.count++; + summary.sum_acc = new_sum_acc; } - break; - case SWQCF_AVG: - case SWQCF_SUM: - if (value != nullptr && value[0] != '\0') + else if (pszValue && pszValue[0] != '\0') { if (def->field_type == SWQ_DATE || def->field_type == SWQ_TIME || def->field_type == SWQ_TIMESTAMP) { OGRField sField; - if (OGRParseDate(value, &sField, 0)) + if (OGRParseDate(pszValue, &sField, 0)) { struct tm brokendowntime; brokendowntime.tm_year = sField.Date.Year - 1900; @@ -504,43 +523,14 @@ const char *swq_select_summarize(swq_select *select_info, int dest_column, } else { - summary.count++; - - // Cf KahanBabushkaNeumaierSum of - // https://en.wikipedia.org/wiki/Kahan_summation_algorithm#Further_enhancements - // We set a number of temporary variables as volatile, to - // prevent potential undesired compiler optimizations. - - const double dfNewVal = CPLAtof(value); - const volatile double new_sum_acc = - summary.sum_acc + dfNewVal; - if (summary.sum_only_finite_terms && - std::isfinite(dfNewVal)) - { - if (std::fabs(summary.sum_acc) >= std::fabs(dfNewVal)) - { - const volatile double diff = - (summary.sum_acc - new_sum_acc); - summary.sum_correction += (diff + dfNewVal); - } - else - { - const volatile double diff = - (dfNewVal - new_sum_acc); - summary.sum_correction += (diff + summary.sum_acc); - } - } - else - { - summary.sum_only_finite_terms = false; - } - summary.sum_acc = new_sum_acc; + return "swq_select_summarize() - AVG()/SUM() called on " + "unexpected field type"; } } break; case SWQCF_COUNT: - if (value != nullptr) + if (pdfValue || pszValue) summary.count++; break; From 48daa9f068fff1f42a3cfc4f632d7f86048740fb Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 23 Jun 2024 17:43:39 +0200 Subject: [PATCH 0233/1119] OGRSQL: add STDDEV_POP() and STDDEV_SAMP() aggregate functions --- autotest/cpp/test_ogr_swq.cpp | 3 +- autotest/ogr/ogr_sql_rfc28.py | 19 +-- autotest/ogr/ogr_sql_test.py | 12 +- doc/source/user/ogr_sql_dialect.rst | 15 ++- ogr/ogr_swq.h | 14 +++ ogr/ogrsf_frmts/generic/ogr_gensql.cpp | 165 ++++++++++++++++++------- ogr/swq.cpp | 53 +++++++- ogr/swq_op_registrar.cpp | 2 + ogr/swq_select.cpp | 80 +++++++----- 9 files changed, 265 insertions(+), 98 deletions(-) diff --git a/autotest/cpp/test_ogr_swq.cpp b/autotest/cpp/test_ogr_swq.cpp index 520a5846ba86..73074b9e13c6 100644 --- a/autotest/cpp/test_ogr_swq.cpp +++ b/autotest/cpp/test_ogr_swq.cpp @@ -216,7 +216,8 @@ TEST_F(test_ogr_swq, select_unparse) swq_select select; const char *pszSQL = "SELECT DISTINCT a, \"a b\" AS renamed, AVG(x.a) AS avg, MIN(a), " - "MAX(\"a b\"), SUM(a), AVG(a), COUNT(a), COUNT(DISTINCT a) " + "MAX(\"a b\"), SUM(a), AVG(a), COUNT(a), COUNT(DISTINCT a), " + "STDDEV_POP(a), STDDEV_SAMP(a) " "FROM 'foo'.\"FOO BAR\" AS x " "JOIN 'bar'.BAR AS y ON FOO.x = BAR.y " "WHERE 1 ORDER BY a, \"a b\" DESC " diff --git a/autotest/ogr/ogr_sql_rfc28.py b/autotest/ogr/ogr_sql_rfc28.py index d5aa09bdac81..6b54af821e2b 100755 --- a/autotest/ogr/ogr_sql_rfc28.py +++ b/autotest/ogr/ogr_sql_rfc28.py @@ -881,7 +881,7 @@ def test_ogr_rfc28_39(data_ds): ############################################################################### -# Test MIN(), MAX() and AVG() on a date (#5333) +# Test MIN(), MAX(), AVG(), STDDEV_POP(), STDDEV_SAMP() on a date (#5333) def test_ogr_rfc28_40(): @@ -896,15 +896,16 @@ def test_ogr_rfc28_40(): feat.SetField(0, "2013/01/01 00:00:00") lyr.CreateFeature(feat) - with ds.ExecuteSQL("SELECT MIN(DATE), MAX(DATE), AVG(DATE) from test") as lyr: + with ds.ExecuteSQL( + "SELECT MIN(DATE), MAX(DATE), AVG(DATE), STDDEV_POP(DATE), STDDEV_SAMP(DATE) from test" + ) as sql_lyr: - ogrtest.check_features_against_list(lyr, "MIN_DATE", ["2013/01/01 00:00:00"]) - lyr.ResetReading() - ogrtest.check_features_against_list(lyr, "MAX_DATE", ["2013/12/31 23:59:59"]) - lyr.ResetReading() - ogrtest.check_features_against_list( - lyr, "AVG_DATE", ["2013/07/02 11:59:59.500"] - ) + f = sql_lyr.GetNextFeature() + assert f["MIN_DATE"] == "2013/01/01 00:00:00" + assert f["MAX_DATE"] == "2013/12/31 23:59:59" + assert f["AVG_DATE"] == "2013/07/02 11:59:59.500" + assert f["STDDEV_POP_DATE"] == pytest.approx(15767999.5, rel=1e-15) + assert f["STDDEV_SAMP_DATE"] == pytest.approx(22299318.744392183, rel=1e-15) ############################################################################### diff --git a/autotest/ogr/ogr_sql_test.py b/autotest/ogr/ogr_sql_test.py index 4d68b46c2409..cba0f8a0ad33 100755 --- a/autotest/ogr/ogr_sql_test.py +++ b/autotest/ogr/ogr_sql_test.py @@ -255,12 +255,14 @@ def test_ogr_sql_4(data_ds): def test_ogr_sql_5(data_ds): with data_ds.ExecuteSQL( - "select max(eas_id), min(eas_id), avg(eas_id), sum(eas_id), count(eas_id) from idlink" + "select max(eas_id), min(eas_id), avg(eas_id), STDDEV_POP(eas_id), STDDEV_SAMP(eas_id), sum(eas_id), count(eas_id) from idlink" ) as sql_lyr: feat = sql_lyr.GetNextFeature() assert feat["max_eas_id"] == 179 assert feat["min_eas_id"] == 158 assert feat["avg_eas_id"] == pytest.approx(168.142857142857, abs=1e-12) + assert feat["STDDEV_POP_eas_id"] == pytest.approx(5.9384599116647205, rel=1e-15) + assert feat["STDDEV_SAMP_eas_id"] == pytest.approx(6.414269805898183, rel=1e-15) assert feat["count_eas_id"] == 7 assert feat["sum_eas_id"] == 1177 @@ -789,10 +791,15 @@ def ds_for_invalid_statements(): "SELECT MAX(foo) FROM my_layer", "SELECT SUM(foo) FROM my_layer", "SELECT AVG(foo) FROM my_layer", + "SELECT STDDEV_POP(foo) FROM my_layer", + "SELECT STDDEV_SAMP(foo) FROM my_layer", "SELECT SUM(strfield) FROM my_layer", "SELECT AVG(strfield) FROM my_layer", "SELECT AVG(intfield, intfield) FROM my_layer", + "SELECT STDDEV_POP(strfield) FROM my_layer", + "SELECT STDDEV_SAMP(strfield) FROM my_layer", "SELECT * FROM my_layer WHERE AVG(intfield) = 1", + "SELECT * FROM my_layer WHERE STDDEV_POP(intfield) = 1", "SELECT * FROM 'foo' foo", "SELECT * FROM my_layer WHERE strfield =", "SELECT * FROM my_layer WHERE strfield = foo", @@ -1101,10 +1108,11 @@ def test_ogr_sql_count_and_null(): assert feat.GetFieldAsInteger(2) == 4, fieldname with ds.ExecuteSQL( - "select avg(intfield) from layer where intfield is null" + "select avg(intfield), STDDEV_POP(intfield) from layer where intfield is null" ) as sql_lyr: feat = sql_lyr.GetNextFeature() assert feat.IsFieldSetAndNotNull(0) == 0 + assert feat.IsFieldSetAndNotNull(1) == 0 # Fix crash when first values is null (#4509) with ds.ExecuteSQL("select distinct strfield_first_null from layer") as sql_lyr: diff --git a/doc/source/user/ogr_sql_dialect.rst b/doc/source/user/ogr_sql_dialect.rst index 207d934bc869..605441add76b 100644 --- a/doc/source/user/ogr_sql_dialect.rst +++ b/doc/source/user/ogr_sql_dialect.rst @@ -107,16 +107,23 @@ memory may be used for datasets with a large number of distinct values. There are also several summarization operators that may be applied to columns. When a summarization operator is applied to any field, then all fields must -have summarization operators applied. The summarization operators are -COUNT (a count of instances), AVG (numerical average), SUM (numerical sum), -MIN (lexical or numerical minimum), and MAX (lexical or numerical maximum). +have summarization operators applied. The summarization operators are: + +- COUNT: count of instances +- AVG: numerical average: +- SUM: numerical sum +- MIN: lexical or numerical minimum +- MAX: lexical or numerical maximum +- STDDEV_POP: (GDAL >= 3.10) numerical population standard deviation. Applied on Date/DateTime/Time fields, this returns a value in seconds. +- STDDEV_SAMP: (GDAL >= 3.10) numerical `sample standard deviation `__. Applied on Date/DateTime/Time fields, this returns a value in seconds. + This example produces a variety of summarization information on parcel property values: .. code-block:: SELECT MIN(prop_value), MAX(prop_value), AVG(prop_value), SUM(prop_value), - COUNT(prop_value) FROM polylayer WHERE prov_name = 'Ontario' + COUNT(prop_value), STDDEV_POP(prop_value) FROM polylayer WHERE prov_name = 'Ontario' It is also possible to apply the COUNT() operator to a DISTINCT SELECT to get a count of distinct values, for instance: diff --git a/ogr/ogr_swq.h b/ogr/ogr_swq.h index a61cd0cc4c18..71f4f45e6b6b 100644 --- a/ogr/ogr_swq.h +++ b/ogr/ogr_swq.h @@ -63,11 +63,17 @@ typedef enum SWQ_CONCAT, SWQ_SUBSTR, SWQ_HSTORE_GET_VALUE, + SWQ_AVG, + SWQ_AGGREGATE_BEGIN = SWQ_AVG, SWQ_MIN, SWQ_MAX, SWQ_COUNT, SWQ_SUM, + SWQ_STDDEV_POP, + SWQ_STDDEV_SAMP, + SWQ_AGGREGATE_END = SWQ_STDDEV_SAMP, + SWQ_CAST, SWQ_CUSTOM_FUNC, /* only if parsing done in bAcceptCustomFuncs mode */ SWQ_ARGUMENT_LIST /* temporary value only set during parsing and replaced by @@ -318,6 +324,8 @@ typedef enum SWQCF_MAX = SWQ_MAX, SWQCF_COUNT = SWQ_COUNT, SWQCF_SUM = SWQ_SUM, + SWQCF_STDDEV_POP = SWQ_STDDEV_POP, + SWQCF_STDDEV_SAMP = SWQ_STDDEV_SAMP, SWQCF_CUSTOM } swq_col_func; @@ -374,6 +382,12 @@ class CPL_UNSTABLE_API swq_summary double sum_correction = 0.0; double min = 0.0; double max = 0.0; + + // Welford's online algorithm for variance: + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm + double mean_for_variance = 0.0; + double sq_dist_from_mean_acc = 0.0; // "M2" + CPLString osMin{}; CPLString osMax{}; }; diff --git a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp index 3c2838a6ccab..3b2e7af7b269 100644 --- a/ogr/ogrsf_frmts/generic/ogr_gensql.cpp +++ b/ogr/ogrsf_frmts/generic/ogr_gensql.cpp @@ -247,10 +247,15 @@ OGRGenSQLResultsLayer::OGRGenSQLResultsLayer( oFDefn.SetType(OFTInteger64); else if (poSrcFDefn != nullptr) { - if (psColDef->col_func != SWQCF_AVG || - psColDef->field_type == SWQ_DATE || - psColDef->field_type == SWQ_TIME || - psColDef->field_type == SWQ_TIMESTAMP) + if (psColDef->col_func == SWQCF_STDDEV_POP || + psColDef->col_func == SWQCF_STDDEV_SAMP) + { + oFDefn.SetType(OFTReal); + } + else if (psColDef->col_func != SWQCF_AVG || + psColDef->field_type == SWQ_DATE || + psColDef->field_type == SWQ_TIME || + psColDef->field_type == SWQ_TIMESTAMP) { oFDefn.SetType(poSrcFDefn->GetType()); if (psColDef->col_func == SWQCF_NONE || @@ -261,8 +266,13 @@ OGRGenSQLResultsLayer::OGRGenSQLResultsLayer( } } else + { oFDefn.SetType(OFTReal); + } + if (psColDef->col_func != SWQCF_AVG && + psColDef->col_func != SWQCF_STDDEV_POP && + psColDef->col_func != SWQCF_STDDEV_SAMP && psColDef->col_func != SWQCF_SUM) { oFDefn.SetWidth(poSrcFDefn->GetWidth()); @@ -1048,55 +1058,116 @@ bool OGRGenSQLResultsLayer::PrepareSummary() const swq_summary &oSummary = psSelectInfo->column_summary[iField]; - if (psColDef->col_func == SWQCF_AVG && oSummary.count > 0) + switch (psColDef->col_func) { - const double dfAvg = oSummary.sum() / oSummary.count; - if (psColDef->field_type == SWQ_DATE || - psColDef->field_type == SWQ_TIME || - psColDef->field_type == SWQ_TIMESTAMP) + case SWQCF_NONE: + case SWQCF_CUSTOM: + break; + + case SWQCF_AVG: { - struct tm brokendowntime; - CPLUnixTimeToYMDHMS(static_cast(dfAvg), - &brokendowntime); - m_poSummaryFeature->SetField( - iField, brokendowntime.tm_year + 1900, - brokendowntime.tm_mon + 1, brokendowntime.tm_mday, - brokendowntime.tm_hour, brokendowntime.tm_min, - static_cast(brokendowntime.tm_sec + - fmod(dfAvg, 1)), - 0); + if (oSummary.count > 0) + { + const double dfAvg = + oSummary.sum() / oSummary.count; + if (psColDef->field_type == SWQ_DATE || + psColDef->field_type == SWQ_TIME || + psColDef->field_type == SWQ_TIMESTAMP) + { + struct tm brokendowntime; + CPLUnixTimeToYMDHMS(static_cast(dfAvg), + &brokendowntime); + m_poSummaryFeature->SetField( + iField, brokendowntime.tm_year + 1900, + brokendowntime.tm_mon + 1, + brokendowntime.tm_mday, + brokendowntime.tm_hour, + brokendowntime.tm_min, + static_cast(brokendowntime.tm_sec + + fmod(dfAvg, 1)), + 0); + } + else + { + m_poSummaryFeature->SetField(iField, dfAvg); + } + } + break; } - else + + case SWQCF_MIN: { - m_poSummaryFeature->SetField(iField, dfAvg); + if (oSummary.count > 0) + { + if (psColDef->field_type == SWQ_DATE || + psColDef->field_type == SWQ_TIME || + psColDef->field_type == SWQ_TIMESTAMP || + psColDef->field_type == SWQ_STRING) + m_poSummaryFeature->SetField( + iField, oSummary.osMin.c_str()); + else + m_poSummaryFeature->SetField(iField, + oSummary.min); + } + break; + } + + case SWQCF_MAX: + { + if (oSummary.count > 0) + { + if (psColDef->field_type == SWQ_DATE || + psColDef->field_type == SWQ_TIME || + psColDef->field_type == SWQ_TIMESTAMP || + psColDef->field_type == SWQ_STRING) + m_poSummaryFeature->SetField( + iField, oSummary.osMax.c_str()); + else + m_poSummaryFeature->SetField(iField, + oSummary.max); + } + break; + } + + case SWQCF_COUNT: + { + m_poSummaryFeature->SetField(iField, oSummary.count); + break; + } + + case SWQCF_SUM: + { + if (oSummary.count > 0) + m_poSummaryFeature->SetField(iField, + oSummary.sum()); + break; + } + + case SWQCF_STDDEV_POP: + { + if (oSummary.count > 0) + { + const double dfVariance = + oSummary.sq_dist_from_mean_acc / oSummary.count; + m_poSummaryFeature->SetField(iField, + sqrt(dfVariance)); + } + break; + } + + case SWQCF_STDDEV_SAMP: + { + if (oSummary.count > 1) + { + const double dfSampleVariance = + oSummary.sq_dist_from_mean_acc / + (oSummary.count - 1); + m_poSummaryFeature->SetField( + iField, sqrt(dfSampleVariance)); + } + break; } } - else if (psColDef->col_func == SWQCF_MIN && oSummary.count > 0) - { - if (psColDef->field_type == SWQ_DATE || - psColDef->field_type == SWQ_TIME || - psColDef->field_type == SWQ_TIMESTAMP || - psColDef->field_type == SWQ_STRING) - m_poSummaryFeature->SetField(iField, - oSummary.osMin.c_str()); - else - m_poSummaryFeature->SetField(iField, oSummary.min); - } - else if (psColDef->col_func == SWQCF_MAX && oSummary.count > 0) - { - if (psColDef->field_type == SWQ_DATE || - psColDef->field_type == SWQ_TIME || - psColDef->field_type == SWQ_TIMESTAMP || - psColDef->field_type == SWQ_STRING) - m_poSummaryFeature->SetField(iField, - oSummary.osMax.c_str()); - else - m_poSummaryFeature->SetField(iField, oSummary.max); - } - else if (psColDef->col_func == SWQCF_COUNT) - m_poSummaryFeature->SetField(iField, oSummary.count); - else if (psColDef->col_func == SWQCF_SUM && oSummary.count > 0) - m_poSummaryFeature->SetField(iField, oSummary.sum()); } else if (psColDef->col_func == SWQCF_COUNT) m_poSummaryFeature->SetField(iField, 0); diff --git a/ogr/swq.cpp b/ogr/swq.cpp index 123cc43198b2..38d983d42489 100644 --- a/ogr/swq.cpp +++ b/ogr/swq.cpp @@ -534,14 +534,61 @@ const char *swq_select_summarize(swq_select *select_info, int dest_column, summary.count++; break; + case SWQCF_STDDEV_POP: + case SWQCF_STDDEV_SAMP: + { + const auto UpdateVariance = [&summary](double dfValue) + { + // Welford's online algorithm for variance: + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm + summary.count++; + const double dfDelta = dfValue - summary.mean_for_variance; + summary.mean_for_variance += dfDelta / summary.count; + const double dfDelta2 = dfValue - summary.mean_for_variance; + summary.sq_dist_from_mean_acc += dfDelta * dfDelta2; + }; + + if (pdfValue) + { + UpdateVariance(*pdfValue); + } + else if (pszValue && pszValue[0] != '\0') + { + if (def->field_type == SWQ_DATE || + def->field_type == SWQ_TIME || + def->field_type == SWQ_TIMESTAMP) + { + OGRField sField; + if (OGRParseDate(pszValue, &sField, 0)) + { + struct tm brokendowntime; + brokendowntime.tm_year = sField.Date.Year - 1900; + brokendowntime.tm_mon = sField.Date.Month - 1; + brokendowntime.tm_mday = sField.Date.Day; + brokendowntime.tm_hour = sField.Date.Hour; + brokendowntime.tm_min = sField.Date.Minute; + brokendowntime.tm_sec = + static_cast(sField.Date.Second); + + UpdateVariance(static_cast( + CPLYMDHMSToUnixTime(&brokendowntime))); + } + } + else + { + return "swq_select_summarize() - STDDEV() called on " + "unexpected field type"; + } + } + + break; + } + case SWQCF_NONE: break; case SWQCF_CUSTOM: return "swq_select_summarize() called on custom field function."; - - default: - return "swq_select_summarize() - unexpected col_func"; } return nullptr; diff --git a/ogr/swq_op_registrar.cpp b/ogr/swq_op_registrar.cpp index 8b82a11da0f9..6b63d5b8b31b 100644 --- a/ogr/swq_op_registrar.cpp +++ b/ogr/swq_op_registrar.cpp @@ -71,6 +71,8 @@ static const swq_operation swq_apsOperations[] = { {"MAX", SWQ_MAX, SWQGeneralEvaluator, SWQColumnFuncChecker}, {"COUNT", SWQ_COUNT, SWQGeneralEvaluator, SWQColumnFuncChecker}, {"SUM", SWQ_SUM, SWQGeneralEvaluator, SWQColumnFuncChecker}, + {"STDDEV_POP", SWQ_STDDEV_POP, SWQGeneralEvaluator, SWQColumnFuncChecker}, + {"STDDEV_SAMP", SWQ_STDDEV_SAMP, SWQGeneralEvaluator, SWQColumnFuncChecker}, {"CAST", SWQ_CAST, SWQCastEvaluator, SWQCastChecker}}; diff --git a/ogr/swq_select.cpp b/ogr/swq_select.cpp index db91efe48552..9e9d84cb8727 100644 --- a/ogr/swq_select.cpp +++ b/ogr/swq_select.cpp @@ -203,16 +203,34 @@ char *swq_select::Unparse() } else { - if (def->col_func == SWQCF_AVG) - osSelect += "AVG("; - else if (def->col_func == SWQCF_MIN) - osSelect += "MIN("; - else if (def->col_func == SWQCF_MAX) - osSelect += "MAX("; - else if (def->col_func == SWQCF_COUNT) - osSelect += "COUNT("; - else if (def->col_func == SWQCF_SUM) - osSelect += "SUM("; + switch (def->col_func) + { + case SWQCF_NONE: + break; + case SWQCF_AVG: + osSelect += "AVG("; + break; + case SWQCF_MIN: + osSelect += "MIN("; + break; + case SWQCF_MAX: + osSelect += "MAX("; + break; + case SWQCF_COUNT: + osSelect += "COUNT("; + break; + case SWQCF_SUM: + osSelect += "SUM("; + break; + case SWQCF_STDDEV_POP: + osSelect += "STDDEV_POP("; + break; + case SWQCF_STDDEV_SAMP: + osSelect += "STDDEV_SAMP("; + break; + case SWQCF_CUSTOM: + break; + } if (def->distinct_flag && def->col_func == SWQCF_COUNT) osSelect += "DISTINCT "; @@ -368,8 +386,8 @@ int swq_select::PushField(swq_expr_node *poExpr, const char *pszAlias, } else if (poExpr->eNodeType == SNT_OPERATION && (poExpr->nOperation == SWQ_CAST || - (poExpr->nOperation >= SWQ_AVG && - poExpr->nOperation <= SWQ_SUM)) && + (poExpr->nOperation >= SWQ_AGGREGATE_BEGIN && + poExpr->nOperation <= SWQ_AGGREGATE_END)) && poExpr->nSubExprCount >= 1 && poExpr->papoSubExpr[0]->eNodeType == SNT_COLUMN) { @@ -573,8 +591,8 @@ int swq_select::PushField(swq_expr_node *poExpr, const char *pszAlias, /* Do we have a special column function in play? */ /* -------------------------------------------------------------------- */ if (poExpr->eNodeType == SNT_OPERATION && - static_cast(poExpr->nOperation) >= SWQ_AVG && - static_cast(poExpr->nOperation) <= SWQ_SUM) + static_cast(poExpr->nOperation) >= SWQ_AGGREGATE_BEGIN && + static_cast(poExpr->nOperation) <= SWQ_AGGREGATE_END) { if (poExpr->nSubExprCount != 1) { @@ -1015,11 +1033,11 @@ CPLErr swq_select::parse(swq_field_list *field_list, } // Identify column function if present. - if (((def->col_func == SWQCF_MIN || def->col_func == SWQCF_MAX || - def->col_func == SWQCF_AVG || def->col_func == SWQCF_SUM) && - def->field_type == SWQ_GEOMETRY) || - ((def->col_func == SWQCF_AVG || def->col_func == SWQCF_SUM) && - def->field_type == SWQ_STRING)) + if (def->col_func != SWQCF_NONE && def->col_func != SWQCF_CUSTOM && + def->col_func != SWQCF_COUNT && + (def->field_type == SWQ_GEOMETRY || + (def->field_type == SWQ_STRING && def->col_func != SWQCF_MIN && + def->col_func != SWQCF_MAX))) { // Possibly this is already enforced by the checker? const swq_operation *op = swq_op_registrar::GetOperator( @@ -1065,9 +1083,17 @@ CPLErr swq_select::parse(swq_field_list *field_list, } } - if (def->col_func == SWQCF_MIN || def->col_func == SWQCF_MAX || - def->col_func == SWQCF_AVG || def->col_func == SWQCF_SUM || - def->col_func == SWQCF_COUNT) + if (def->col_func == SWQCF_NONE) + { + if (query_mode == SWQM_DISTINCT_LIST) + { + def->distinct_flag = TRUE; + this_indicator = SWQM_DISTINCT_LIST; + } + else + this_indicator = SWQM_RECORDSET; + } + else if (def->col_func != SWQCF_CUSTOM) { this_indicator = SWQM_SUMMARY_RECORD; if (def->col_func == SWQCF_COUNT && def->distinct_flag && @@ -1078,16 +1104,6 @@ CPLErr swq_select::parse(swq_field_list *field_list, return CE_Failure; } } - else if (def->col_func == SWQCF_NONE) - { - if (query_mode == SWQM_DISTINCT_LIST) - { - def->distinct_flag = TRUE; - this_indicator = SWQM_DISTINCT_LIST; - } - else - this_indicator = SWQM_RECORDSET; - } if (this_indicator != query_mode && this_indicator != -1 && query_mode != 0) From b012d9a3a885414d3a017d1fa8f22ad5e3518bbd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 23 Jun 2024 17:45:22 +0200 Subject: [PATCH 0234/1119] SQLite/GPKG: add STDDEV_POP() and STDDEV_SAMP() aggregate functions --- autotest/ogr/ogr_sqlite.py | 12 ++++ doc/source/user/sql_sqlite_dialect.rst | 9 +++ .../sqlite/ogrsqlitesqlfunctionscommon.cpp | 71 +++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/autotest/ogr/ogr_sqlite.py b/autotest/ogr/ogr_sqlite.py index a85b64511b75..f395e9592fa5 100755 --- a/autotest/ogr/ogr_sqlite.py +++ b/autotest/ogr/ogr_sqlite.py @@ -4095,3 +4095,15 @@ def test_ogr_sql_ST_Area_on_ellipsoid(tmp_vsimem, require_spatialite): with ds.ExecuteSQL("SELECT ST_Area(null, 1) FROM my_layer") as sql_lyr: f = sql_lyr.GetNextFeature() assert f[0] is None + + +def test_ogr_sqlite_stddev(): + """Test STDDEV_POP() and STDDEV_SAMP""" + + ds = ogr.Open(":memory:", update=1) + ds.ExecuteSQL("CREATE TABLE test(v REAL)") + ds.ExecuteSQL("INSERT INTO test VALUES (4),(NULL),('invalid'),(5)") + with ds.ExecuteSQL("SELECT STDDEV_POP(v), STDDEV_SAMP(v) FROM test") as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f.GetField(0) == pytest.approx(0.5, rel=1e-15) + assert f.GetField(1) == pytest.approx(0.5**0.5, rel=1e-15) diff --git a/doc/source/user/sql_sqlite_dialect.rst b/doc/source/user/sql_sqlite_dialect.rst index 20b18076b250..042cc0c5425a 100644 --- a/doc/source/user/sql_sqlite_dialect.rst +++ b/doc/source/user/sql_sqlite_dialect.rst @@ -201,6 +201,15 @@ For example we can select the annotation features as: SELECT * FROM nation WHERE OGR_STYLE LIKE 'LABEL%' +Statistics functions +++++++++++++++++++++ + +In addition to standard COUNT(), SUM(), AVG(), MIN(), MAX(), the following +aggregate functions are available: + +- STDDEV_POP: (GDAL >= 3.10) numerical population standard deviation. +- STDDEV_SAMP: (GDAL >= 3.10) numerical `sample standard deviation `__ + Spatialite SQL functions ++++++++++++++++++++++++ diff --git a/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctionscommon.cpp b/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctionscommon.cpp index fe5f0d515669..fd886800542c 100644 --- a/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctionscommon.cpp +++ b/ogr/ogrsf_frmts/sqlite/ogrsqlitesqlfunctionscommon.cpp @@ -336,6 +336,69 @@ static void OGRSQLITE_LIKE(sqlite3_context *pContext, int argc, insensitive, bUTF8Strings)); } +/************************************************************************/ +/* OGRSQLITE_STDDEV_Step() */ +/************************************************************************/ + +// Welford's online algorithm for variance: +// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm +struct OGRSQLITE_STDDEV_Context +{ + int64_t nValues; + double dfMean; + double dfM2; // Accumulator for squared distance from the mean +}; + +static void OGRSQLITE_STDDEV_Step(sqlite3_context *pContext, int /* argc*/, + sqlite3_value **argv) +{ + auto pAggCtxt = + static_cast(sqlite3_aggregate_context( + pContext, static_cast(sizeof(OGRSQLITE_STDDEV_Context)))); + const auto eType = sqlite3_value_type(argv[0]); + if (eType != SQLITE_INTEGER && eType != SQLITE_FLOAT) + return; + + const double dfValue = sqlite3_value_double(argv[0]); + pAggCtxt->nValues++; + const double dfDelta = dfValue - pAggCtxt->dfMean; + pAggCtxt->dfMean += dfDelta / pAggCtxt->nValues; + const double dfDelta2 = dfValue - pAggCtxt->dfMean; + pAggCtxt->dfM2 += dfDelta * dfDelta2; +} + +/************************************************************************/ +/* OGRSQLITE_STDDEV_POP_Finalize() */ +/************************************************************************/ + +static void OGRSQLITE_STDDEV_POP_Finalize(sqlite3_context *pContext) +{ + auto pAggCtxt = + static_cast(sqlite3_aggregate_context( + pContext, static_cast(sizeof(OGRSQLITE_STDDEV_Context)))); + if (pAggCtxt->nValues > 0) + { + sqlite3_result_double(pContext, + sqrt(pAggCtxt->dfM2 / pAggCtxt->nValues)); + } +} + +/************************************************************************/ +/* OGRSQLITE_STDDEV_SAMP_Finalize() */ +/************************************************************************/ + +static void OGRSQLITE_STDDEV_SAMP_Finalize(sqlite3_context *pContext) +{ + auto pAggCtxt = + static_cast(sqlite3_aggregate_context( + pContext, static_cast(sizeof(OGRSQLITE_STDDEV_Context)))); + if (pAggCtxt->nValues > 1) + { + sqlite3_result_double(pContext, + sqrt(pAggCtxt->dfM2 / (pAggCtxt->nValues - 1))); + } +} + /************************************************************************/ /* OGRSQLiteRegisterSQLFunctionsCommon() */ /************************************************************************/ @@ -365,6 +428,14 @@ static OGRSQLiteExtensionData *OGRSQLiteRegisterSQLFunctionsCommon(sqlite3 *hDB) OGRSQLITE_LIKE, nullptr, nullptr); } + sqlite3_create_function(hDB, "STDDEV_POP", 1, UTF8_INNOCUOUS, nullptr, + nullptr, OGRSQLITE_STDDEV_Step, + OGRSQLITE_STDDEV_POP_Finalize); + + sqlite3_create_function(hDB, "STDDEV_SAMP", 1, UTF8_INNOCUOUS, nullptr, + nullptr, OGRSQLITE_STDDEV_Step, + OGRSQLITE_STDDEV_SAMP_Finalize); + pData->SetRegExpCache(OGRSQLiteRegisterRegExpFunction(hDB)); return pData; From 8a11460049da10ea83b8c59e568e8e5bf36eaeae Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 23 Jun 2024 20:42:49 +0200 Subject: [PATCH 0235/1119] [Lint] OSM: more usage of std::vector, std::unique_ptr, and C++ cast operator --- ogr/ogrsf_frmts/osm/CMakeLists.txt | 3 +- ogr/ogrsf_frmts/osm/gpb.h | 18 +- ogr/ogrsf_frmts/osm/ogr_osm.h | 125 ++--- ogr/ogrsf_frmts/osm/ogrosmdatasource.cpp | 631 +++++++++++------------ ogr/ogrsf_frmts/osm/ogrosmdriver.cpp | 3 +- ogr/ogrsf_frmts/osm/ogrosmlayer.cpp | 136 +++-- ogr/ogrsf_frmts/osm/osm_parser.cpp | 162 +++--- 7 files changed, 542 insertions(+), 536 deletions(-) diff --git a/ogr/ogrsf_frmts/osm/CMakeLists.txt b/ogr/ogrsf_frmts/osm/CMakeLists.txt index fe87d02d5444..7b78fbc2309c 100644 --- a/ogr/ogrsf_frmts/osm/CMakeLists.txt +++ b/ogr/ogrsf_frmts/osm/CMakeLists.txt @@ -2,7 +2,8 @@ add_gdal_driver( TARGET ogr_OSM SOURCES ogrosmdatasource.cpp ogrosmdriver.cpp ogrosmlayer.cpp osm_parser.cpp - BUILTIN) + BUILTIN + STRONG_CXX_WFLAGS) gdal_standard_includes(ogr_OSM) set(GDAL_DATA_FILES diff --git a/ogr/ogrsf_frmts/osm/gpb.h b/ogr/ogrsf_frmts/osm/gpb.h index 763d7e8fa0b3..e49097cdfef8 100644 --- a/ogr/ogrsf_frmts/osm/gpb.h +++ b/ogr/ogrsf_frmts/osm/gpb.h @@ -91,7 +91,7 @@ inline int ReadVarUInt32(const GByte **ppabyData) if (!(nByte & 0x80)) { *ppabyData = pabyData + 1; - return nVal | ((unsigned)nByte << nShift); + return nVal | (static_cast(nByte) << nShift); } nVal |= (nByte & 0x7f) << nShift; pabyData++; @@ -102,7 +102,7 @@ inline int ReadVarUInt32(const GByte **ppabyData) if (!(nByte & 0x80)) { *ppabyData = pabyData + 1; - return nVal | (((unsigned)nByte & 0xf) << nShift); + return nVal | ((static_cast(nByte) & 0xf) << nShift); } *ppabyData = pabyData; return nVal; @@ -120,7 +120,8 @@ inline int ReadVarUInt32(const GByte **ppabyData) #define READ_SIZE(pabyData, pabyDataLimit, nSize) \ { \ READ_VARUINT32(pabyData, pabyDataLimit, nSize); \ - if (CHECK_OOB && nSize > (unsigned int)(pabyDataLimit - pabyData)) \ + if (CHECK_OOB && \ + nSize > static_cast(pabyDataLimit - pabyData)) \ THROW_GPB_EXCEPTION; \ } @@ -140,9 +141,9 @@ inline GUIntBig ReadVarUInt64(const GByte **ppabyData) if (!(nByte & 0x80)) { *ppabyData = pabyData + 1; - return nVal | ((GUIntBig)nByte << nShift); + return nVal | (static_cast(nByte) << nShift); } - nVal |= ((GUIntBig)(nByte & 0x7f)) << nShift; + nVal |= (static_cast(nByte & 0x7f)) << nShift; pabyData++; nShift += 7; if (nShift == 63) @@ -151,7 +152,7 @@ inline GUIntBig ReadVarUInt64(const GByte **ppabyData) if (!(nByte & 0x80)) { *ppabyData = pabyData + 1; - return nVal | (((GUIntBig)nByte & 1) << nShift); + return nVal | ((static_cast(nByte) & 1) << nShift); } *ppabyData = pabyData; return nVal; @@ -169,7 +170,8 @@ inline GUIntBig ReadVarUInt64(const GByte **ppabyData) #define READ_SIZE64(pabyData, pabyDataLimit, nSize) \ { \ READ_VARUINT64(pabyData, pabyDataLimit, nSize); \ - if (CHECK_OOB && nSize > (unsigned int)(pabyDataLimit - pabyData)) \ + if (CHECK_OOB && \ + nSize > static_cast(pabyDataLimit - pabyData)) \ THROW_GPB_EXCEPTION; \ } @@ -314,7 +316,7 @@ inline void SkipVarInt(const GByte **ppabyData) do \ { \ READ_SIZE(pabyData, pabyDataLimit, l_nDataLength); \ - pszTxt = (char *)VSI_MALLOC_VERBOSE(l_nDataLength + 1); \ + pszTxt = static_cast(VSI_MALLOC_VERBOSE(l_nDataLength + 1)); \ if (pszTxt == nullptr) \ THROW_GPB_EXCEPTION; \ memcpy(pszTxt, pabyData, l_nDataLength); \ diff --git a/ogr/ogrsf_frmts/osm/ogr_osm.h b/ogr/ogrsf_frmts/osm/ogr_osm.h index 4ffe52d4b343..c4e29cafda93 100644 --- a/ogr/ogrsf_frmts/osm/ogr_osm.h +++ b/ogr/ogrsf_frmts/osm/ogr_osm.h @@ -46,7 +46,7 @@ #include "ogrsqlitevfs.h" -class ConstCharComp +class OGROSMConstCharComp { public: bool operator()(const char *a, const char *b) const @@ -58,25 +58,28 @@ class ConstCharComp class OGROSMComputedAttribute { public: - CPLString osName; - int nIndex; - OGRFieldType eType; - CPLString osSQL; - sqlite3_stmt *hStmt; - std::vector aosAttrToBind; - std::vector anIndexToBind; - bool bHardcodedZOrder; - - OGROSMComputedAttribute() - : nIndex(-1), eType(OFTString), hStmt(nullptr), bHardcodedZOrder(false) - { - } + CPLString osName{}; + int nIndex = -1; + OGRFieldType eType = OFTString; + CPLString osSQL{}; + sqlite3_stmt *hStmt = nullptr; + std::vector aosAttrToBind{}; + std::vector anIndexToBind{}; + bool bHardcodedZOrder = false; + + OGROSMComputedAttribute() = default; - explicit OGROSMComputedAttribute(const char *pszName) - : osName(pszName), nIndex(-1), eType(OFTString), hStmt(nullptr), - bHardcodedZOrder(false) + explicit OGROSMComputedAttribute(const char *pszName) : osName(pszName) { } + + OGROSMComputedAttribute(OGROSMComputedAttribute &&) = default; + OGROSMComputedAttribute &operator=(OGROSMComputedAttribute &&) = default; + + private: + OGROSMComputedAttribute(const OGROSMComputedAttribute &) = delete; + OGROSMComputedAttribute & + operator=(const OGROSMComputedAttribute &) = delete; }; /************************************************************************/ @@ -93,21 +96,18 @@ class OGROSMLayer final : public OGRLayer int m_nIdxLayer = 0; OGRFeatureDefn *m_poFeatureDefn = nullptr; OGRSpatialReference *m_poSRS = nullptr; - long m_nFeatureCount = 0; std::vector m_apszNames{}; /* Needed to keep a "reference" to the string inserted into oMapFieldNameToIndex */ - std::map m_oMapFieldNameToIndex{}; + std::map m_oMapFieldNameToIndex{}; std::vector m_oComputedAttributes{}; bool m_bResetReadingAllowed = false; - int m_nFeatureArraySize = 0; - int m_nFeatureArrayMaxSize = 0; - int m_nFeatureArrayIndex = 0; - OGRFeature **m_papoFeatures = nullptr; + size_t m_nFeatureArrayIndex = 0; + std::vector> m_apoFeatures{}; bool m_bHasOSMId = false; int m_nIndexOSMId = -1; @@ -128,20 +128,23 @@ class OGROSMLayer final : public OGRLayer bool m_bUserInterested = true; - bool AddToArray(OGRFeature *poFeature, int bCheckFeatureThreshold); + bool AddToArray(std::unique_ptr, bool bCheckFeatureThreshold); int AddInOtherOrAllTags(const char *pszK); char szLaunderedFieldName[256]; const char *GetLaunderedFieldName(const char *pszName); - std::vector apszInsignificantKeys; - std::map aoSetInsignificantKeys; + std::vector apszInsignificantKeys{}; + std::map aoSetInsignificantKeys{}; + + std::vector apszIgnoreKeys{}; + std::map aoSetIgnoreKeys{}; - std::vector apszIgnoreKeys; - std::map aoSetIgnoreKeys; + std::set aoSetWarnKeys{}; - std::set aoSetWarnKeys; + OGROSMLayer(const OGROSMLayer &) = delete; + OGROSMLayer &operator=(const OGROSMLayer &) = delete; public: OGROSMLayer(OGROSMDataSource *m_poDS, int m_nIdxLayer, const char *pszName); @@ -175,9 +178,10 @@ class OGROSMLayer final : public OGRLayer const OGREnvelope *GetSpatialFilterEnvelope(); - int AddFeature(OGRFeature *poFeature, int bAttrFilterAlreadyEvaluated, - int *pbFilteredOut = nullptr, - int bCheckFeatureThreshold = TRUE); + bool AddFeature(std::unique_ptr poFeature, + bool bAttrFilterAlreadyEvaluated, + bool *pbFilteredOut = nullptr, + bool bCheckFeatureThreshold = true); void ForceResetReading(); void AddField(const char *pszName, OGRFieldType eFieldType, @@ -304,15 +308,15 @@ class OGROSMLayer final : public OGRLayer /* OGROSMDataSource */ /************************************************************************/ -typedef struct +struct KeyDesc { - char *pszK; - int nKeyIndex; - int nOccurrences; - std::vector asValues; - std::map - anMapV; /* map that is the reverse of asValues */ -} KeyDesc; + char *pszK = nullptr; + int nKeyIndex = 0; + int nOccurrences = 0; + std::vector apszValues{}; + //! map that is the reverse of apszValues + std::map anMapV{}; +}; typedef struct { @@ -331,7 +335,7 @@ typedef struct union { - int nValueIndex; /* index of KeyDesc.asValues */ + int nValueIndex; /* index of KeyDesc.apszValues */ int nOffsetInpabyNonRedundantValues; /* offset in OGROSMDataSource.pabyNonRedundantValues */ @@ -359,20 +363,20 @@ typedef struct int nLat; } LonLat; -typedef struct +struct WayFeaturePair { - GIntBig nWayID; - GIntBig - *panNodeRefs; /* point to a sub-array of OGROSMDataSource.anReqIds */ - unsigned int nRefs; - unsigned int nTags; - IndexedKVP *pasTags; /* point to a sub-array of + GIntBig nWayID = 0; + /* point to a sub-array of OGROSMDataSource.anReqIds */ + GIntBig *panNodeRefs = nullptr; + unsigned int nRefs = 0; + unsigned int nTags = 0; + IndexedKVP *pasTags = nullptr; /* point to a sub-array of OGROSMDataSource.pasAccumulatedTags */ - OSMInfo sInfo; - OGRFeature *poFeature; - bool bIsArea : 1; - bool bAttrFilterAlreadyEvaluated : 1; -} WayFeaturePair; + OSMInfo sInfo{}; + std::unique_ptr poFeature{}; + bool bIsArea = false; + bool bAttrFilterAlreadyEvaluated = false; +}; #ifdef ENABLE_NODE_LOOKUP_BY_HASHING typedef struct @@ -387,8 +391,7 @@ class OGROSMDataSource final : public OGRDataSource { friend class OGROSMLayer; - int m_nLayers = 0; - OGROSMLayer **m_papoLayers = nullptr; + std::vector> m_apoLayers{}; char *m_pszName = nullptr; OGREnvelope m_sExtent{}; @@ -492,11 +495,10 @@ class OGROSMDataSource final : public OGRDataSource unsigned int MAX_INDEXED_VALUES_PER_KEY = 0; GByte *pabyNonRedundantValues = nullptr; int nNonRedundantValuesLen = 0; - WayFeaturePair *m_pasWayFeaturePairs = nullptr; - int m_nWayFeaturePairs = 0; + std::vector m_asWayFeaturePairs{}; - std::vector m_asKeys{}; - std::map + std::vector m_apsKeys{}; + std::map m_aoMapIndexedKeys{}; /* map that is the reverse of asKeys */ CPLString m_osNodesFilename{}; @@ -574,6 +576,9 @@ class OGROSMDataSource final : public OGRDataSource int iCurLayer, const std::vector &oAttributes); bool IsClosedWayTaggedAsPolygon(unsigned int nTags, const OSMTag *pasTags); + OGROSMDataSource(const OGROSMDataSource &) = delete; + OGROSMDataSource &operator=(const OGROSMDataSource &) = delete; + public: OGROSMDataSource(); virtual ~OGROSMDataSource(); @@ -585,7 +590,7 @@ class OGROSMDataSource final : public OGRDataSource virtual int GetLayerCount() override { - return m_nLayers; + return static_cast(m_apoLayers.size()); } virtual OGRLayer *GetLayer(int) override; diff --git a/ogr/ogrsf_frmts/osm/ogrosmdatasource.cpp b/ogr/ogrsf_frmts/osm/ogrosmdatasource.cpp index 53226246e321..9e7a6aaef4b6 100644 --- a/ogr/ogrsf_frmts/osm/ogrosmdatasource.cpp +++ b/ogr/ogrsf_frmts/osm/ogrosmdatasource.cpp @@ -69,7 +69,7 @@ #include "sqlite3.h" #undef SQLITE_STATIC -#define SQLITE_STATIC ((sqlite3_destructor_type) nullptr) +#define SQLITE_STATIC (static_cast(nullptr)) constexpr int LIMIT_IDS_PER_REQUEST = 200; @@ -159,7 +159,7 @@ constexpr int HASHED_INDEXES_ARRAY_SIZE = 3145739; constexpr int COLLISION_BUCKET_ARRAY_SIZE = (MAX_ACCUMULATED_NODES / 100) * 40; // hash function = identity -#define HASH_ID_FUNC(x) ((GUIntBig)(x)) +#define HASH_ID_FUNC(x) (static_cast(x)) #endif // ENABLE_NODE_LOOKUP_BY_HASHING // #define FAKE_LOOKUP_NODES @@ -174,9 +174,9 @@ static void WriteVarSInt64(GIntBig nSVal, GByte **ppabyData); class DSToBeOpened { public: - GIntBig nPID; - CPLString osDSName; - CPLString osInterestLayers; + GIntBig nPID{}; + CPLString osDSName{}; + CPLString osInterestLayers{}; }; static CPLMutex *hMutex = nullptr; @@ -205,13 +205,14 @@ static CPLString GetInterestLayersForDSName(const CPLString &osDSName) { CPLMutexHolder oMutexHolder(&hMutex); GIntBig nPID = CPLGetPID(); - for (int i = 0; i < (int)oListDSToBeOpened.size(); i++) + for (auto oIter = oListDSToBeOpened.begin(); + oIter < oListDSToBeOpened.end(); ++oIter) { - if (oListDSToBeOpened[i].nPID == nPID && - oListDSToBeOpened[i].osDSName == osDSName) + const auto &ds = *oIter; + if (ds.nPID == nPID && ds.osDSName == osDSName) { - CPLString osInterestLayers = oListDSToBeOpened[i].osInterestLayers; - oListDSToBeOpened.erase(oListDSToBeOpened.begin() + i); + CPLString osInterestLayers = ds.osInterestLayers; + oListDSToBeOpened.erase(oIter); return osInterestLayers; } } @@ -224,7 +225,7 @@ static CPLString GetInterestLayersForDSName(const CPLString &osDSName) OGROSMDataSource::OGROSMDataSource() { - m_asKeys.push_back(nullptr); // guard to avoid index 0 to be used + m_apsKeys.push_back(nullptr); // guard to avoid index 0 to be used MAX_INDEXED_KEYS = static_cast( atoi(CPLGetConfigOption("OSM_MAX_INDEXED_KEYS", "32768"))); @@ -239,9 +240,7 @@ OGROSMDataSource::OGROSMDataSource() OGROSMDataSource::~OGROSMDataSource() { - for (int i = 0; i < m_nLayers; i++) - delete m_papoLayers[i]; - CPLFree(m_papoLayers); + m_apoLayers.clear(); CPLFree(m_pszName); @@ -278,11 +277,6 @@ OGROSMDataSource::~OGROSMDataSource() CPLFree(m_pasLonLatArray); CPLFree(m_panUnsortedReqIds); - for (int i = 0; i < m_nWayFeaturePairs; i++) - { - delete m_pasWayFeaturePairs[i].poFeature; - } - CPLFree(m_pasWayFeaturePairs); CPLFree(m_pasAccumulatedTags); CPLFree(pabyNonRedundantKeys); CPLFree(pabyNonRedundantValues); @@ -301,14 +295,14 @@ OGROSMDataSource::~OGROSMDataSource() fclose(f); #endif - for (int i = 1; i < static_cast(m_asKeys.size()); i++) + for (int i = 1; i < static_cast(m_apsKeys.size()); i++) { - KeyDesc *psKD = m_asKeys[i]; + KeyDesc *psKD = m_apsKeys[i]; if (psKD) { CPLFree(psKD->pszK); - for (int j = 0; j < static_cast(psKD->asValues.size()); j++) - CPLFree(psKD->asValues[j]); + for (int j = 0; j < static_cast(psKD->apszValues.size()); j++) + CPLFree(psKD->apszValues[j]); delete psKD; } } @@ -545,7 +539,7 @@ bool OGROSMDataSource::FlushCurrentSectorCompressedCase() { GByte abyOutBuffer[2 * SECTOR_SIZE]; GByte *pabyOut = abyOutBuffer; - LonLat *pasLonLatIn = (LonLat *)m_pabySector; + LonLat *pasLonLatIn = reinterpret_cast(m_pabySector); int nLastLon = 0; int nLastLat = 0; bool bLastValid = false; @@ -729,7 +723,7 @@ bool OGROSMDataSource::IndexPointCustom(OSMNode *psNode) void OGROSMDataSource::NotifyNodes(unsigned int nNodes, OSMNode *pasNodes) { const OGREnvelope *psEnvelope = - m_papoLayers[IDX_LYR_POINTS]->GetSpatialFilterEnvelope(); + m_apoLayers[IDX_LYR_POINTS]->GetSpatialFilterEnvelope(); for (unsigned int i = 0; i < nNodes; i++) { @@ -744,7 +738,7 @@ void OGROSMDataSource::NotifyNodes(unsigned int nNodes, OSMNode *pasNodes) if (!IndexPoint(&pasNodes[i])) break; - if (!m_papoLayers[IDX_LYR_POINTS]->IsUserInterested()) + if (!m_apoLayers[IDX_LYR_POINTS]->IsUserInterested()) continue; bool bInterestingTag = m_bReportAllNodes; @@ -755,7 +749,7 @@ void OGROSMDataSource::NotifyNodes(unsigned int nNodes, OSMNode *pasNodes) for (unsigned int j = 0; j < pasNodes[i].nTags; j++) { const char *pszK = pasTags[j].pszK; - if (m_papoLayers[IDX_LYR_POINTS]->IsSignificantKey(pszK)) + if (m_apoLayers[IDX_LYR_POINTS]->IsSignificantKey(pszK)) { bInterestingTag = true; break; @@ -765,19 +759,20 @@ void OGROSMDataSource::NotifyNodes(unsigned int nNodes, OSMNode *pasNodes) if (bInterestingTag) { - OGRFeature *poFeature = - new OGRFeature(m_papoLayers[IDX_LYR_POINTS]->GetLayerDefn()); + auto poFeature = std::make_unique( + m_apoLayers[IDX_LYR_POINTS]->GetLayerDefn()); poFeature->SetGeometryDirectly( new OGRPoint(pasNodes[i].dfLon, pasNodes[i].dfLat)); - m_papoLayers[IDX_LYR_POINTS]->SetFieldsFromTags( - poFeature, pasNodes[i].nID, false, pasNodes[i].nTags, pasTags, - &pasNodes[i].sInfo); + m_apoLayers[IDX_LYR_POINTS]->SetFieldsFromTags( + poFeature.get(), pasNodes[i].nID, false, pasNodes[i].nTags, + pasTags, &pasNodes[i].sInfo); - int bFilteredOut = FALSE; - if (!m_papoLayers[IDX_LYR_POINTS]->AddFeature( - poFeature, FALSE, &bFilteredOut, !m_bFeatureAdded)) + bool bFilteredOut = false; + if (!m_apoLayers[IDX_LYR_POINTS]->AddFeature(std::move(poFeature), + false, &bFilteredOut, + !m_bFeatureAdded)) { m_bStopParsing = true; break; @@ -935,7 +930,8 @@ void OGROSMDataSource::LookupNodesSQLite() while (sqlite3_step(hStmt) == SQLITE_ROW) { const GIntBig id = sqlite3_column_int64(hStmt, 0); - LonLat *psLonLat = (LonLat *)sqlite3_column_blob(hStmt, 1); + const LonLat *psLonLat = + reinterpret_cast(sqlite3_column_blob(hStmt, 1)); m_panReqIds[j] = id; m_pasLonLatArray[j].nLon = psLonLat->nLon; @@ -956,7 +952,7 @@ static bool DecompressSector(const GByte *pabyIn, int nSectorSize, GByte *pabyOut) { const GByte *pabyPtr = pabyIn; - LonLat *pasLonLatOut = (LonLat *)pabyOut; + LonLat *pasLonLatOut = reinterpret_cast(pabyOut); int nLastLon = 0; int nLastLat = 0; bool bLastValid = false; @@ -969,9 +965,9 @@ static bool DecompressSector(const GByte *pabyIn, int nSectorSize, if (bLastValid) { pasLonLatOut[i].nLon = - (int)(nLastLon + ReadVarSInt64(&pabyPtr)); + static_cast(nLastLon + ReadVarSInt64(&pabyPtr)); pasLonLatOut[i].nLat = - (int)(nLastLat + ReadVarSInt64(&pabyPtr)); + static_cast(nLastLat + ReadVarSInt64(&pabyPtr)); } else { @@ -990,7 +986,7 @@ static bool DecompressSector(const GByte *pabyIn, int nSectorSize, } } - int nRead = (int)(pabyPtr - pabyIn); + int nRead = static_cast(pabyPtr - pabyIn); nRead = ROUND_COMPRESS_SIZE(nRead); return nRead == nSectorSize; } @@ -1308,11 +1304,11 @@ static void WriteVarInt(unsigned int nVal, std::vector &abyData) { if ((nVal & (~0x7fU)) == 0) { - abyData.push_back((GByte)nVal); + abyData.push_back(static_cast(nVal)); return; } - abyData.push_back(0x80 | (GByte)(nVal & 0x7f)); + abyData.push_back(0x80 | static_cast(nVal & 0x7f)); nVal >>= 7; } } @@ -1325,13 +1321,13 @@ static void WriteVarInt64(GUIntBig nVal, std::vector &abyData) { while (true) { - if ((((GUInt32)nVal) & (~0x7fU)) == 0) + if ((static_cast(nVal) & (~0x7fU)) == 0) { - abyData.push_back((GByte)nVal); + abyData.push_back(static_cast(nVal)); return; } - abyData.push_back(0x80 | (GByte)(nVal & 0x7f)); + abyData.push_back(0x80 | static_cast(nVal & 0x7f)); nVal >>= 7; } } @@ -1348,11 +1344,11 @@ static void WriteVarSInt64(GIntBig nSVal, std::vector &abyData) { if ((nVal & (~0x7f)) == 0) { - abyData.push_back((GByte)nVal); + abyData.push_back(static_cast(nVal)); return; } - abyData.push_back(0x80 | (GByte)(nVal & 0x7f)); + abyData.push_back(0x80 | static_cast(nVal & 0x7f)); nVal >>= 7; } } @@ -1370,12 +1366,12 @@ static void WriteVarSInt64(GIntBig nSVal, GByte **ppabyData) { if ((nVal & (~0x7f)) == 0) { - *pabyData = (GByte)nVal; + *pabyData = static_cast(nVal); *ppabyData = pabyData + 1; return; } - *pabyData = 0x80 | (GByte)(nVal & 0x7f); + *pabyData = 0x80 | static_cast(nVal & 0x7f); nVal >>= 7; pabyData++; } @@ -1403,8 +1399,9 @@ void OGROSMDataSource::CompressWay(bool bIsArea, unsigned int nTags, } else { - const char *pszK = (const char *)pabyNonRedundantKeys + - pasTags[iTag].uKey.nOffsetInpabyNonRedundantKeys; + const char *pszK = + reinterpret_cast(pabyNonRedundantKeys) + + pasTags[iTag].uKey.nOffsetInpabyNonRedundantKeys; abyCompressedWay.push_back(0); @@ -1420,7 +1417,7 @@ void OGROSMDataSource::CompressWay(bool bIsArea, unsigned int nTags, else { const char *pszV = - (const char *)pabyNonRedundantValues + + reinterpret_cast(pabyNonRedundantValues) + pasTags[iTag].uVal.nOffsetInpabyNonRedundantValues; if (pasTags[iTag].bKIsIndex) @@ -1455,8 +1452,8 @@ void OGROSMDataSource::CompressWay(bool bIsArea, unsigned int nTags, reinterpret_cast(&(pasLonLatPairs[0])) + sizeof(LonLat)); for (int i = 1; i < nPoints; i++) { - GIntBig nDiff64 = (GIntBig)pasLonLatPairs[i].nLon - - (GIntBig)pasLonLatPairs[i - 1].nLon; + GIntBig nDiff64 = static_cast(pasLonLatPairs[i].nLon) - + static_cast(pasLonLatPairs[i - 1].nLon); WriteVarSInt64(nDiff64, abyCompressedWay); nDiff64 = pasLonLatPairs[i].nLat - pasLonLatPairs[i - 1].nLat; @@ -1510,14 +1507,15 @@ void OGROSMDataSource::UncompressWay(int nBytes, const GByte *pabyCompressedWay, if (pasTags) { - CPLAssert(nK >= 0 && nK < (int)m_asKeys.size()); + CPLAssert(nK >= 0 && static_cast(nK) < m_apsKeys.size()); pasTags[iTag].pszK = - nK ? m_asKeys[nK]->pszK : reinterpret_cast(pszK); + nK ? m_apsKeys[nK]->pszK : reinterpret_cast(pszK); CPLAssert(nK == 0 || - (nV >= 0 && nV < (int)m_asKeys[nK]->asValues.size())); - pasTags[iTag].pszV = - nV ? m_asKeys[nK]->asValues[nV] : (const char *)pszV; + (nV >= 0 && static_cast(nV) < + m_apsKeys[nK]->apszValues.size())); + pasTags[iTag].pszV = nV ? m_apsKeys[nK]->apszValues[nV] + : reinterpret_cast(pszV); } } @@ -1550,8 +1548,8 @@ void OGROSMDataSource::UncompressWay(int nBytes, const GByte *pabyCompressedWay, pabyPtr += 2 * sizeof(int); do { - lonLat.nLon = (int)(lonLat.nLon + ReadVarSInt64(&pabyPtr)); - lonLat.nLat = (int)(lonLat.nLat + ReadVarSInt64(&pabyPtr)); + lonLat.nLon = static_cast(lonLat.nLon + ReadVarSInt64(&pabyPtr)); + lonLat.nLat = static_cast(lonLat.nLat + ReadVarSInt64(&pabyPtr)); asCoords.emplace_back(lonLat); } while (pabyPtr < pabyCompressedWay + nBytes); } @@ -1622,26 +1620,24 @@ int OGROSMDataSource::FindNode(GIntBig nID) void OGROSMDataSource::ProcessWaysBatch() { - if (m_nWayFeaturePairs == 0) + if (m_asWayFeaturePairs.empty()) return; - // printf("nodes = %d, features = %d\n", nUnsortedReqIds, nWayFeaturePairs); + // printf("nodes = %d, features = %d\n", nUnsortedReqIds, int(m_asWayFeaturePairs.size())); LookupNodes(); - for (int iPair = 0; iPair < m_nWayFeaturePairs; iPair++) + for (WayFeaturePair &sWayFeaturePairs : m_asWayFeaturePairs) { - WayFeaturePair *psWayFeaturePairs = &m_pasWayFeaturePairs[iPair]; - - const bool bIsArea = psWayFeaturePairs->bIsArea; + const bool bIsArea = sWayFeaturePairs.bIsArea; m_asLonLatCache.clear(); #ifdef ENABLE_NODE_LOOKUP_BY_HASHING if (m_bHashedIndexValid) { - for (unsigned int i = 0; i < psWayFeaturePairs->nRefs; i++) + for (unsigned int i = 0; i < sWayFeaturePairs.nRefs; i++) { int nIndInHashArray = static_cast( - HASH_ID_FUNC(psWayFeaturePairs->panNodeRefs[i]) % + HASH_ID_FUNC(sWayFeaturePairs.panNodeRefs[i]) % HASHED_INDEXES_ARRAY_SIZE); int nIdx = m_panHashedIndexes[nIndInHashArray]; if (nIdx < -1) @@ -1651,7 +1647,7 @@ void OGROSMDataSource::ProcessWaysBatch() { nIdx = m_psCollisionBuckets[iBucket].nInd; if (m_panReqIds[nIdx] == - psWayFeaturePairs->panNodeRefs[i]) + sWayFeaturePairs.panNodeRefs[i]) break; iBucket = m_psCollisionBuckets[iBucket].nNext; if (iBucket < 0) @@ -1662,7 +1658,7 @@ void OGROSMDataSource::ProcessWaysBatch() } } else if (nIdx >= 0 && - m_panReqIds[nIdx] != psWayFeaturePairs->panNodeRefs[i]) + m_panReqIds[nIdx] != sWayFeaturePairs.panNodeRefs[i]) nIdx = -1; if (nIdx >= 0) @@ -1675,20 +1671,20 @@ void OGROSMDataSource::ProcessWaysBatch() #endif // ENABLE_NODE_LOOKUP_BY_HASHING { int nIdx = -1; - for (unsigned int i = 0; i < psWayFeaturePairs->nRefs; i++) + for (unsigned int i = 0; i < sWayFeaturePairs.nRefs; i++) { - if (nIdx >= 0 && psWayFeaturePairs->panNodeRefs[i] == - psWayFeaturePairs->panNodeRefs[i - 1] + 1) + if (nIdx >= 0 && sWayFeaturePairs.panNodeRefs[i] == + sWayFeaturePairs.panNodeRefs[i - 1] + 1) { - if (nIdx + 1 < (int)m_nReqIds && + if (static_cast(nIdx + 1) < m_nReqIds && m_panReqIds[nIdx + 1] == - psWayFeaturePairs->panNodeRefs[i]) + sWayFeaturePairs.panNodeRefs[i]) nIdx++; else nIdx = -1; } else - nIdx = FindNode(psWayFeaturePairs->panNodeRefs[i]); + nIdx = FindNode(sWayFeaturePairs.panNodeRefs[i]); if (nIdx >= 0) { m_asLonLatCache.push_back(m_pasLonLatArray[nIdx]); @@ -1706,28 +1702,27 @@ void OGROSMDataSource::ProcessWaysBatch() CPLDebug("OSM", "Way " CPL_FRMT_GIB " with %d nodes that could be found. Discarding it", - psWayFeaturePairs->nWayID, + sWayFeaturePairs.nWayID, static_cast(m_asLonLatCache.size())); - delete psWayFeaturePairs->poFeature; - psWayFeaturePairs->poFeature = nullptr; - psWayFeaturePairs->bIsArea = false; + sWayFeaturePairs.poFeature.reset(); + sWayFeaturePairs.bIsArea = false; continue; } - if (bIsArea && m_papoLayers[IDX_LYR_MULTIPOLYGONS]->IsUserInterested()) + if (bIsArea && m_apoLayers[IDX_LYR_MULTIPOLYGONS]->IsUserInterested()) { - IndexWay(psWayFeaturePairs->nWayID, /*bIsArea = */ true, - psWayFeaturePairs->nTags, psWayFeaturePairs->pasTags, + IndexWay(sWayFeaturePairs.nWayID, /*bIsArea = */ true, + sWayFeaturePairs.nTags, sWayFeaturePairs.pasTags, m_asLonLatCache.data(), static_cast(m_asLonLatCache.size()), - &psWayFeaturePairs->sInfo); + &sWayFeaturePairs.sInfo); } else - IndexWay(psWayFeaturePairs->nWayID, bIsArea, 0, nullptr, + IndexWay(sWayFeaturePairs.nWayID, bIsArea, 0, nullptr, m_asLonLatCache.data(), static_cast(m_asLonLatCache.size()), nullptr); - if (psWayFeaturePairs->poFeature == nullptr) + if (sWayFeaturePairs.poFeature == nullptr) { continue; } @@ -1743,35 +1738,33 @@ void OGROSMDataSource::ProcessWaysBatch() INT_TO_DBL(m_asLonLatCache[i].nLat)); } - psWayFeaturePairs->poFeature->SetGeometryDirectly(poGeom); + sWayFeaturePairs.poFeature->SetGeometryDirectly(poGeom); - if (m_asLonLatCache.size() != psWayFeaturePairs->nRefs) - CPLDebug( - "OSM", - "For way " CPL_FRMT_GIB ", got only %d nodes instead of %d", - psWayFeaturePairs->nWayID, nPoints, psWayFeaturePairs->nRefs); - - int bFilteredOut = FALSE; - if (!m_papoLayers[IDX_LYR_LINES]->AddFeature( - psWayFeaturePairs->poFeature, - psWayFeaturePairs->bAttrFilterAlreadyEvaluated, &bFilteredOut, + if (m_asLonLatCache.size() != sWayFeaturePairs.nRefs) + CPLDebug("OSM", + "For way " CPL_FRMT_GIB + ", got only %d nodes instead of %d", + sWayFeaturePairs.nWayID, nPoints, sWayFeaturePairs.nRefs); + + bool bFilteredOut = false; + if (!m_apoLayers[IDX_LYR_LINES]->AddFeature( + std::move(sWayFeaturePairs.poFeature), + sWayFeaturePairs.bAttrFilterAlreadyEvaluated, &bFilteredOut, !m_bFeatureAdded)) m_bStopParsing = true; else if (!bFilteredOut) m_bFeatureAdded = true; } - if (m_papoLayers[IDX_LYR_MULTIPOLYGONS]->IsUserInterested()) + if (m_apoLayers[IDX_LYR_MULTIPOLYGONS]->IsUserInterested()) { - for (int iPair = 0; iPair < m_nWayFeaturePairs; iPair++) + for (WayFeaturePair &sWayFeaturePairs : m_asWayFeaturePairs) { - WayFeaturePair *psWayFeaturePairs = &m_pasWayFeaturePairs[iPair]; - - if (psWayFeaturePairs->bIsArea && - (psWayFeaturePairs->nTags || m_bReportAllWays)) + if (sWayFeaturePairs.bIsArea && + (sWayFeaturePairs.nTags || m_bReportAllWays)) { sqlite3_bind_int64(m_hInsertPolygonsStandaloneStmt, 1, - psWayFeaturePairs->nWayID); + sWayFeaturePairs.nWayID); int rc = sqlite3_step(m_hInsertPolygonsStandaloneStmt); sqlite3_reset(m_hInsertPolygonsStandaloneStmt); @@ -1780,13 +1773,13 @@ void OGROSMDataSource::ProcessWaysBatch() CPLError(CE_Failure, CPLE_AppDefined, "Failed inserting into " "polygons_standalone " CPL_FRMT_GIB ": %s", - psWayFeaturePairs->nWayID, sqlite3_errmsg(m_hDB)); + sWayFeaturePairs.nWayID, sqlite3_errmsg(m_hDB)); } } } } - m_nWayFeaturePairs = 0; + m_asWayFeaturePairs.clear(); m_nUnsortedReqIds = 0; m_nAccumulatedTags = 0; @@ -1903,7 +1896,7 @@ void OGROSMDataSource::NotifyWay(OSMWay *psWay) for (unsigned int i = 0; i < psWay->nTags; i++) { const char *pszK = psWay->pasTags[i].pszK; - if (m_papoLayers[IDX_LYR_LINES]->IsSignificantKey(pszK)) + if (m_apoLayers[IDX_LYR_LINES]->IsSignificantKey(pszK)) { bInterestingTag = true; break; @@ -1911,29 +1904,29 @@ void OGROSMDataSource::NotifyWay(OSMWay *psWay) } } - OGRFeature *poFeature = nullptr; + std::unique_ptr poFeature; bool bAttrFilterAlreadyEvaluated = false; - if (!bIsArea && m_papoLayers[IDX_LYR_LINES]->IsUserInterested() && + if (!bIsArea && m_apoLayers[IDX_LYR_LINES]->IsUserInterested() && bInterestingTag) { - poFeature = new OGRFeature(m_papoLayers[IDX_LYR_LINES]->GetLayerDefn()); + poFeature = std::make_unique( + m_apoLayers[IDX_LYR_LINES]->GetLayerDefn()); - m_papoLayers[IDX_LYR_LINES]->SetFieldsFromTags( - poFeature, psWay->nID, false, psWay->nTags, psWay->pasTags, + m_apoLayers[IDX_LYR_LINES]->SetFieldsFromTags( + poFeature.get(), psWay->nID, false, psWay->nTags, psWay->pasTags, &psWay->sInfo); // Optimization: if we have an attribute filter, that does not require // geometry, and if we don't need to index ways, then we can just // evaluate the attribute filter without the geometry. - if (m_papoLayers[IDX_LYR_LINES]->HasAttributeFilter() && - !m_papoLayers[IDX_LYR_LINES] + if (m_apoLayers[IDX_LYR_LINES]->HasAttributeFilter() && + !m_apoLayers[IDX_LYR_LINES] ->AttributeFilterEvaluationNeedsGeometry() && !m_bIndexWays) { - if (!m_papoLayers[IDX_LYR_LINES]->EvaluateAttributeFilter( - poFeature)) + if (!m_apoLayers[IDX_LYR_LINES]->EvaluateAttributeFilter( + poFeature.get())) { - delete poFeature; return; } bAttrFilterAlreadyEvaluated = true; @@ -1946,7 +1939,8 @@ void OGROSMDataSource::NotifyWay(OSMWay *psWay) if (m_nUnsortedReqIds + psWay->nRefs > static_cast(MAX_ACCUMULATED_NODES) || - m_nWayFeaturePairs == MAX_DELAYED_FEATURES || + m_asWayFeaturePairs.size() == + static_cast(MAX_DELAYED_FEATURES) || m_nAccumulatedTags + psWay->nTags > static_cast(MAX_ACCUMULATED_TAGS) || nNonRedundantKeysLen + 1024 > MAX_NON_REDUNDANT_KEYS || @@ -1955,25 +1949,24 @@ void OGROSMDataSource::NotifyWay(OSMWay *psWay) ProcessWaysBatch(); } - WayFeaturePair *psWayFeaturePairs = - &m_pasWayFeaturePairs[m_nWayFeaturePairs]; + m_asWayFeaturePairs.push_back(WayFeaturePair()); + WayFeaturePair &sWayFeaturePairs = m_asWayFeaturePairs.back(); - psWayFeaturePairs->nWayID = psWay->nID; - psWayFeaturePairs->nRefs = psWay->nRefs - (bIsArea ? 1 : 0); - psWayFeaturePairs->panNodeRefs = m_panUnsortedReqIds + m_nUnsortedReqIds; - psWayFeaturePairs->poFeature = poFeature; - psWayFeaturePairs->bIsArea = bIsArea; - psWayFeaturePairs->bAttrFilterAlreadyEvaluated = - bAttrFilterAlreadyEvaluated; + sWayFeaturePairs.nWayID = psWay->nID; + sWayFeaturePairs.nRefs = psWay->nRefs - (bIsArea ? 1 : 0); + sWayFeaturePairs.panNodeRefs = m_panUnsortedReqIds + m_nUnsortedReqIds; + sWayFeaturePairs.poFeature = std::move(poFeature); + sWayFeaturePairs.bIsArea = bIsArea; + sWayFeaturePairs.bAttrFilterAlreadyEvaluated = bAttrFilterAlreadyEvaluated; - if (bIsArea && m_papoLayers[IDX_LYR_MULTIPOLYGONS]->IsUserInterested()) + if (bIsArea && m_apoLayers[IDX_LYR_MULTIPOLYGONS]->IsUserInterested()) { unsigned int nTagCount = 0; if (m_bNeedsToSaveWayInfo) { if (!psWay->sInfo.bTimeStampIsStr) - psWayFeaturePairs->sInfo.ts.nTimeStamp = + sWayFeaturePairs.sInfo.ts.nTimeStamp = psWay->sInfo.ts.nTimeStamp; else { @@ -1987,30 +1980,31 @@ void OGROSMDataSource::NotifyWay(OSMWay *psWay) brokendown.tm_mday = sField.Date.Day; brokendown.tm_hour = sField.Date.Hour; brokendown.tm_min = sField.Date.Minute; - brokendown.tm_sec = (int)(sField.Date.Second + .5); - psWayFeaturePairs->sInfo.ts.nTimeStamp = + brokendown.tm_sec = + static_cast(sField.Date.Second + .5); + sWayFeaturePairs.sInfo.ts.nTimeStamp = CPLYMDHMSToUnixTime(&brokendown); } else - psWayFeaturePairs->sInfo.ts.nTimeStamp = 0; + sWayFeaturePairs.sInfo.ts.nTimeStamp = 0; } - psWayFeaturePairs->sInfo.nChangeset = psWay->sInfo.nChangeset; - psWayFeaturePairs->sInfo.nVersion = psWay->sInfo.nVersion; - psWayFeaturePairs->sInfo.nUID = psWay->sInfo.nUID; - psWayFeaturePairs->sInfo.bTimeStampIsStr = false; - psWayFeaturePairs->sInfo.pszUserSID = ""; // FIXME + sWayFeaturePairs.sInfo.nChangeset = psWay->sInfo.nChangeset; + sWayFeaturePairs.sInfo.nVersion = psWay->sInfo.nVersion; + sWayFeaturePairs.sInfo.nUID = psWay->sInfo.nUID; + sWayFeaturePairs.sInfo.bTimeStampIsStr = false; + sWayFeaturePairs.sInfo.pszUserSID = ""; // FIXME } else { - psWayFeaturePairs->sInfo.ts.nTimeStamp = 0; - psWayFeaturePairs->sInfo.nChangeset = 0; - psWayFeaturePairs->sInfo.nVersion = 0; - psWayFeaturePairs->sInfo.nUID = 0; - psWayFeaturePairs->sInfo.bTimeStampIsStr = false; - psWayFeaturePairs->sInfo.pszUserSID = ""; + sWayFeaturePairs.sInfo.ts.nTimeStamp = 0; + sWayFeaturePairs.sInfo.nChangeset = 0; + sWayFeaturePairs.sInfo.nVersion = 0; + sWayFeaturePairs.sInfo.nUID = 0; + sWayFeaturePairs.sInfo.bTimeStampIsStr = false; + sWayFeaturePairs.sInfo.pszUserSID = ""; } - psWayFeaturePairs->pasTags = m_pasAccumulatedTags + m_nAccumulatedTags; + sWayFeaturePairs.pasTags = m_pasAccumulatedTags + m_nAccumulatedTags; for (unsigned int iTag = 0; iTag < psWay->nTags; iTag++) { @@ -2028,14 +2022,14 @@ void OGROSMDataSource::NotifyWay(OSMWay *psWay) KeyDesc *psKD = nullptr; if (oIterK == m_aoMapIndexedKeys.end()) { - if (m_asKeys.size() >= 1 + MAX_INDEXED_KEYS) + if (m_apsKeys.size() >= 1 + MAX_INDEXED_KEYS) { - if (m_asKeys.size() == 1 + MAX_INDEXED_KEYS) + if (m_apsKeys.size() == 1 + MAX_INDEXED_KEYS) { CPLDebug("OSM", "More than %d different keys found", MAX_INDEXED_KEYS); // To avoid next warnings. - m_asKeys.push_back(nullptr); + m_apsKeys.push_back(nullptr); } const int nLenK = static_cast(strlen(pszK)) + 1; @@ -2057,12 +2051,12 @@ void OGROSMDataSource::NotifyWay(OSMWay *psWay) { psKD = new KeyDesc(); psKD->pszK = CPLStrdup(pszK); - psKD->nKeyIndex = static_cast(m_asKeys.size()); + psKD->nKeyIndex = static_cast(m_apsKeys.size()); psKD->nOccurrences = 0; - psKD->asValues.push_back(CPLStrdup( + psKD->apszValues.push_back(CPLStrdup( "")); // guard value to avoid index 0 to be used m_aoMapIndexedKeys[psKD->pszK] = psKD; - m_asKeys.push_back(psKD); + m_apsKeys.push_back(psKD); } } else @@ -2079,16 +2073,16 @@ void OGROSMDataSource::NotifyWay(OSMWay *psWay) } if (psKD != nullptr && - psKD->asValues.size() < 1 + MAX_INDEXED_VALUES_PER_KEY) + psKD->apszValues.size() < 1 + MAX_INDEXED_VALUES_PER_KEY) { int nValueIndex = 0; auto oIterV = psKD->anMapV.find(pszV); if (oIterV == psKD->anMapV.end()) { char *pszVDup = CPLStrdup(pszV); - nValueIndex = static_cast(psKD->asValues.size()); + nValueIndex = static_cast(psKD->apszValues.size()); psKD->anMapV[pszVDup] = nValueIndex; - psKD->asValues.push_back(pszVDup); + psKD->apszValues.push_back(pszVDup); } else nValueIndex = oIterV->second; @@ -2102,12 +2096,12 @@ void OGROSMDataSource::NotifyWay(OSMWay *psWay) const int nLenV = static_cast(strlen(pszV)) + 1; if (psKD != nullptr && - psKD->asValues.size() == 1 + MAX_INDEXED_VALUES_PER_KEY) + psKD->apszValues.size() == 1 + MAX_INDEXED_VALUES_PER_KEY) { CPLDebug("OSM", "More than %d different values for tag %s", MAX_INDEXED_VALUES_PER_KEY, pszK); // To avoid next warnings. - psKD->asValues.push_back(CPLStrdup("")); + psKD->apszValues.push_back(CPLStrdup("")); } if (nNonRedundantValuesLen + nLenV > MAX_NON_REDUNDANT_VALUES) @@ -2131,23 +2125,21 @@ void OGROSMDataSource::NotifyWay(OSMWay *psWay) break; } - psWayFeaturePairs->nTags = nTagCount; + sWayFeaturePairs.nTags = nTagCount; } else { - psWayFeaturePairs->sInfo.ts.nTimeStamp = 0; - psWayFeaturePairs->sInfo.nChangeset = 0; - psWayFeaturePairs->sInfo.nVersion = 0; - psWayFeaturePairs->sInfo.nUID = 0; - psWayFeaturePairs->sInfo.bTimeStampIsStr = false; - psWayFeaturePairs->sInfo.pszUserSID = ""; + sWayFeaturePairs.sInfo.ts.nTimeStamp = 0; + sWayFeaturePairs.sInfo.nChangeset = 0; + sWayFeaturePairs.sInfo.nVersion = 0; + sWayFeaturePairs.sInfo.nUID = 0; + sWayFeaturePairs.sInfo.bTimeStampIsStr = false; + sWayFeaturePairs.sInfo.pszUserSID = ""; - psWayFeaturePairs->nTags = 0; - psWayFeaturePairs->pasTags = nullptr; + sWayFeaturePairs.nTags = 0; + sWayFeaturePairs.pasTags = nullptr; } - m_nWayFeaturePairs++; - memcpy(m_panUnsortedReqIds + m_nUnsortedReqIds, psWay->panNodeRefs, sizeof(GIntBig) * (psWay->nRefs - (bIsArea ? 1 : 0))); m_nUnsortedReqIds += (psWay->nRefs - (bIsArea ? 1 : 0)); @@ -2342,14 +2334,15 @@ OGRGeometry *OGROSMDataSource::BuildMultiPolygon(OSMRelation *psRelation, if (poMLS->getNumGeometries() > 0) { - OGRGeometryH hPoly = OGRBuildPolygonFromEdges((OGRGeometryH)poMLS, TRUE, - FALSE, 0, nullptr); + OGRGeometryH hPoly = OGRBuildPolygonFromEdges( + OGRGeometry::ToHandle(poMLS), TRUE, FALSE, 0, nullptr); if (hPoly != nullptr && OGR_G_GetGeometryType(hPoly) == wkbPolygon) { OGRPolygon *poSuperPoly = OGRGeometry::FromHandle(hPoly)->toPolygon(); - for (unsigned int i = 0; - i < 1 + (unsigned int)poSuperPoly->getNumInteriorRings(); i++) + const unsigned nRings = 1 + static_cast( + poSuperPoly->getNumInteriorRings()); + for (unsigned int i = 0; i < nRings; i++) { OGRLinearRing *poRing = (i == 0) ? poSuperPoly->getExteriorRing() @@ -2494,7 +2487,7 @@ OGRGeometry *OGROSMDataSource::BuildGeometryCollection(OSMRelation *psRelation, void OGROSMDataSource::NotifyRelation(OSMRelation *psRelation) { - if (m_nWayFeaturePairs != 0) + if (!m_asWayFeaturePairs.empty()) ProcessWaysBatch(); m_nRelationsProcessed++; @@ -2542,26 +2535,26 @@ void OGROSMDataSource::NotifyRelation(OSMRelation *psRelation) const int iCurLayer = bMultiPolygon ? IDX_LYR_MULTIPOLYGONS : bMultiLineString ? IDX_LYR_MULTILINESTRINGS : IDX_LYR_OTHER_RELATIONS; - if (!m_papoLayers[iCurLayer]->IsUserInterested()) + if (!m_apoLayers[iCurLayer]->IsUserInterested()) return; - OGRFeature *poFeature = nullptr; + std::unique_ptr poFeature; if (!(bMultiPolygon && !bInterestingTagFound) && // We cannot do early filtering for multipolygon that has no // interesting tag, since we may fetch attributes from ways. - m_papoLayers[iCurLayer]->HasAttributeFilter() && - !m_papoLayers[iCurLayer]->AttributeFilterEvaluationNeedsGeometry()) + m_apoLayers[iCurLayer]->HasAttributeFilter() && + !m_apoLayers[iCurLayer]->AttributeFilterEvaluationNeedsGeometry()) { - poFeature = new OGRFeature(m_papoLayers[iCurLayer]->GetLayerDefn()); + poFeature = std::make_unique( + m_apoLayers[iCurLayer]->GetLayerDefn()); - m_papoLayers[iCurLayer]->SetFieldsFromTags( - poFeature, psRelation->nID, false, psRelation->nTags, + m_apoLayers[iCurLayer]->SetFieldsFromTags( + poFeature.get(), psRelation->nID, false, psRelation->nTags, psRelation->pasTags, &psRelation->sInfo); - if (!m_papoLayers[iCurLayer]->EvaluateAttributeFilter(poFeature)) + if (!m_apoLayers[iCurLayer]->EvaluateAttributeFilter(poFeature.get())) { - delete poFeature; return; } } @@ -2592,10 +2585,11 @@ void OGROSMDataSource::NotifyRelation(OSMRelation *psRelation) bool bAttrFilterAlreadyEvaluated = true; if (poFeature == nullptr) { - poFeature = new OGRFeature(m_papoLayers[iCurLayer]->GetLayerDefn()); + poFeature = std::make_unique( + m_apoLayers[iCurLayer]->GetLayerDefn()); - m_papoLayers[iCurLayer]->SetFieldsFromTags( - poFeature, psRelation->nID, false, + m_apoLayers[iCurLayer]->SetFieldsFromTags( + poFeature.get(), psRelation->nID, false, nExtraTags ? nExtraTags : psRelation->nTags, nExtraTags ? pasExtraTags : psRelation->pasTags, &psRelation->sInfo); @@ -2605,18 +2599,14 @@ void OGROSMDataSource::NotifyRelation(OSMRelation *psRelation) poFeature->SetGeometryDirectly(poGeom); - int bFilteredOut = FALSE; - if (!m_papoLayers[iCurLayer]->AddFeature( - poFeature, bAttrFilterAlreadyEvaluated, &bFilteredOut, - !m_bFeatureAdded)) + bool bFilteredOut = FALSE; + if (!m_apoLayers[iCurLayer]->AddFeature( + std::move(poFeature), bAttrFilterAlreadyEvaluated, + &bFilteredOut, !m_bFeatureAdded)) m_bStopParsing = true; else if (!bFilteredOut) m_bFeatureAdded = true; } - else - { - delete poFeature; - } } static void OGROSMNotifyRelation(OSMRelation *psRelation, @@ -2650,7 +2640,7 @@ void OGROSMDataSource::ProcessPolygonsStandalone() bool bFirst = true; while (m_bHasRowInPolygonsStandalone && - m_papoLayers[IDX_LYR_MULTIPOLYGONS]->m_nFeatureArraySize < 10000) + m_apoLayers[IDX_LYR_MULTIPOLYGONS]->m_apoFeatures.size() < 10000) { if (bFirst) { @@ -2685,17 +2675,18 @@ void OGROSMDataSource::ProcessPolygonsStandalone() INT_TO_DBL(m_asLonLatCache[j].nLat)); } - OGRFeature *poFeature = new OGRFeature( - m_papoLayers[IDX_LYR_MULTIPOLYGONS]->GetLayerDefn()); + auto poFeature = std::make_unique( + m_apoLayers[IDX_LYR_MULTIPOLYGONS]->GetLayerDefn()); - m_papoLayers[IDX_LYR_MULTIPOLYGONS]->SetFieldsFromTags( - poFeature, id, true, nTags, pasTags, &sInfo); + m_apoLayers[IDX_LYR_MULTIPOLYGONS]->SetFieldsFromTags( + poFeature.get(), id, true, nTags, pasTags, &sInfo); poFeature->SetGeometryDirectly(poMulti); - int bFilteredOut = FALSE; - if (!m_papoLayers[IDX_LYR_MULTIPOLYGONS]->AddFeature( - poFeature, FALSE, &bFilteredOut, !m_bFeatureAdded)) + bool bFilteredOut = false; + if (!m_apoLayers[IDX_LYR_MULTIPOLYGONS]->AddFeature( + std::move(poFeature), FALSE, &bFilteredOut, + !m_bFeatureAdded)) { m_bStopParsing = true; break; @@ -2778,31 +2769,26 @@ int OGROSMDataSource::Open(const char *pszFilename, char **papszOpenOptionsIn) if (m_bCompressNodes) CPLDebug("OSM", "Using compression for nodes DB"); - m_nLayers = 5; - m_papoLayers = static_cast( - CPLMalloc(m_nLayers * sizeof(OGROSMLayer *))); - - m_papoLayers[IDX_LYR_POINTS] = - new OGROSMLayer(this, IDX_LYR_POINTS, "points"); - m_papoLayers[IDX_LYR_POINTS]->GetLayerDefn()->SetGeomType(wkbPoint); + // Do not change the below order without updating the IDX_LYR_ constants! + m_apoLayers.emplace_back( + std::make_unique(this, IDX_LYR_POINTS, "points")); + m_apoLayers.back()->GetLayerDefn()->SetGeomType(wkbPoint); - m_papoLayers[IDX_LYR_LINES] = new OGROSMLayer(this, IDX_LYR_LINES, "lines"); - m_papoLayers[IDX_LYR_LINES]->GetLayerDefn()->SetGeomType(wkbLineString); + m_apoLayers.emplace_back( + std::make_unique(this, IDX_LYR_LINES, "lines")); + m_apoLayers.back()->GetLayerDefn()->SetGeomType(wkbLineString); - m_papoLayers[IDX_LYR_MULTILINESTRINGS] = - new OGROSMLayer(this, IDX_LYR_MULTILINESTRINGS, "multilinestrings"); - m_papoLayers[IDX_LYR_MULTILINESTRINGS]->GetLayerDefn()->SetGeomType( - wkbMultiLineString); + m_apoLayers.emplace_back(std::make_unique( + this, IDX_LYR_MULTILINESTRINGS, "multilinestrings")); + m_apoLayers.back()->GetLayerDefn()->SetGeomType(wkbMultiLineString); - m_papoLayers[IDX_LYR_MULTIPOLYGONS] = - new OGROSMLayer(this, IDX_LYR_MULTIPOLYGONS, "multipolygons"); - m_papoLayers[IDX_LYR_MULTIPOLYGONS]->GetLayerDefn()->SetGeomType( - wkbMultiPolygon); + m_apoLayers.emplace_back(std::make_unique( + this, IDX_LYR_MULTIPOLYGONS, "multipolygons")); + m_apoLayers.back()->GetLayerDefn()->SetGeomType(wkbMultiPolygon); - m_papoLayers[IDX_LYR_OTHER_RELATIONS] = - new OGROSMLayer(this, IDX_LYR_OTHER_RELATIONS, "other_relations"); - m_papoLayers[IDX_LYR_OTHER_RELATIONS]->GetLayerDefn()->SetGeomType( - wkbGeometryCollection); + m_apoLayers.emplace_back(std::make_unique( + this, IDX_LYR_OTHER_RELATIONS, "other_relations")); + m_apoLayers.back()->GetLayerDefn()->SetGeomType(wkbGeometryCollection); if (!ParseConf(papszOpenOptionsIn)) { @@ -2828,26 +2814,26 @@ int OGROSMDataSource::Open(const char *pszFilename, char **papszOpenOptionsIn) } const auto eTagsSubType = m_bTagsAsHSTORE ? OFSTNone : OFSTJSON; - for (int i = 0; i < m_nLayers; i++) + for (auto &&poLayer : m_apoLayers) { - if (m_papoLayers[i]->HasAllTags()) + if (poLayer->HasAllTags()) { - m_papoLayers[i]->AddField("all_tags", OFTString, eTagsSubType); - if (m_papoLayers[i]->HasOtherTags()) + poLayer->AddField("all_tags", OFTString, eTagsSubType); + if (poLayer->HasOtherTags()) { - m_papoLayers[i]->SetHasOtherTags(false); + poLayer->SetHasOtherTags(false); } } - else if (m_papoLayers[i]->HasOtherTags()) - m_papoLayers[i]->AddField("other_tags", OFTString, eTagsSubType); + else if (poLayer->HasOtherTags()) + poLayer->AddField("other_tags", OFTString, eTagsSubType); } m_bNeedsToSaveWayInfo = - (m_papoLayers[IDX_LYR_MULTIPOLYGONS]->HasTimestamp() || - m_papoLayers[IDX_LYR_MULTIPOLYGONS]->HasChangeset() || - m_papoLayers[IDX_LYR_MULTIPOLYGONS]->HasVersion() || - m_papoLayers[IDX_LYR_MULTIPOLYGONS]->HasUID() || - m_papoLayers[IDX_LYR_MULTIPOLYGONS]->HasUser()); + (m_apoLayers[IDX_LYR_MULTIPOLYGONS]->HasTimestamp() || + m_apoLayers[IDX_LYR_MULTIPOLYGONS]->HasChangeset() || + m_apoLayers[IDX_LYR_MULTIPOLYGONS]->HasVersion() || + m_apoLayers[IDX_LYR_MULTIPOLYGONS]->HasUID() || + m_apoLayers[IDX_LYR_MULTIPOLYGONS]->HasUser()); m_panReqIds = static_cast( VSI_MALLOC_VERBOSE(MAX_ACCUMULATED_NODES * sizeof(GIntBig))); @@ -2861,8 +2847,16 @@ int OGROSMDataSource::Open(const char *pszFilename, char **papszOpenOptionsIn) VSI_MALLOC_VERBOSE(MAX_ACCUMULATED_NODES * sizeof(LonLat))); m_panUnsortedReqIds = static_cast( VSI_MALLOC_VERBOSE(MAX_ACCUMULATED_NODES * sizeof(GIntBig))); - m_pasWayFeaturePairs = static_cast( - VSI_MALLOC_VERBOSE(MAX_DELAYED_FEATURES * sizeof(WayFeaturePair))); + try + { + m_asWayFeaturePairs.resize(MAX_DELAYED_FEATURES); + } + catch (const std::exception &) + { + CPLError(CE_Failure, CPLE_OutOfMemory, + "OGROSMDataSource::Open(): out of memory"); + return FALSE; + } m_pasAccumulatedTags = static_cast( VSI_MALLOC_VERBOSE(MAX_ACCUMULATED_TAGS * sizeof(IndexedKVP))); pabyNonRedundantValues = @@ -2870,9 +2864,8 @@ int OGROSMDataSource::Open(const char *pszFilename, char **papszOpenOptionsIn) pabyNonRedundantKeys = static_cast(VSI_MALLOC_VERBOSE(MAX_NON_REDUNDANT_KEYS)); if (m_panReqIds == nullptr || m_pasLonLatArray == nullptr || - m_panUnsortedReqIds == nullptr || m_pasWayFeaturePairs == nullptr || - m_pasAccumulatedTags == nullptr || pabyNonRedundantValues == nullptr || - pabyNonRedundantKeys == nullptr) + m_panUnsortedReqIds == nullptr || m_pasAccumulatedTags == nullptr || + pabyNonRedundantValues == nullptr || pabyNonRedundantKeys == nullptr) { return FALSE; } @@ -2911,7 +2904,8 @@ int OGROSMDataSource::Open(const char *pszFilename, char **papszOpenOptionsIn) CPLPushErrorHandler(CPLQuietErrorHandler); bool bSuccess = - VSIFSeekL(m_fpNodes, (vsi_l_offset)(nSize * 3 / 4), SEEK_SET) == 0; + VSIFSeekL(m_fpNodes, static_cast(nSize * 3 / 4), + SEEK_SET) == 0; CPLPopErrorHandler(); if (bSuccess) @@ -3380,9 +3374,9 @@ void OGROSMDataSource::AddComputedAttributes( { if (!oAttributes[i].osSQL.empty()) { - m_papoLayers[iCurLayer]->AddComputedAttribute(oAttributes[i].osName, - oAttributes[i].eType, - oAttributes[i].osSQL); + m_apoLayers[iCurLayer]->AddComputedAttribute(oAttributes[i].osName, + oAttributes[i].eType, + oAttributes[i].osSQL); } } } @@ -3425,14 +3419,17 @@ bool OGROSMDataSource::ParseConf(char **papszOpenOptionsIn) iCurLayer = -1; pszLine++; - ((char *)pszLine)[strlen(pszLine) - 1] = '\0'; /* Evil but OK */ - for (int i = 0; i < m_nLayers; i++) + const_cast(pszLine)[strlen(pszLine) - 1] = + '\0'; /* Evil but OK */ + int i = 0; + for (auto &&poLayer : m_apoLayers) { - if (strcmp(pszLine, m_papoLayers[i]->GetName()) == 0) + if (strcmp(pszLine, poLayer->GetName()) == 0) { iCurLayer = i; break; } + ++i; } if (iCurLayer < 0) { @@ -3533,89 +3530,88 @@ bool OGROSMDataSource::ParseConf(char **papszOpenOptionsIn) strcmp(papszTokens[0], "other_tags") == 0) { if (strcmp(papszTokens[1], "no") == 0) - m_papoLayers[iCurLayer]->SetHasOtherTags(false); + m_apoLayers[iCurLayer]->SetHasOtherTags(false); else if (strcmp(papszTokens[1], "yes") == 0) - m_papoLayers[iCurLayer]->SetHasOtherTags(true); + m_apoLayers[iCurLayer]->SetHasOtherTags(true); } else if (CSLCount(papszTokens) == 2 && strcmp(papszTokens[0], "all_tags") == 0) { if (strcmp(papszTokens[1], "no") == 0) - m_papoLayers[iCurLayer]->SetHasAllTags(false); + m_apoLayers[iCurLayer]->SetHasAllTags(false); else if (strcmp(papszTokens[1], "yes") == 0) - m_papoLayers[iCurLayer]->SetHasAllTags(true); + m_apoLayers[iCurLayer]->SetHasAllTags(true); } else if (CSLCount(papszTokens) == 2 && strcmp(papszTokens[0], "osm_id") == 0) { if (strcmp(papszTokens[1], "no") == 0) - m_papoLayers[iCurLayer]->SetHasOSMId(false); + m_apoLayers[iCurLayer]->SetHasOSMId(false); else if (strcmp(papszTokens[1], "yes") == 0) { - m_papoLayers[iCurLayer]->SetHasOSMId(true); - m_papoLayers[iCurLayer]->AddField("osm_id", OFTString); + m_apoLayers[iCurLayer]->SetHasOSMId(true); + m_apoLayers[iCurLayer]->AddField("osm_id", OFTString); if (iCurLayer == IDX_LYR_MULTIPOLYGONS) - m_papoLayers[iCurLayer]->AddField("osm_way_id", - OFTString); + m_apoLayers[iCurLayer]->AddField("osm_way_id", + OFTString); } } else if (CSLCount(papszTokens) == 2 && strcmp(papszTokens[0], "osm_version") == 0) { if (strcmp(papszTokens[1], "no") == 0) - m_papoLayers[iCurLayer]->SetHasVersion(false); + m_apoLayers[iCurLayer]->SetHasVersion(false); else if (strcmp(papszTokens[1], "yes") == 0) { - m_papoLayers[iCurLayer]->SetHasVersion(true); - m_papoLayers[iCurLayer]->AddField("osm_version", - OFTInteger); + m_apoLayers[iCurLayer]->SetHasVersion(true); + m_apoLayers[iCurLayer]->AddField("osm_version", OFTInteger); } } else if (CSLCount(papszTokens) == 2 && strcmp(papszTokens[0], "osm_timestamp") == 0) { if (strcmp(papszTokens[1], "no") == 0) - m_papoLayers[iCurLayer]->SetHasTimestamp(false); + m_apoLayers[iCurLayer]->SetHasTimestamp(false); else if (strcmp(papszTokens[1], "yes") == 0) { - m_papoLayers[iCurLayer]->SetHasTimestamp(true); - m_papoLayers[iCurLayer]->AddField("osm_timestamp", - OFTDateTime); + m_apoLayers[iCurLayer]->SetHasTimestamp(true); + m_apoLayers[iCurLayer]->AddField("osm_timestamp", + OFTDateTime); } } else if (CSLCount(papszTokens) == 2 && strcmp(papszTokens[0], "osm_uid") == 0) { if (strcmp(papszTokens[1], "no") == 0) - m_papoLayers[iCurLayer]->SetHasUID(false); + m_apoLayers[iCurLayer]->SetHasUID(false); else if (strcmp(papszTokens[1], "yes") == 0) { - m_papoLayers[iCurLayer]->SetHasUID(true); - m_papoLayers[iCurLayer]->AddField("osm_uid", OFTInteger); + m_apoLayers[iCurLayer]->SetHasUID(true); + m_apoLayers[iCurLayer]->AddField("osm_uid", OFTInteger); } } else if (CSLCount(papszTokens) == 2 && strcmp(papszTokens[0], "osm_user") == 0) { if (strcmp(papszTokens[1], "no") == 0) - m_papoLayers[iCurLayer]->SetHasUser(false); + m_apoLayers[iCurLayer]->SetHasUser(false); else if (strcmp(papszTokens[1], "yes") == 0) { - m_papoLayers[iCurLayer]->SetHasUser(true); - m_papoLayers[iCurLayer]->AddField("osm_user", OFTString); + m_apoLayers[iCurLayer]->SetHasUser(true); + m_apoLayers[iCurLayer]->AddField("osm_user", OFTString); } } else if (CSLCount(papszTokens) == 2 && strcmp(papszTokens[0], "osm_changeset") == 0) { if (strcmp(papszTokens[1], "no") == 0) - m_papoLayers[iCurLayer]->SetHasChangeset(false); + m_apoLayers[iCurLayer]->SetHasChangeset(false); else if (strcmp(papszTokens[1], "yes") == 0) { - m_papoLayers[iCurLayer]->SetHasChangeset(true); - m_papoLayers[iCurLayer]->AddField("osm_changeset", - OFTInteger); + m_apoLayers[iCurLayer]->SetHasChangeset(true); + m_apoLayers[iCurLayer]->AddField("osm_changeset", + OFTInteger); } } else if (CSLCount(papszTokens) == 2 && @@ -3625,8 +3621,8 @@ bool OGROSMDataSource::ParseConf(char **papszOpenOptionsIn) CSLTokenizeString2(papszTokens[1], ",", 0); for (int i = 0; papszTokens2[i] != nullptr; i++) { - m_papoLayers[iCurLayer]->AddField(papszTokens2[i], - OFTString); + m_apoLayers[iCurLayer]->AddField(papszTokens2[i], + OFTString); for (const char *&pszIgnoredKey : m_ignoredKeys) { if (strcmp(papszTokens2[i], pszIgnoredKey) == 0) @@ -3643,7 +3639,7 @@ bool OGROSMDataSource::ParseConf(char **papszOpenOptionsIn) CSLTokenizeString2(papszTokens[1], ",", 0); for (int i = 0; papszTokens2[i] != nullptr; i++) { - m_papoLayers[iCurLayer]->AddInsignificantKey( + m_apoLayers[iCurLayer]->AddInsignificantKey( papszTokens2[i]); } CSLDestroy(papszTokens2); @@ -3655,8 +3651,8 @@ bool OGROSMDataSource::ParseConf(char **papszOpenOptionsIn) CSLTokenizeString2(papszTokens[1], ",", 0); for (int i = 0; papszTokens2[i] != nullptr; i++) { - m_papoLayers[iCurLayer]->AddIgnoreKey(papszTokens2[i]); - m_papoLayers[iCurLayer]->AddWarnKey(papszTokens2[i]); + m_apoLayers[iCurLayer]->AddIgnoreKey(papszTokens2[i]); + m_apoLayers[iCurLayer]->AddWarnKey(papszTokens2[i]); } CSLDestroy(papszTokens2); } @@ -3709,11 +3705,11 @@ bool OGROSMDataSource::ParseConf(char **papszOpenOptionsIn) if (!bFound) { const int idx = - m_papoLayers[iCurLayer]->GetLayerDefn()->GetFieldIndex( + m_apoLayers[iCurLayer]->GetLayerDefn()->GetFieldIndex( osName); if (idx >= 0) { - m_papoLayers[iCurLayer] + m_apoLayers[iCurLayer] ->GetLayerDefn() ->GetFieldDefn(idx) ->SetType(eType); @@ -3821,29 +3817,24 @@ int OGROSMDataSource::MyResetReading() sqlite3_reset(m_hSelectPolygonsStandaloneStmt); { - for (int i = 0; i < m_nWayFeaturePairs; i++) - { - delete m_pasWayFeaturePairs[i].poFeature; - } - m_nWayFeaturePairs = 0; + m_asWayFeaturePairs.clear(); m_nUnsortedReqIds = 0; m_nReqIds = 0; m_nAccumulatedTags = 0; nNonRedundantKeysLen = 0; nNonRedundantValuesLen = 0; - for (int i = 1; i < static_cast(m_asKeys.size()); i++) + for (KeyDesc *psKD : m_apsKeys) { - KeyDesc *psKD = m_asKeys[i]; if (psKD) { CPLFree(psKD->pszK); - for (int j = 0; j < (int)psKD->asValues.size(); j++) - CPLFree(psKD->asValues[j]); + for (auto *pszValue : psKD->apszValues) + CPLFree(pszValue); delete psKD; } } - m_asKeys.resize(1); // keep guard to avoid index 0 to be used + m_apsKeys.resize(1); // keep guard to avoid index 0 to be used m_aoMapIndexedKeys.clear(); } @@ -3878,9 +3869,9 @@ int OGROSMDataSource::MyResetReading() } } - for (int i = 0; i < m_nLayers; i++) + for (auto &&poLayer : m_apoLayers) { - m_papoLayers[i]->ForceResetReading(); + poLayer->ForceResetReading(); } m_bStopParsing = false; @@ -3911,7 +3902,7 @@ OGRFeature *OGROSMDataSource::GetNextFeature(OGRLayer **ppoBelongingLayer, if (m_poCurrentLayer == nullptr) { - m_poCurrentLayer = m_papoLayers[0]; + m_poCurrentLayer = m_apoLayers[0].get(); } if (pdfProgressPct != nullptr || pfnProgress != nullptr) { @@ -3999,9 +3990,9 @@ bool OGROSMDataSource::ParseNextChunk(int nIdxLayer, if (!pfnProgress(dfPct, "", pProgressData)) { m_bStopParsing = true; - for (int i = 0; i < m_nLayers; i++) + for (auto &&poLayer : m_apoLayers) { - m_papoLayers[i]->ForceResetReading(); + poLayer->ForceResetReading(); } return false; } @@ -4011,7 +4002,7 @@ bool OGROSMDataSource::ParseNextChunk(int nIdxLayer, { if (eRet == OSM_EOF) { - if (m_nWayFeaturePairs != 0) + if (!m_asWayFeaturePairs.empty()) ProcessWaysBatch(); ProcessPolygonsStandalone(); @@ -4098,12 +4089,12 @@ bool OGROSMDataSource::TransferToDiskIfNecesserary() { VSIFSeekL(fp, 0, SEEK_END); vsi_l_offset nCurSize = VSIFTellL(fp); - GIntBig nNewSize = - static_cast(m_nMaxSizeForInMemoryDBInMB) * + vsi_l_offset nNewSize = + static_cast(m_nMaxSizeForInMemoryDBInMB) * 1024 * 1024; CPLPushErrorHandler(CPLQuietErrorHandler); const bool bSuccess = - VSIFSeekL(fp, (vsi_l_offset)nNewSize, SEEK_SET) == 0; + VSIFSeekL(fp, nNewSize, SEEK_SET) == 0; CPLPopErrorHandler(); if (bSuccess) @@ -4224,10 +4215,10 @@ int OGROSMDataSource::TestCapability(const char *pszCap) OGRLayer *OGROSMDataSource::GetLayer(int iLayer) { - if (iLayer < 0 || iLayer >= m_nLayers) + if (iLayer < 0 || static_cast(iLayer) >= m_apoLayers.size()) return nullptr; - return m_papoLayers[iLayer]; + return m_apoLayers[iLayer].get(); } /************************************************************************/ @@ -4263,6 +4254,10 @@ class OGROSMSingleFeatureLayer final : public OGRLayer OGRFeatureDefn *poFeatureDefn; int iNextShapeId; + OGROSMSingleFeatureLayer(const OGROSMSingleFeatureLayer &) = delete; + OGROSMSingleFeatureLayer & + operator=(const OGROSMSingleFeatureLayer &) = delete; + public: OGROSMSingleFeatureLayer(const char *pszLayerName, int nVal); OGROSMSingleFeatureLayer(const char *pszLayerName, const char *pszVal); @@ -4404,26 +4399,26 @@ OGRLayer *OGROSMDataSource::ExecuteSQL(const char *pszSQLCommand, char **papszTokens = CSLTokenizeString2(pszSQLCommand + 21, ",", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES); - for (int i = 0; i < m_nLayers; i++) + for (auto &&poLayer : m_apoLayers) { - m_papoLayers[i]->SetDeclareInterest(FALSE); + poLayer->SetDeclareInterest(FALSE); } for (int i = 0; papszTokens[i] != nullptr; i++) { OGROSMLayer *poLayer = - reinterpret_cast(GetLayerByName(papszTokens[i])); + dynamic_cast(GetLayerByName(papszTokens[i])); if (poLayer != nullptr) { poLayer->SetDeclareInterest(TRUE); } } - if (m_papoLayers[IDX_LYR_POINTS]->IsUserInterested() && - !m_papoLayers[IDX_LYR_LINES]->IsUserInterested() && - !m_papoLayers[IDX_LYR_MULTILINESTRINGS]->IsUserInterested() && - !m_papoLayers[IDX_LYR_MULTIPOLYGONS]->IsUserInterested() && - !m_papoLayers[IDX_LYR_OTHER_RELATIONS]->IsUserInterested()) + if (m_apoLayers[IDX_LYR_POINTS]->IsUserInterested() && + !m_apoLayers[IDX_LYR_LINES]->IsUserInterested() && + !m_apoLayers[IDX_LYR_MULTILINESTRINGS]->IsUserInterested() && + !m_apoLayers[IDX_LYR_MULTIPOLYGONS]->IsUserInterested() && + !m_apoLayers[IDX_LYR_OTHER_RELATIONS]->IsUserInterested()) { if (CPLGetConfigOption("OSM_INDEX_POINTS", nullptr) == nullptr) { @@ -4444,10 +4439,10 @@ OGRLayer *OGROSMDataSource::ExecuteSQL(const char *pszSQLCommand, m_bUseWaysIndex = false; } } - else if (m_papoLayers[IDX_LYR_LINES]->IsUserInterested() && - !m_papoLayers[IDX_LYR_MULTILINESTRINGS]->IsUserInterested() && - !m_papoLayers[IDX_LYR_MULTIPOLYGONS]->IsUserInterested() && - !m_papoLayers[IDX_LYR_OTHER_RELATIONS]->IsUserInterested()) + else if (m_apoLayers[IDX_LYR_LINES]->IsUserInterested() && + !m_apoLayers[IDX_LYR_MULTILINESTRINGS]->IsUserInterested() && + !m_apoLayers[IDX_LYR_MULTIPOLYGONS]->IsUserInterested() && + !m_apoLayers[IDX_LYR_OTHER_RELATIONS]->IsUserInterested()) { if (CPLGetConfigOption("OSM_INDEX_WAYS", nullptr) == nullptr) { @@ -4526,10 +4521,10 @@ OGRLayer *OGROSMDataSource::ExecuteSQL(const char *pszSQLCommand, { /* Backup current optimization parameters */ m_abSavedDeclaredInterest.resize(0); - for (int i = 0; i < m_nLayers; i++) + for (auto &&poLayer : m_apoLayers) { m_abSavedDeclaredInterest.push_back( - m_papoLayers[i]->IsUserInterested()); + poLayer->IsUserInterested()); } m_bIndexPointsBackup = m_bIndexPoints; m_bUsePointsIndexBackup = m_bUsePointsIndex; @@ -4578,9 +4573,11 @@ void OGROSMDataSource::ReleaseResultSet(OGRLayer *poLayer) m_bIsFeatureCountEnabled = false; /* Restore backup'ed optimization parameters */ - for (int i = 0; i < m_nLayers; i++) + int i = 0; + for (auto &&poIterLayer : m_apoLayers) { - m_papoLayers[i]->SetDeclareInterest(m_abSavedDeclaredInterest[i]); + poIterLayer->SetDeclareInterest(m_abSavedDeclaredInterest[i]); + ++i; } if (m_bIndexPointsBackup && !m_bIndexPoints) CPLDebug("OSM", "Re-enabling indexing of nodes"); @@ -4590,7 +4587,7 @@ void OGROSMDataSource::ReleaseResultSet(OGRLayer *poLayer) CPLDebug("OSM", "Re-enabling indexing of ways"); m_bIndexWays = m_bIndexWaysBackup; m_bUseWaysIndex = m_bUseWaysIndexBackup; - m_abSavedDeclaredInterest.resize(0); + m_abSavedDeclaredInterest.clear(); } delete poLayer; diff --git a/ogr/ogrsf_frmts/osm/ogrosmdriver.cpp b/ogr/ogrsf_frmts/osm/ogrosmdriver.cpp index 14903e128d69..7414ee5d6132 100644 --- a/ogr/ogrsf_frmts/osm/ogrosmdriver.cpp +++ b/ogr/ogrsf_frmts/osm/ogrosmdriver.cpp @@ -51,7 +51,8 @@ static int OGROSMDriverIdentify(GDALOpenInfo *poOpenInfo) if (poOpenInfo->fpL == nullptr || poOpenInfo->nHeaderBytes == 0) return GDAL_IDENTIFY_FALSE; - if (strstr((const char *)poOpenInfo->pabyHeader, "(poOpenInfo->pabyHeader), + " #include #include #include @@ -56,8 +57,11 @@ #include "osm_parser.h" #include "sqlite3.h" -constexpr int SWITCH_THRESHOLD = 10000; -constexpr int MAX_THRESHOLD = 100000; +#undef SQLITE_TRANSIENT +#define SQLITE_TRANSIENT reinterpret_cast(-1) + +constexpr size_t SWITCH_THRESHOLD = 10000; +constexpr size_t MAX_THRESHOLD = 100000; /************************************************************************/ /* OGROSMLayer() */ @@ -90,12 +94,6 @@ OGROSMLayer::~OGROSMLayer() if (m_poSRS) m_poSRS->Release(); - for (int i = 0; i < m_nFeatureArraySize; i++) - { - if (m_papoFeatures[i]) - delete m_papoFeatures[i]; - } - for (int i = 0; i < static_cast(m_apszNames.size()); i++) CPLFree(m_apszNames[i]); @@ -109,8 +107,6 @@ OGROSMLayer::~OGROSMLayer() { sqlite3_finalize(m_oComputedAttributes[i].hStmt); } - - CPLFree(m_papoFeatures); } /************************************************************************/ @@ -131,14 +127,8 @@ void OGROSMLayer::ResetReading() void OGROSMLayer::ForceResetReading() { - for (int i = 0; i < m_nFeatureArraySize; i++) - { - if (m_papoFeatures[i]) - delete m_papoFeatures[i]; - } + m_apoFeatures.clear(); m_nFeatureArrayIndex = 0; - m_nFeatureArraySize = 0; - m_nFeatureCount = 0; m_bResetReadingAllowed = false; } @@ -208,7 +198,7 @@ OGRFeature *OGROSMLayer::MyGetNextFeature(OGROSMLayer **ppoNewCurLayer, *ppoNewCurLayer = m_poDS->GetCurrentLayer(); m_bResetReadingAllowed = true; - if (m_nFeatureArraySize == 0) + if (m_apoFeatures.empty()) { if (m_poDS->IsInterleavedReading()) { @@ -225,15 +215,15 @@ OGRFeature *OGROSMLayer::MyGetNextFeature(OGROSMLayer **ppoNewCurLayer, // force a switch to that layer, so that it gets emptied. for (int i = 0; i < m_poDS->GetLayerCount(); i++) { - if (m_poDS->m_papoLayers[i] != this && - m_poDS->m_papoLayers[i]->m_nFeatureArraySize > + if (m_poDS->m_apoLayers[i].get() != this && + m_poDS->m_apoLayers[i]->m_apoFeatures.size() > SWITCH_THRESHOLD) { - *ppoNewCurLayer = m_poDS->m_papoLayers[i]; + *ppoNewCurLayer = m_poDS->m_apoLayers[i].get(); CPLDebug("OSM", "Switching to '%s' as they are too many " "features in '%s'", - m_poDS->m_papoLayers[i]->GetName(), GetName()); + m_poDS->m_apoLayers[i]->GetName(), GetName()); return nullptr; } } @@ -241,21 +231,21 @@ OGRFeature *OGROSMLayer::MyGetNextFeature(OGROSMLayer **ppoNewCurLayer, // Read some more data and accumulate features. m_poDS->ParseNextChunk(m_nIdxLayer, pfnProgress, pProgressData); - if (m_nFeatureArraySize == 0) + if (m_apoFeatures.empty()) { // If there are really no more features to read in the // current layer, force a switch to another non-empty layer. for (int i = 0; i < m_poDS->GetLayerCount(); i++) { - if (m_poDS->m_papoLayers[i] != this && - m_poDS->m_papoLayers[i]->m_nFeatureArraySize > 0) + if (m_poDS->m_apoLayers[i].get() != this && + !m_poDS->m_apoLayers[i]->m_apoFeatures.empty()) { - *ppoNewCurLayer = m_poDS->m_papoLayers[i]; + *ppoNewCurLayer = m_poDS->m_apoLayers[i].get(); CPLDebug("OSM", "Switching to '%s' as they are " "no more feature in '%s'", - m_poDS->m_papoLayers[i]->GetName(), GetName()); + m_poDS->m_apoLayers[i]->GetName(), GetName()); return nullptr; } } @@ -272,7 +262,7 @@ OGRFeature *OGROSMLayer::MyGetNextFeature(OGROSMLayer **ppoNewCurLayer, int bRet = m_poDS->ParseNextChunk(m_nIdxLayer, nullptr, nullptr); // cppcheck-suppress knownConditionTrueFalse - if (m_nFeatureArraySize != 0) + if (!m_apoFeatures.empty()) break; if (bRet == FALSE) return nullptr; @@ -280,15 +270,16 @@ OGRFeature *OGROSMLayer::MyGetNextFeature(OGROSMLayer **ppoNewCurLayer, } } - OGRFeature *poFeature = m_papoFeatures[m_nFeatureArrayIndex]; - - m_papoFeatures[m_nFeatureArrayIndex] = nullptr; + auto poFeature = std::move(m_apoFeatures[m_nFeatureArrayIndex]); m_nFeatureArrayIndex++; - if (m_nFeatureArrayIndex == m_nFeatureArraySize) - m_nFeatureArrayIndex = m_nFeatureArraySize = 0; + if (m_nFeatureArrayIndex == m_apoFeatures.size()) + { + m_nFeatureArrayIndex = 0; + m_apoFeatures.clear(); + } - return poFeature; + return poFeature.release(); } /************************************************************************/ @@ -311,9 +302,10 @@ int OGROSMLayer::TestCapability(const char *pszCap) /* AddToArray() */ /************************************************************************/ -bool OGROSMLayer::AddToArray(OGRFeature *poFeature, int bCheckFeatureThreshold) +bool OGROSMLayer::AddToArray(std::unique_ptr poFeature, + bool bCheckFeatureThreshold) { - if (bCheckFeatureThreshold && m_nFeatureArraySize > MAX_THRESHOLD) + if (bCheckFeatureThreshold && m_apoFeatures.size() > MAX_THRESHOLD) { if (!m_bHasWarnedTooManyFeatures) { @@ -330,25 +322,18 @@ bool OGROSMLayer::AddToArray(OGRFeature *poFeature, int bCheckFeatureThreshold) return false; } - if (m_nFeatureArraySize == m_nFeatureArrayMaxSize) + try { - m_nFeatureArrayMaxSize = - m_nFeatureArrayMaxSize + m_nFeatureArrayMaxSize / 2 + 128; - CPLDebug("OSM", "For layer %s, new max size is %d", GetName(), - m_nFeatureArrayMaxSize); - OGRFeature **papoNewFeatures = - static_cast(VSI_REALLOC_VERBOSE( - m_papoFeatures, m_nFeatureArrayMaxSize * sizeof(OGRFeature *))); - if (papoNewFeatures == nullptr) - { - CPLError(CE_Failure, CPLE_AppDefined, - "For layer %s, cannot resize feature array to %d features", - GetName(), m_nFeatureArrayMaxSize); - return false; - } - m_papoFeatures = papoNewFeatures; + m_apoFeatures.push_back(std::move(poFeature)); + } + catch (const std::exception &) + { + CPLError(CE_Failure, CPLE_OutOfMemory, + "For layer %s, cannot resize feature array to %" PRIu64 + " features", + GetName(), static_cast(m_apoFeatures.size()) + 1); + return false; } - m_papoFeatures[m_nFeatureArraySize++] = poFeature; return true; } @@ -366,44 +351,40 @@ int OGROSMLayer::EvaluateAttributeFilter(OGRFeature *poFeature) /* AddFeature() */ /************************************************************************/ -int OGROSMLayer::AddFeature(OGRFeature *poFeature, - int bAttrFilterAlreadyEvaluated, int *pbFilteredOut, - int bCheckFeatureThreshold) +bool OGROSMLayer::AddFeature(std::unique_ptr poFeature, + bool bAttrFilterAlreadyEvaluated, + bool *pbFilteredOut, bool bCheckFeatureThreshold) { if (!m_bUserInterested) { if (pbFilteredOut) - *pbFilteredOut = TRUE; - delete poFeature; - return TRUE; + *pbFilteredOut = true; + return true; } OGRGeometry *poGeom = poFeature->GetGeometryRef(); if (poGeom) poGeom->assignSpatialReference(m_poSRS); - if ((m_poFilterGeom == nullptr || - FilterGeometry(poFeature->GetGeometryRef())) && + if ((m_poFilterGeom == nullptr || FilterGeometry(poGeom)) && (m_poAttrQuery == nullptr || bAttrFilterAlreadyEvaluated || - m_poAttrQuery->Evaluate(poFeature))) + m_poAttrQuery->Evaluate(poFeature.get()))) { - if (!AddToArray(poFeature, bCheckFeatureThreshold)) + if (!AddToArray(std::move(poFeature), bCheckFeatureThreshold)) { - delete poFeature; - return FALSE; + return false; } } else { if (pbFilteredOut) - *pbFilteredOut = TRUE; - delete poFeature; - return TRUE; + *pbFilteredOut = true; + return true; } if (pbFilteredOut) - *pbFilteredOut = FALSE; - return TRUE; + *pbFilteredOut = false; + return true; } /************************************************************************/ @@ -479,8 +460,7 @@ void OGROSMLayer::AddField(const char *pszName, OGRFieldType eFieldType, int OGROSMLayer::GetFieldIndex(const char *pszName) { - std::map::iterator oIter = - m_oMapFieldNameToIndex.find(pszName); + const auto oIter = m_oMapFieldNameToIndex.find(pszName); if (oIter != m_oMapFieldNameToIndex.end()) return oIter->second; @@ -497,7 +477,7 @@ int OGROSMLayer::AddInOtherOrAllTags(const char *pszK) if (aoSetIgnoreKeys.find(pszK) == aoSetIgnoreKeys.end()) { - char *pszColon = strchr((char *)pszK, ':'); + char *pszColon = strchr(const_cast(pszK), ':'); if (pszColon) { char chBackup = pszColon[1]; @@ -655,7 +635,7 @@ void OGROSMLayer::SetFieldsFromTags(OGRFeature *poFeature, GIntBig nID, } if (m_bHasChangeset) { - poFeature->SetField("osm_changeset", (int)psInfo->nChangeset); + poFeature->SetField("osm_changeset", psInfo->nChangeset); } m_osAllTagsBuffer.clear(); @@ -929,8 +909,8 @@ void OGROSMLayer::SetFieldsFromTags(OGRFeature *poFeature, GIntBig nID, { case SQLITE_INTEGER: poFeature->SetField( - oAttr.nIndex, - (GIntBig)sqlite3_column_int64(oAttr.hStmt, 0)); + oAttr.nIndex, static_cast(sqlite3_column_int64( + oAttr.hStmt, 0))); break; case SQLITE_FLOAT: poFeature->SetField(oAttr.nIndex, @@ -938,8 +918,8 @@ void OGROSMLayer::SetFieldsFromTags(OGRFeature *poFeature, GIntBig nID, break; case SQLITE_TEXT: poFeature->SetField( - oAttr.nIndex, - (const char *)sqlite3_column_text(oAttr.hStmt, 0)); + oAttr.nIndex, reinterpret_cast( + sqlite3_column_text(oAttr.hStmt, 0))); break; default: break; diff --git a/ogr/ogrsf_frmts/osm/osm_parser.cpp b/ogr/ogrsf_frmts/osm/osm_parser.cpp index cb333d27fe8b..ec44919bfa59 100644 --- a/ogr/ogrsf_frmts/osm/osm_parser.cpp +++ b/ogr/ogrsf_frmts/osm/osm_parser.cpp @@ -461,22 +461,23 @@ constexpr int READSTRINGTABLE_IDX_STRING = 1; static bool ReadStringTable(const GByte *pabyData, const GByte *pabyDataLimit, OSMContext *psCtxt) { - char *pszStrBuf = (char *)pabyData; + const GByte *const pabyDataStart = pabyData; unsigned int nStrCount = 0; int *panStrOff = psCtxt->panStrOff; - psCtxt->pszStrBuf = pszStrBuf; + psCtxt->pszStrBuf = reinterpret_cast(const_cast(pabyData)); try { - if ((unsigned int)(pabyDataLimit - pabyData) > psCtxt->nStrAllocated) + if (static_cast(pabyDataLimit - pabyData) > + psCtxt->nStrAllocated) { psCtxt->nStrAllocated = std::max(psCtxt->nStrAllocated * 2, - (unsigned int)(pabyDataLimit - pabyData)); - int *panStrOffNew = (int *)VSI_REALLOC_VERBOSE( - panStrOff, psCtxt->nStrAllocated * sizeof(int)); + static_cast(pabyDataLimit - pabyData)); + int *panStrOffNew = static_cast(VSI_REALLOC_VERBOSE( + panStrOff, psCtxt->nStrAllocated * sizeof(int))); if (panStrOffNew == nullptr) THROW_OSM_PARSING_EXCEPTION; panStrOff = panStrOffNew; @@ -493,7 +494,7 @@ static bool ReadStringTable(const GByte *pabyData, const GByte *pabyDataLimit, READ_SIZE(pabyData, pabyDataLimit, nDataLength); panStrOff[nStrCount++] = - static_cast(pabyData - (GByte *)pszStrBuf); + static_cast(pabyData - pabyDataStart); GByte *pbSaved = const_cast(&pabyData[nDataLength]); pabyData += nDataLength; @@ -614,9 +615,10 @@ static bool ReadDenseNodes(const GByte *pabyData, const GByte *pabyDataLimit, { psCtxt->nNodesAllocated = std::max(psCtxt->nNodesAllocated * 2, nSize); - OSMNode *pasNodesNew = (OSMNode *)VSI_REALLOC_VERBOSE( - psCtxt->pasNodes, - psCtxt->nNodesAllocated * sizeof(OSMNode)); + OSMNode *pasNodesNew = + static_cast(VSI_REALLOC_VERBOSE( + psCtxt->pasNodes, + psCtxt->nNodesAllocated * sizeof(OSMNode))); if (pasNodesNew == nullptr) THROW_OSM_PARSING_EXCEPTION; psCtxt->pasNodes = pasNodesNew; @@ -693,9 +695,10 @@ static bool ReadDenseNodes(const GByte *pabyData, const GByte *pabyDataLimit, psCtxt->nTagsAllocated = std::max(psCtxt->nTagsAllocated * 2, nMaxTags); - OSMTag *pasTagsNew = (OSMTag *)VSI_REALLOC_VERBOSE( - psCtxt->pasTags, - psCtxt->nTagsAllocated * sizeof(OSMTag)); + OSMTag *pasTagsNew = + static_cast(VSI_REALLOC_VERBOSE( + psCtxt->pasTags, + psCtxt->nTagsAllocated * sizeof(OSMTag))); if (pasTagsNew == nullptr) THROW_OSM_PARSING_EXCEPTION; psCtxt->pasTags = pasTagsNew; @@ -820,11 +823,13 @@ static bool ReadDenseNodes(const GByte *pabyData, const GByte *pabyDataLimit, pasNodes[nNodes].nID = nID; pasNodes[nNodes].dfLat = - .000000001 * (psCtxt->nLatOffset + - ((double)psCtxt->nGranularity * nLat)); + .000000001 * + (psCtxt->nLatOffset + + (static_cast(psCtxt->nGranularity) * nLat)); pasNodes[nNodes].dfLon = - .000000001 * (psCtxt->nLonOffset + - ((double)psCtxt->nGranularity * nLon)); + .000000001 * + (psCtxt->nLonOffset + + (static_cast(psCtxt->nGranularity) * nLon)); if (pasNodes[nNodes].dfLon < -180 || pasNodes[nNodes].dfLon > 180 || pasNodes[nNodes].dfLat < -90 || pasNodes[nNodes].dfLat > 90) @@ -974,16 +979,18 @@ static bool ReadNode(const GByte *pabyData, const GByte *pabyDataLimit, GIntBig nLat = 0; READ_VARSINT64_NOCHECK(pabyData, pabyDataLimit, nLat); sNode.dfLat = - 0.000000001 * (psCtxt->nLatOffset + - ((double)psCtxt->nGranularity * nLat)); + 0.000000001 * + (psCtxt->nLatOffset + + (static_cast(psCtxt->nGranularity) * nLat)); } else if (nKey == MAKE_KEY(NODE_IDX_LON, WT_VARINT)) { GIntBig nLon = 0; READ_VARSINT64_NOCHECK(pabyData, pabyDataLimit, nLon); sNode.dfLon = - 0.000000001 * (psCtxt->nLonOffset + - ((double)psCtxt->nGranularity * nLon)); + 0.000000001 * + (psCtxt->nLonOffset + + (static_cast(psCtxt->nGranularity) * nLon)); } else if (nKey == MAKE_KEY(NODE_IDX_KEYS, WT_DATA)) { @@ -997,9 +1004,10 @@ static bool ReadNode(const GByte *pabyData, const GByte *pabyDataLimit, { psCtxt->nTagsAllocated = std::max(psCtxt->nTagsAllocated * 2, nSize); - OSMTag *pasTagsNew = (OSMTag *)VSI_REALLOC_VERBOSE( - psCtxt->pasTags, - psCtxt->nTagsAllocated * sizeof(OSMTag)); + OSMTag *pasTagsNew = + static_cast(VSI_REALLOC_VERBOSE( + psCtxt->pasTags, + psCtxt->nTagsAllocated * sizeof(OSMTag))); if (pasTagsNew == nullptr) THROW_OSM_PARSING_EXCEPTION; psCtxt->pasTags = pasTagsNew; @@ -1124,9 +1132,10 @@ static bool ReadWay(const GByte *pabyData, const GByte *pabyDataLimit, { psCtxt->nTagsAllocated = std::max(psCtxt->nTagsAllocated * 2, nSize); - OSMTag *pasTagsNew = (OSMTag *)VSI_REALLOC_VERBOSE( - psCtxt->pasTags, - psCtxt->nTagsAllocated * sizeof(OSMTag)); + OSMTag *pasTagsNew = + static_cast(VSI_REALLOC_VERBOSE( + psCtxt->pasTags, + psCtxt->nTagsAllocated * sizeof(OSMTag))); if (pasTagsNew == nullptr) THROW_OSM_PARSING_EXCEPTION; psCtxt->pasTags = pasTagsNew; @@ -1194,9 +1203,10 @@ static bool ReadWay(const GByte *pabyData, const GByte *pabyDataLimit, { psCtxt->nNodeRefsAllocated = std::max(psCtxt->nNodeRefsAllocated * 2, nSize); - GIntBig *panNodeRefsNew = (GIntBig *)VSI_REALLOC_VERBOSE( - psCtxt->panNodeRefs, - psCtxt->nNodeRefsAllocated * sizeof(GIntBig)); + GIntBig *panNodeRefsNew = + static_cast(VSI_REALLOC_VERBOSE( + psCtxt->panNodeRefs, + psCtxt->nNodeRefsAllocated * sizeof(GIntBig))); if (panNodeRefsNew == nullptr) THROW_OSM_PARSING_EXCEPTION; psCtxt->panNodeRefs = panNodeRefsNew; @@ -1286,9 +1296,10 @@ static bool ReadRelation(const GByte *pabyData, const GByte *pabyDataLimit, { psCtxt->nTagsAllocated = std::max(psCtxt->nTagsAllocated * 2, nSize); - OSMTag *pasTagsNew = (OSMTag *)VSI_REALLOC_VERBOSE( - psCtxt->pasTags, - psCtxt->nTagsAllocated * sizeof(OSMTag)); + OSMTag *pasTagsNew = + static_cast(VSI_REALLOC_VERBOSE( + psCtxt->pasTags, + psCtxt->nTagsAllocated * sizeof(OSMTag))); if (pasTagsNew == nullptr) THROW_OSM_PARSING_EXCEPTION; psCtxt->pasTags = pasTagsNew; @@ -1355,9 +1366,10 @@ static bool ReadRelation(const GByte *pabyData, const GByte *pabyDataLimit, { psCtxt->nMembersAllocated = std::max(psCtxt->nMembersAllocated * 2, nSize); - OSMMember *pasMembersNew = (OSMMember *)VSI_REALLOC_VERBOSE( - psCtxt->pasMembers, - psCtxt->nMembersAllocated * sizeof(OSMMember)); + OSMMember *pasMembersNew = + static_cast(VSI_REALLOC_VERBOSE( + psCtxt->pasMembers, + psCtxt->nMembersAllocated * sizeof(OSMMember))); if (pasMembersNew == nullptr) THROW_OSM_PARSING_EXCEPTION; psCtxt->pasMembers = pasMembersNew; @@ -1416,7 +1428,8 @@ static bool ReadRelation(const GByte *pabyData, const GByte *pabyDataLimit, if (nType > MEMBER_RELATION) THROW_OSM_PARSING_EXCEPTION; - psCtxt->pasMembers[nIter].eType = (OSMMemberType)nType; + psCtxt->pasMembers[nIter].eType = + static_cast(nType); } pabyData += nSize; } @@ -1832,9 +1845,10 @@ static bool ReadBlob(OSMContext *psCtxt, BlobType eType) if (psCtxt->nUncompressedAllocated > UINT_MAX - EXTRA_BYTES) THROW_OSM_PARSING_EXCEPTION; - pabyUncompressedNew = (GByte *)VSI_REALLOC_VERBOSE( - psCtxt->pabyUncompressed, - psCtxt->nUncompressedAllocated + EXTRA_BYTES); + pabyUncompressedNew = + static_cast(VSI_REALLOC_VERBOSE( + psCtxt->pabyUncompressed, + psCtxt->nUncompressedAllocated + EXTRA_BYTES)); if (pabyUncompressedNew == nullptr) THROW_OSM_PARSING_EXCEPTION; psCtxt->pabyUncompressed = pabyUncompressedNew; @@ -2101,7 +2115,7 @@ static void EmptyNotifyBoundsFunc(double /* dfXMin */, double /* dfYMin */, static const char *OSM_AddString(OSMContext *psCtxt, const char *pszStr) { - int nLen = (int)strlen(pszStr); + const auto nLen = strlen(pszStr); if (psCtxt->nStrLength + nLen + 1 > psCtxt->nStrAllocated) { CPLError(CE_Failure, CPLE_AppDefined, "String buffer too small"); @@ -2110,7 +2124,7 @@ static const char *OSM_AddString(OSMContext *psCtxt, const char *pszStr) char *pszRet = psCtxt->pszStrBuf + psCtxt->nStrLength; memcpy(pszRet, pszStr, nLen); pszRet[nLen] = '\0'; - psCtxt->nStrLength += nLen + 1; + psCtxt->nStrLength += static_cast(nLen) + 1; return pszRet; } @@ -2131,7 +2145,7 @@ static void XMLCALL OSM_XML_startElementCbk(void *pUserData, const char *pszName, const char **ppszAttr) { - OSMContext *psCtxt = (OSMContext *)pUserData; + OSMContext *psCtxt = static_cast(pUserData); const char **ppszIter = ppszAttr; if (psCtxt->bStopParsing) @@ -2384,8 +2398,9 @@ static void XMLCALL OSM_XML_startElementCbk(void *pUserData, { int nMembersAllocated = std::max(psCtxt->nMembersAllocated * 2, psCtxt->sRelation.nMembers + 1); - OSMMember *pasMembersNew = (OSMMember *)VSI_REALLOC_VERBOSE( - psCtxt->pasMembers, nMembersAllocated * sizeof(OSMMember)); + OSMMember *pasMembersNew = + static_cast(VSI_REALLOC_VERBOSE( + psCtxt->pasMembers, nMembersAllocated * sizeof(OSMMember))); if (pasMembersNew == nullptr) { CPLError(CE_Failure, CPLE_AppDefined, @@ -2436,8 +2451,8 @@ static void XMLCALL OSM_XML_startElementCbk(void *pUserData, if (psCtxt->nTags == psCtxt->nTagsAllocated) { psCtxt->nTagsAllocated = psCtxt->nTagsAllocated * 2; - OSMTag *pasTagsNew = (OSMTag *)VSI_REALLOC_VERBOSE( - psCtxt->pasTags, psCtxt->nTagsAllocated * sizeof(OSMTag)); + OSMTag *pasTagsNew = static_cast(VSI_REALLOC_VERBOSE( + psCtxt->pasTags, psCtxt->nTagsAllocated * sizeof(OSMTag))); if (pasTagsNew == nullptr) { if (psCtxt->bInNode) @@ -2487,7 +2502,7 @@ static void XMLCALL OSM_XML_startElementCbk(void *pUserData, static void XMLCALL OSM_XML_endElementCbk(void *pUserData, const char *pszName) { - OSMContext *psCtxt = (OSMContext *)pUserData; + OSMContext *psCtxt = static_cast(pUserData); if (psCtxt->bStopParsing) return; @@ -2568,7 +2583,7 @@ static void XMLCALL OSM_XML_dataHandlerCbk(void *pUserData, { CPLError(CE_Failure, CPLE_AppDefined, "File probably corrupted (million laugh pattern)"); - XML_StopParser(psCtxt->hXMLParser, XML_FALSE); + XML_StopParser(psCtxt->hXMLParser, false); psCtxt->bStopParsing = true; return; } @@ -2592,24 +2607,27 @@ static OSMRetCode XML_ProcessBlock(OSMContext *psCtxt) { psCtxt->nDataHandlerCounter = 0; - const unsigned int nLen = (unsigned int)VSIFReadL( - psCtxt->pabyBlob, 1, XML_BUFSIZE, psCtxt->fp); + const unsigned int nLen = static_cast( + VSIFReadL(psCtxt->pabyBlob, 1, XML_BUFSIZE, psCtxt->fp)); psCtxt->nBytesRead += nLen; psCtxt->bEOF = nLen < XML_BUFSIZE; const int eErr = - XML_Parse(psCtxt->hXMLParser, (const char *)psCtxt->pabyBlob, nLen, + XML_Parse(psCtxt->hXMLParser, + reinterpret_cast(psCtxt->pabyBlob), nLen, psCtxt->bEOF); if (eErr == XML_STATUS_ERROR) { - CPLError(CE_Failure, CPLE_AppDefined, - "XML parsing of OSM file failed : %s " - "at line %d, column %d", - XML_ErrorString(XML_GetErrorCode(psCtxt->hXMLParser)), - (int)XML_GetCurrentLineNumber(psCtxt->hXMLParser), - (int)XML_GetCurrentColumnNumber(psCtxt->hXMLParser)); + CPLError( + CE_Failure, CPLE_AppDefined, + "XML parsing of OSM file failed : %s " + "at line %d, column %d", + XML_ErrorString(XML_GetErrorCode(psCtxt->hXMLParser)), + static_cast(XML_GetCurrentLineNumber(psCtxt->hXMLParser)), + static_cast( + XML_GetCurrentColumnNumber(psCtxt->hXMLParser))); psCtxt->bStopParsing = true; } psCtxt->nWithoutEventCounter++; @@ -2649,7 +2667,7 @@ OSMContext *OSM_Open(const char *pszFilename, NotifyNodesFunc pfnNotifyNodes, bool bPBF = false; - if (strstr((const char *)abyHeader, "(abyHeader), "nBlobSizeAllocated = XML_BUFSIZE; psCtxt->nStrAllocated = 1024 * 1024; - psCtxt->pszStrBuf = (char *)VSI_MALLOC_VERBOSE(psCtxt->nStrAllocated); + psCtxt->pszStrBuf = + static_cast(VSI_MALLOC_VERBOSE(psCtxt->nStrAllocated)); if (psCtxt->pszStrBuf) psCtxt->pszStrBuf[0] = '\0'; @@ -2726,23 +2745,23 @@ OSMContext *OSM_Open(const char *pszFilename, NotifyNodesFunc pfnNotifyNodes, psCtxt->bTryToFetchBounds = true; psCtxt->nNodesAllocated = 1; - psCtxt->pasNodes = (OSMNode *)VSI_MALLOC_VERBOSE( - sizeof(OSMNode) * psCtxt->nNodesAllocated); + psCtxt->pasNodes = static_cast( + VSI_MALLOC_VERBOSE(sizeof(OSMNode) * psCtxt->nNodesAllocated)); psCtxt->nTagsAllocated = 256; - psCtxt->pasTags = (OSMTag *)VSI_MALLOC_VERBOSE(sizeof(OSMTag) * - psCtxt->nTagsAllocated); + psCtxt->pasTags = static_cast( + VSI_MALLOC_VERBOSE(sizeof(OSMTag) * psCtxt->nTagsAllocated)); /* 300 is the recommended value, but there are files with more than 2000 * so we should be able */ /* to realloc over that value */ psCtxt->nMembersAllocated = 2000; - psCtxt->pasMembers = (OSMMember *)VSI_MALLOC_VERBOSE( - sizeof(OSMMember) * psCtxt->nMembersAllocated); + psCtxt->pasMembers = static_cast( + VSI_MALLOC_VERBOSE(sizeof(OSMMember) * psCtxt->nMembersAllocated)); psCtxt->nNodeRefsAllocated = 10000; - psCtxt->panNodeRefs = (GIntBig *)VSI_MALLOC_VERBOSE( - sizeof(GIntBig) * psCtxt->nNodeRefsAllocated); + psCtxt->panNodeRefs = static_cast( + VSI_MALLOC_VERBOSE(sizeof(GIntBig) * psCtxt->nNodeRefsAllocated)); if (psCtxt->pszStrBuf == nullptr || psCtxt->pasNodes == nullptr || psCtxt->pasTags == nullptr || psCtxt->pasMembers == nullptr || @@ -2754,14 +2773,15 @@ OSMContext *OSM_Open(const char *pszFilename, NotifyNodesFunc pfnNotifyNodes, } #endif - psCtxt->pabyBlob = (GByte *)VSI_MALLOC_VERBOSE(psCtxt->nBlobSizeAllocated); + psCtxt->pabyBlob = + static_cast(VSI_MALLOC_VERBOSE(psCtxt->nBlobSizeAllocated)); if (psCtxt->pabyBlob == nullptr) { OSM_Close(psCtxt); return nullptr; } - psCtxt->pabyBlobHeader = - (GByte *)VSI_MALLOC_VERBOSE(MAX_BLOB_HEADER_SIZE + EXTRA_BYTES); + psCtxt->pabyBlobHeader = static_cast( + VSI_MALLOC_VERBOSE(MAX_BLOB_HEADER_SIZE + EXTRA_BYTES)); if (psCtxt->pabyBlobHeader == nullptr) { OSM_Close(psCtxt); From 62fc992460537a55a4aa512c7fcc18f7fe522417 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 23 Jun 2024 23:50:40 +0200 Subject: [PATCH 0236/1119] Fix number of new Coverity Scan warnings, likely due to new version of the tool --- alg/gdalchecksum.cpp | 7 +- alg/gdalrasterize.cpp | 7 +- alg/gdalwarpkernel.cpp | 5 +- alg/internal_libqhull/poly_r.c | 17 ++- apps/argparse/argparse.hpp | 6 +- apps/gdalargumentparser.cpp | 8 +- autotest/cpp/test_gdal.cpp | 2 +- frmts/grib/degrib/degrib/clock.c | 1 + frmts/gsg/gsbgdataset.cpp | 32 ++--- frmts/gtiff/gt_overview.cpp | 7 +- frmts/gtiff/gtiffdataset_read.cpp | 3 +- frmts/gtiff/gtiffdataset_write.cpp | 7 +- frmts/gtiff/gtiffrasterband_read.cpp | 4 + frmts/mrf/marfa.h | 6 +- frmts/netcdf/netcdfdataset.cpp | 122 +++++++++++------- frmts/northwood/northwood.cpp | 2 + frmts/pdf/pdfdataset.cpp | 16 ++- gcore/gdaljp2structure.cpp | 4 +- gcore/gdalmultidim.cpp | 1 + ogr/ogr_p.h | 3 +- ogr/ogrlinestring.cpp | 28 ++-- .../arrow_common/ograrrowlayer.hpp | 2 +- ogr/ogrsf_frmts/geojson/libjson/json_util.c | 4 +- ogr/ogrsf_frmts/gmlas/ogrgmlaslayer.cpp | 2 +- .../gpkg/ogrgeopackagetablelayer.cpp | 11 +- ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp | 45 ++++--- ogr/ogrsf_frmts/ntf/ntfrecord.cpp | 1 + ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp | 20 ++- .../openfilegdb/filegdbtable_freelist.cpp | 3 + ogr/ogrsf_frmts/osm/ogrosmdatasource.cpp | 1 + ogr/ogrsf_frmts/pmtiles/pmtiles/pmtiles.hpp | 3 + ogr/ogrsf_frmts/shape/ogrshape.h | 1 + ogr/ogrsf_frmts/shape/ogrshapedatasource.cpp | 7 +- ogr/ogrsf_frmts/vfk/vfkreader.cpp | 4 +- ogr/ogrsf_frmts/vfk/vfkreader.h | 2 +- ogr/ogrsf_frmts/vfk/vfkreaderp.h | 4 +- ogr/ogrsf_frmts/vfk/vfkreadersqlite.cpp | 6 +- ogr/ogrsf_frmts/vrt/ogrvrtdriver.cpp | 11 +- ogr/ogrsf_frmts/wfs/ogrwfsdatasource.cpp | 1 + port/cpl_md5.cpp | 1 + port/cpl_spawn.cpp | 8 +- port/cpl_vsi_mem.cpp | 101 +++++++++------ port/cpl_vsil_s3.cpp | 1 + port/cpl_vsisimple.cpp | 6 +- port/cpl_worker_thread_pool.cpp | 39 +++++- port/cpl_worker_thread_pool.h | 7 +- 46 files changed, 374 insertions(+), 205 deletions(-) diff --git a/alg/gdalchecksum.cpp b/alg/gdalchecksum.cpp index e0f0dcafacf8..64f6158433b8 100644 --- a/alg/gdalchecksum.cpp +++ b/alg/gdalchecksum.cpp @@ -80,12 +80,9 @@ int CPL_STDCALL GDALChecksumImage(GDALRasterBandH hBand, int nXOff, int nYOff, const auto IntFromDouble = [](double dfVal) { int nVal; - if (CPLIsNan(dfVal) || CPLIsInf(dfVal)) + if (!std::isfinite(dfVal)) { - // Most compilers seem to cast NaN or Inf to 0x80000000. - // but VC7 is an exception. So we force the result - // of such a cast. - nVal = 0x80000000; + nVal = INT_MIN; } else { diff --git a/alg/gdalrasterize.cpp b/alg/gdalrasterize.cpp index 16be0bac7c55..6aa6efc998e3 100644 --- a/alg/gdalrasterize.cpp +++ b/alg/gdalrasterize.cpp @@ -1559,11 +1559,8 @@ CPLErr GDALRasterizeLayers(GDALDatasetH hDS, int nBandCount, int *panBandList, if (!(pszYChunkSize && ((nYChunkSize = atoi(pszYChunkSize))) != 0)) { const GIntBig nYChunkSize64 = GDALGetCacheMax64() / nScanlineBytes; - const int knIntMax = std::numeric_limits::max(); - if (nYChunkSize64 > knIntMax) - nYChunkSize = knIntMax; - else - nYChunkSize = static_cast(nYChunkSize64); + nYChunkSize = static_cast( + std::min(nYChunkSize64, std::numeric_limits::max())); } if (nYChunkSize < 1) diff --git a/alg/gdalwarpkernel.cpp b/alg/gdalwarpkernel.cpp index 98b1555c4a1b..51361a45d12c 100644 --- a/alg/gdalwarpkernel.cpp +++ b/alg/gdalwarpkernel.cpp @@ -513,6 +513,7 @@ static CPLErr GWKRun(GDALWarpKernel *poWK, const char *pszFuncName, job.pfnFunc = pfnFunc; } + bool bStopFlag; { std::unique_lock lock(psThreadData->mutex); @@ -550,6 +551,8 @@ static CPLErr GWKRun(GDALWarpKernel *poWK, const char *pszFuncName, } } } + + bStopFlag = psThreadData->stopFlag; } /* -------------------------------------------------------------------- */ @@ -557,7 +560,7 @@ static CPLErr GWKRun(GDALWarpKernel *poWK, const char *pszFuncName, /* -------------------------------------------------------------------- */ psThreadData->poJobQueue->WaitCompletion(); - return psThreadData->stopFlag ? CE_Failure : CE_None; + return bStopFlag ? CE_Failure : CE_None; } /************************************************************************/ diff --git a/alg/internal_libqhull/poly_r.c b/alg/internal_libqhull/poly_r.c index c68596a12bf9..b7e09b54fc8d 100644 --- a/alg/internal_libqhull/poly_r.c +++ b/alg/internal_libqhull/poly_r.c @@ -1137,10 +1137,13 @@ ridgeT *qh_newridge(qhT *qh) { qh_memalloc_(qh, (int)sizeof(ridgeT), freelistp, ridge, ridgeT); memset((char *)ridge, (size_t)0, sizeof(ridgeT)); zinc_(Ztotridges); + ridge->id= qh->ridge_id; if (qh->ridge_id == UINT_MAX) { qh_fprintf(qh, qh->ferr, 7074, "qhull warning: more than 2^32 ridges. Qhull results are OK. Since the ridge ID wraps around to 0, two ridges may have the same identifier.\n"); + qh->ridge_id = 0; + } else { + qh->ridge_id++; } - ridge->id= qh->ridge_id++; trace4((qh, qh->ferr, 4056, "qh_newridge: created ridge r%d\n", ridge->id)); return(ridge); } /* newridge */ @@ -1176,10 +1179,14 @@ int qh_pointid(qhT *qh, pointT *point) { offset= (ptr_intT)(point - qh->first_point); /* coverity[divide_arg] */ id= offset / qh->hull_dim; - }else if ((id= qh_setindex(qh->other_points, point)) != -1) - id += qh->num_points; - else - return qh_IDunknown; + } else { + id = qh_setindex(qh->other_points, point); + if (id >= 0) { + id += qh->num_points; + } else { + return qh_IDunknown; + } + } return (int)id; } /* pointid */ diff --git a/apps/argparse/argparse.hpp b/apps/argparse/argparse.hpp index a29c120e00ab..7b589e7c79c8 100644 --- a/apps/argparse/argparse.hpp +++ b/apps/argparse/argparse.hpp @@ -2041,8 +2041,10 @@ class ArgumentParser { } stream << std::setw(2) << " "; - stream << std::setw(static_cast(longest_arg_length - 2)) - << command; + if (longest_arg_length >= 2) { + stream << std::setw(static_cast(longest_arg_length - 2)) + << command; + } stream << " " << subparser->get().m_description << "\n"; } } diff --git a/apps/gdalargumentparser.cpp b/apps/gdalargumentparser.cpp index c70a939be599..f9992cea7317 100644 --- a/apps/gdalargumentparser.cpp +++ b/apps/gdalargumentparser.cpp @@ -50,10 +50,10 @@ GDALArgumentParser::GDALArgumentParser(const std::string &program_name, add_argument("-h", "--help") .flag() .action( - [this, program_name](const auto &) + [this](const auto &) { std::cout << usage() << std::endl << std::endl; - std::cout << _("Note: ") << program_name + std::cout << _("Note: ") << m_program_name << _(" --long-usage for full help.") << std::endl; std::exit(0); }) @@ -77,11 +77,11 @@ GDALArgumentParser::GDALArgumentParser(const std::string &program_name, .flag() .hidden() .action( - [program_name](const auto &) + [this](const auto &) { printf("%s was compiled against GDAL %s and " "is running against GDAL %s\n", - program_name.c_str(), GDAL_RELEASE_NAME, + m_program_name.c_str(), GDAL_RELEASE_NAME, GDALVersionInfo("RELEASE_NAME")); std::exit(0); }) diff --git a/autotest/cpp/test_gdal.cpp b/autotest/cpp/test_gdal.cpp index 459105bdb9ec..8e8af2c85dd3 100644 --- a/autotest/cpp/test_gdal.cpp +++ b/autotest/cpp/test_gdal.cpp @@ -3413,7 +3413,7 @@ TEST_F(test_gdal, gtiff_ReadCompressedData) CE_None); EXPECT_EQ(nGotSize, nNeededSize); EXPECT_NE(pBuffer, nullptr); - if (pBuffer != nullptr && nGotSize == nNeededSize) + if (pBuffer != nullptr && nGotSize == nNeededSize && nNeededSize >= 2) { const GByte *pabyBuffer = static_cast(pBuffer); EXPECT_EQ(pabyBuffer[0], 0xFF); diff --git a/frmts/grib/degrib/degrib/clock.c b/frmts/grib/degrib/degrib/clock.c index 0f1ea08fcdab..a38543c8df45 100644 --- a/frmts/grib/degrib/degrib/clock.c +++ b/frmts/grib/degrib/degrib/clock.c @@ -729,6 +729,7 @@ sChar Clock_GetTimeZone () #else const struct tm *gmTimePtr = gmtime (&ansTime); #endif + timeZone = 0; if (gmTimePtr) { timeZone = gmTimePtr->tm_hour; diff --git a/frmts/gsg/gsbgdataset.cpp b/frmts/gsg/gsbgdataset.cpp index 2a234236fbb8..41b9d3d25a54 100644 --- a/frmts/gsg/gsbgdataset.cpp +++ b/frmts/gsg/gsbgdataset.cpp @@ -53,7 +53,7 @@ class GSBGDataset final : public GDALPamDataset static const float fNODATA_VALUE; static const size_t nHEADER_SIZE; - static CPLErr WriteHeader(VSILFILE *fp, GInt16 nXSize, GInt16 nYSize, + static CPLErr WriteHeader(VSILFILE *fp, int nXSize, int nYSize, double dfMinX, double dfMaxX, double dfMinY, double dfMaxY, double dfMinZ, double dfMaxZ); @@ -417,9 +417,9 @@ CPLErr GSBGRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void *pImage) if (bHeaderNeedsUpdate && dfMaxZ > dfMinZ) { - CPLErr eErr = poGDS->WriteHeader(poGDS->fp, (GInt16)nRasterXSize, - (GInt16)nRasterYSize, dfMinX, dfMaxX, - dfMinY, dfMaxY, dfMinZ, dfMaxZ); + CPLErr eErr = + poGDS->WriteHeader(poGDS->fp, nRasterXSize, nRasterYSize, dfMinX, + dfMaxX, dfMinY, dfMaxY, dfMinZ, dfMaxZ); return eErr; } @@ -686,9 +686,9 @@ CPLErr GSBGDataset::SetGeoTransform(double *padfGeoTransform) padfGeoTransform[5] * (nRasterYSize - 0.5) + padfGeoTransform[3]; double dfMaxY = padfGeoTransform[3] + padfGeoTransform[5] / 2; - CPLErr eErr = WriteHeader(fp, (GInt16)poGRB->nRasterXSize, - (GInt16)poGRB->nRasterYSize, dfMinX, dfMaxX, - dfMinY, dfMaxY, poGRB->dfMinZ, poGRB->dfMaxZ); + CPLErr eErr = + WriteHeader(fp, poGRB->nRasterXSize, poGRB->nRasterYSize, dfMinX, + dfMaxX, dfMinY, dfMaxY, poGRB->dfMinZ, poGRB->dfMaxZ); if (eErr == CE_None) { @@ -705,7 +705,7 @@ CPLErr GSBGDataset::SetGeoTransform(double *padfGeoTransform) /* WriteHeader() */ /************************************************************************/ -CPLErr GSBGDataset::WriteHeader(VSILFILE *fp, GInt16 nXSize, GInt16 nYSize, +CPLErr GSBGDataset::WriteHeader(VSILFILE *fp, int nXSize, int nYSize, double dfMinX, double dfMaxX, double dfMinY, double dfMaxY, double dfMinZ, double dfMaxZ) @@ -724,7 +724,8 @@ CPLErr GSBGDataset::WriteHeader(VSILFILE *fp, GInt16 nXSize, GInt16 nYSize, return CE_Failure; } - GInt16 nTemp = CPL_LSBWORD16(nXSize); + assert(nXSize >= 0 && nXSize <= std::numeric_limits::max()); + GInt16 nTemp = CPL_LSBWORD16(static_cast(nXSize)); if (VSIFWriteL((void *)&nTemp, 2, 1, fp) != 1) { CPLError(CE_Failure, CPLE_FileIO, @@ -732,7 +733,8 @@ CPLErr GSBGDataset::WriteHeader(VSILFILE *fp, GInt16 nXSize, GInt16 nYSize, return CE_Failure; } - nTemp = CPL_LSBWORD16(nYSize); + assert(nYSize >= 0 && nYSize <= std::numeric_limits::max()); + nTemp = CPL_LSBWORD16(static_cast(nYSize)); if (VSIFWriteL((void *)&nTemp, 2, 1, fp) != 1) { CPLError(CE_Failure, CPLE_FileIO, @@ -847,8 +849,8 @@ GDALDataset *GSBGDataset::Create(const char *pszFilename, int nXSize, return nullptr; } - CPLErr eErr = WriteHeader(fp, (GInt16)nXSize, (GInt16)nYSize, 0.0, nXSize, - 0.0, nYSize, 0.0, 0.0); + CPLErr eErr = + WriteHeader(fp, nXSize, nYSize, 0.0, nXSize, 0.0, nYSize, 0.0, 0.0); if (eErr != CE_None) { VSIFCloseL(fp); @@ -941,8 +943,8 @@ GDALDataset *GSBGDataset::CreateCopy(const char *pszFilename, return nullptr; } - GInt16 nXSize = (GInt16)poSrcBand->GetXSize(); - GInt16 nYSize = (GInt16)poSrcBand->GetYSize(); + const int nXSize = poSrcBand->GetXSize(); + const int nYSize = poSrcBand->GetYSize(); double adfGeoTransform[6]; poSrcDS->GetGeoTransform(adfGeoTransform); @@ -974,7 +976,7 @@ GDALDataset *GSBGDataset::CreateCopy(const char *pszFilename, float fSrcNoDataValue = (float)poSrcBand->GetNoDataValue(&bSrcHasNDValue); double dfMinZ = std::numeric_limits::max(); double dfMaxZ = std::numeric_limits::lowest(); - for (GInt16 iRow = nYSize - 1; iRow >= 0; iRow--) + for (int iRow = nYSize - 1; iRow >= 0; iRow--) { eErr = poSrcBand->RasterIO(GF_Read, 0, iRow, nXSize, 1, pfData, nXSize, 1, GDT_Float32, 0, 0, nullptr); diff --git a/frmts/gtiff/gt_overview.cpp b/frmts/gtiff/gt_overview.cpp index 38ca89e434c2..6405f32261ad 100644 --- a/frmts/gtiff/gt_overview.cpp +++ b/frmts/gtiff/gt_overview.cpp @@ -188,8 +188,11 @@ toff_t GTIFFWriteDirectory(TIFF *hTIFF, int nSubfileType, int nXSize, } TIFFWriteDirectory(hTIFF); - TIFFSetDirectory(hTIFF, - static_cast(TIFFNumberOfDirectories(hTIFF) - 1)); + const tdir_t nNumberOfDirs = TIFFNumberOfDirectories(hTIFF); + if (nNumberOfDirs > 0) // always true, but to please Coverity + { + TIFFSetDirectory(hTIFF, static_cast(nNumberOfDirs - 1)); + } const toff_t nOffset = TIFFCurrentDirOffset(hTIFF); diff --git a/frmts/gtiff/gtiffdataset_read.cpp b/frmts/gtiff/gtiffdataset_read.cpp index 9e602fa2619d..59da8e19c738 100644 --- a/frmts/gtiff/gtiffdataset_read.cpp +++ b/frmts/gtiff/gtiffdataset_read.cpp @@ -106,7 +106,8 @@ int GTiffDataset::GetJPEGOverviewCount() GByte abyFFD8[] = {0xFF, 0xD8}; if (TIFFGetField(m_hTIFF, TIFFTAG_JPEGTABLES, &nJPEGTableSize, &pJPEGTable)) { - if (pJPEGTable == nullptr || nJPEGTableSize > INT_MAX || + if (pJPEGTable == nullptr || nJPEGTableSize < 2 || + nJPEGTableSize > INT_MAX || static_cast(pJPEGTable)[nJPEGTableSize - 1] != 0xD9) { m_nJPEGOverviewCount = 0; diff --git a/frmts/gtiff/gtiffdataset_write.cpp b/frmts/gtiff/gtiffdataset_write.cpp index 062ba5cd1bfc..601ce94e5685 100644 --- a/frmts/gtiff/gtiffdataset_write.cpp +++ b/frmts/gtiff/gtiffdataset_write.cpp @@ -2117,8 +2117,11 @@ void GTiffDataset::Crystalize() } else { - TIFFSetDirectory( - m_hTIFF, static_cast(TIFFNumberOfDirectories(m_hTIFF) - 1)); + const tdir_t nNumberOfDirs = TIFFNumberOfDirectories(m_hTIFF); + if (nNumberOfDirs > 0) + { + TIFFSetDirectory(m_hTIFF, static_cast(nNumberOfDirs - 1)); + } } RestoreVolatileParameters(m_hTIFF); diff --git a/frmts/gtiff/gtiffrasterband_read.cpp b/frmts/gtiff/gtiffrasterband_read.cpp index b16d57b4436a..22b90e51e259 100644 --- a/frmts/gtiff/gtiffrasterband_read.cpp +++ b/frmts/gtiff/gtiffrasterband_read.cpp @@ -32,6 +32,7 @@ #include "gtiffjpegoverviewds.h" #include +#include #include #include #include @@ -509,6 +510,9 @@ CPLVirtualMem *GTiffRasterBand::GetVirtualMemAutoInternal(GDALRWFlag eRWFlag, CPLAssert(panByteCounts[0] == static_cast(nBlockSize)); // Now simulate the writing of other blocks. + assert(nBlocks > 0); + assert(static_cast(nBlockSize) < + std::numeric_limits::max() / nBlocks); const vsi_l_offset nDataSize = static_cast(nBlockSize) * nBlocks; if (VSIFTruncateL(fp, nBaseOffset + nDataSize) != 0) diff --git a/frmts/mrf/marfa.h b/frmts/mrf/marfa.h index 6e0cff3171d2..20c908fff4fc 100644 --- a/frmts/mrf/marfa.h +++ b/frmts/mrf/marfa.h @@ -966,11 +966,11 @@ class LERC_Band final : public MRFRasterBand protected: virtual CPLErr Decompress(buf_mgr &dst, buf_mgr &src) override; virtual CPLErr Compress(buf_mgr &dst, buf_mgr &src) override; - double precision; + double precision = 0; // L1 or L2 - int version; + int version = 0; // L2 version - int l2ver; + int l2ver = 0; // Build a MRF header for a single LERC tile static CPLXMLNode *GetMRFConfig(GDALOpenInfo *poOpenInfo); diff --git a/frmts/netcdf/netcdfdataset.cpp b/frmts/netcdf/netcdfdataset.cpp index 2960870dccf4..ae3dbf3f9e2e 100644 --- a/frmts/netcdf/netcdfdataset.cpp +++ b/frmts/netcdf/netcdfdataset.cpp @@ -7043,7 +7043,9 @@ bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4, int nDimCount = -1; int status = nc_inq_ndims(nOldGrpId, &nDimCount); NCDF_ERR(status); - int *panDimIds = static_cast(CPLMalloc(sizeof(int) * nDimCount)); + if (nDimCount < 0 || nDimCount > NC_MAX_DIMS) + return false; + int anDimIds[NC_MAX_DIMS]; int nUnlimiDimID = -1; status = nc_inq_unlimdim(nOldGrpId, &nUnlimiDimID); NCDF_ERR(status); @@ -7052,21 +7054,21 @@ bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4, // In NC4, the dimension ids of a group are not necessarily in // [0,nDimCount-1] range int nDimCount2 = -1; - status = nc_inq_dimids(nOldGrpId, &nDimCount2, panDimIds, FALSE); + status = nc_inq_dimids(nOldGrpId, &nDimCount2, anDimIds, FALSE); NCDF_ERR(status); CPLAssert(nDimCount == nDimCount2); } else { for (int i = 0; i < nDimCount; i++) - panDimIds[i] = i; + anDimIds[i] = i; } for (int i = 0; i < nDimCount; i++) { char szDimName[NC_MAX_NAME + 1]; szDimName[0] = 0; size_t nLen = 0; - const int nDimId = panDimIds[i]; + const int nDimId = anDimIds[i]; status = nc_inq_dim(nOldGrpId, nDimId, szDimName, &nLen); NCDF_ERR(status); if (NCDFIsUnlimitedDim(bIsNC4, nOldGrpId, nDimId)) @@ -7079,11 +7081,9 @@ bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4, CPLAssert(nDimId == nNewDimId); if (status != NC_NOERR) { - CPLFree(panDimIds); return false; } } - CPLFree(panDimIds); // Clone main attributes if (!CloneAttributes(nOldGrpId, nNewGrpId, NC_GLOBAL, NC_GLOBAL)) @@ -7108,7 +7108,6 @@ bool netCDFDataset::CloneGrp(int nOldGrpId, int nNewGrpId, bool bIsNC4, int nVarDimCount = -1; status = nc_inq_varndims(nOldGrpId, i, &nVarDimCount); NCDF_ERR(status); - int anDimIds[NC_MAX_DIMS]; status = nc_inq_vardimid(nOldGrpId, i, anDimIds); NCDF_ERR(status); int nNewVarId = -1; @@ -10379,7 +10378,7 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, NCDFSafeStrcat(&pszAttrValue, "{", &nAttrValueSize); double dfValue = 0.0; - size_t m; + size_t m = 0; char szTemp[256]; bool bSetDoubleFromStr = false; @@ -10398,10 +10397,13 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, CPLCalloc(nAttrLen, sizeof(signed char))); nc_get_att_schar(nCdfId, nVarId, pszAttrName, pscTemp); dfValue = static_cast(pscTemp[0]); - for (m = 0; m < nAttrLen - 1; m++) + if (nAttrLen > 1) { - snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]); - NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + for (m = 0; m < nAttrLen - 1; m++) + { + snprintf(szTemp, sizeof(szTemp), "%d,", pscTemp[m]); + NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + } } snprintf(szTemp, sizeof(szTemp), "%d", pscTemp[m]); NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); @@ -10414,10 +10416,13 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, static_cast(CPLCalloc(nAttrLen, sizeof(short))); nc_get_att_short(nCdfId, nVarId, pszAttrName, psTemp); dfValue = static_cast(psTemp[0]); - for (m = 0; m < nAttrLen - 1; m++) + if (nAttrLen > 1) { - snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]); - NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + for (m = 0; m < nAttrLen - 1; m++) + { + snprintf(szTemp, sizeof(szTemp), "%d,", psTemp[m]); + NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + } } snprintf(szTemp, sizeof(szTemp), "%d", psTemp[m]); NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); @@ -10429,10 +10434,13 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, int *pnTemp = static_cast(CPLCalloc(nAttrLen, sizeof(int))); nc_get_att_int(nCdfId, nVarId, pszAttrName, pnTemp); dfValue = static_cast(pnTemp[0]); - for (m = 0; m < nAttrLen - 1; m++) + if (nAttrLen > 1) { - snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]); - NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + for (m = 0; m < nAttrLen - 1; m++) + { + snprintf(szTemp, sizeof(szTemp), "%d,", pnTemp[m]); + NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + } } snprintf(szTemp, sizeof(szTemp), "%d", pnTemp[m]); NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); @@ -10445,10 +10453,13 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, static_cast(CPLCalloc(nAttrLen, sizeof(float))); nc_get_att_float(nCdfId, nVarId, pszAttrName, pfTemp); dfValue = static_cast(pfTemp[0]); - for (m = 0; m < nAttrLen - 1; m++) + if (nAttrLen > 1) { - CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]); - NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + for (m = 0; m < nAttrLen - 1; m++) + { + CPLsnprintf(szTemp, sizeof(szTemp), "%.8g,", pfTemp[m]); + NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + } } CPLsnprintf(szTemp, sizeof(szTemp), "%.8g", pfTemp[m]); NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); @@ -10461,10 +10472,13 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, static_cast(CPLCalloc(nAttrLen, sizeof(double))); nc_get_att_double(nCdfId, nVarId, pszAttrName, pdfTemp); dfValue = pdfTemp[0]; - for (m = 0; m < nAttrLen - 1; m++) + if (nAttrLen > 1) { - CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]); - NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + for (m = 0; m < nAttrLen - 1; m++) + { + CPLsnprintf(szTemp, sizeof(szTemp), "%.16g,", pdfTemp[m]); + NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + } } CPLsnprintf(szTemp, sizeof(szTemp), "%.16g", pdfTemp[m]); NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); @@ -10478,12 +10492,15 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, nc_get_att_string(nCdfId, nVarId, pszAttrName, ppszTemp); bSetDoubleFromStr = true; dfValue = 0.0; - for (m = 0; m < nAttrLen - 1; m++) + if (nAttrLen > 1) { - NCDFSafeStrcat(&pszAttrValue, - ppszTemp[m] ? ppszTemp[m] : "{NULL}", - &nAttrValueSize); - NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize); + for (m = 0; m < nAttrLen - 1; m++) + { + NCDFSafeStrcat(&pszAttrValue, + ppszTemp[m] ? ppszTemp[m] : "{NULL}", + &nAttrValueSize); + NCDFSafeStrcat(&pszAttrValue, ",", &nAttrValueSize); + } } NCDFSafeStrcat(&pszAttrValue, ppszTemp[m] ? ppszTemp[m] : "{NULL}", &nAttrValueSize); @@ -10497,10 +10514,13 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, CPLCalloc(nAttrLen, sizeof(unsigned char))); nc_get_att_uchar(nCdfId, nVarId, pszAttrName, pucTemp); dfValue = static_cast(pucTemp[0]); - for (m = 0; m < nAttrLen - 1; m++) + if (nAttrLen > 1) { - CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]); - NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + for (m = 0; m < nAttrLen - 1; m++) + { + CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pucTemp[m]); + NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + } } CPLsnprintf(szTemp, sizeof(szTemp), "%u", pucTemp[m]); NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); @@ -10514,10 +10534,13 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, CPLCalloc(nAttrLen, sizeof(unsigned short))); nc_get_att_ushort(nCdfId, nVarId, pszAttrName, pusTemp); dfValue = static_cast(pusTemp[0]); - for (m = 0; m < nAttrLen - 1; m++) + if (nAttrLen > 1) { - CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]); - NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + for (m = 0; m < nAttrLen - 1; m++) + { + CPLsnprintf(szTemp, sizeof(szTemp), "%u,", pusTemp[m]); + NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + } } CPLsnprintf(szTemp, sizeof(szTemp), "%u", pusTemp[m]); NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); @@ -10530,10 +10553,13 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, static_cast(CPLCalloc(nAttrLen, sizeof(int))); nc_get_att_uint(nCdfId, nVarId, pszAttrName, punTemp); dfValue = static_cast(punTemp[0]); - for (m = 0; m < nAttrLen - 1; m++) + if (nAttrLen > 1) { - CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]); - NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + for (m = 0; m < nAttrLen - 1; m++) + { + CPLsnprintf(szTemp, sizeof(szTemp), "%u,", punTemp[m]); + NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + } } CPLsnprintf(szTemp, sizeof(szTemp), "%u", punTemp[m]); NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); @@ -10546,11 +10572,14 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, static_cast(CPLCalloc(nAttrLen, sizeof(GIntBig))); nc_get_att_longlong(nCdfId, nVarId, pszAttrName, panTemp); dfValue = static_cast(panTemp[0]); - for (m = 0; m < nAttrLen - 1; m++) + if (nAttrLen > 1) { - CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", - panTemp[m]); - NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + for (m = 0; m < nAttrLen - 1; m++) + { + CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB ",", + panTemp[m]); + NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + } } CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GIB, panTemp[m]); NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); @@ -10563,11 +10592,14 @@ static CPLErr NCDFGetAttr1(int nCdfId, int nVarId, const char *pszAttrName, static_cast(CPLCalloc(nAttrLen, sizeof(GUIntBig))); nc_get_att_ulonglong(nCdfId, nVarId, pszAttrName, panTemp); dfValue = static_cast(panTemp[0]); - for (m = 0; m < nAttrLen - 1; m++) + if (nAttrLen > 1) { - CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", - panTemp[m]); - NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + for (m = 0; m < nAttrLen - 1; m++) + { + CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB ",", + panTemp[m]); + NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); + } } CPLsnprintf(szTemp, sizeof(szTemp), CPL_FRMT_GUIB, panTemp[m]); NCDFSafeStrcat(&pszAttrValue, szTemp, &nAttrValueSize); diff --git a/frmts/northwood/northwood.cpp b/frmts/northwood/northwood.cpp index 1917e154f2fd..a12d8a849a8e 100644 --- a/frmts/northwood/northwood.cpp +++ b/frmts/northwood/northwood.cpp @@ -32,6 +32,7 @@ #include "northwood.h" #include +#include #include #include @@ -79,6 +80,7 @@ int nwt_ParseHeader(NWT_GRID *pGrd, const unsigned char *nwtHeader) memcpy(&pGrd->dfMaxY, &nwtHeader[37], sizeof(pGrd->dfMaxY)); CPL_LSBPTR64(&pGrd->dfMaxY); + assert(pGrd->nXSide > 1); pGrd->dfStepSize = (pGrd->dfMaxX - pGrd->dfMinX) / (pGrd->nXSide - 1); /* dfTmp = (pGrd->dfMaxY - pGrd->dfMinY) / (pGrd->nYSide - 1); */ diff --git a/frmts/pdf/pdfdataset.cpp b/frmts/pdf/pdfdataset.cpp index 33bc89b0f49d..1f6b63961887 100644 --- a/frmts/pdf/pdfdataset.cpp +++ b/frmts/pdf/pdfdataset.cpp @@ -2480,7 +2480,7 @@ GDALPDFObject *PDFDataset::GetCatalog() return m_poCatalogObject; #ifdef HAVE_POPPLER - if (m_bUseLib.test(PDFLIB_POPPLER)) + if (m_bUseLib.test(PDFLIB_POPPLER) && m_poDocPoppler) { m_poCatalogObjectPoppler = std::make_unique(m_poDocPoppler->getXRef()->getCatalog()); @@ -2491,7 +2491,7 @@ GDALPDFObject *PDFDataset::GetCatalog() #endif #ifdef HAVE_PODOFO - if (m_bUseLib.test(PDFLIB_PODOFO)) + if (m_bUseLib.test(PDFLIB_PODOFO) && m_poDocPodofo) { int nCatalogNum = 0; int nCatalogGen = 0; @@ -2517,7 +2517,7 @@ GDALPDFObject *PDFDataset::GetCatalog() #endif #ifdef HAVE_PDFIUM - if (m_bUseLib.test(PDFLIB_PDFIUM)) + if (m_bUseLib.test(PDFLIB_PDFIUM) && m_poDocPdfium) { const CPDF_Dictionary *catalog = m_poDocPdfium->doc->GetRoot(); if (catalog) @@ -4957,11 +4957,11 @@ PDFDataset *PDFDataset::Open(GDALOpenInfo *poOpenInfo) return nullptr; } poPageObj = GDALPDFObjectPdfium::Build(pageObj); - if (poPageObj == nullptr) - return nullptr; } #endif // ~ HAVE_PDFIUM + if (poPageObj == nullptr) + return nullptr; GDALPDFDictionary *poPageDict = poPageObj->GetDictionary(); if (poPageDict == nullptr) { @@ -5032,7 +5032,9 @@ PDFDataset *PDFDataset::Open(GDALOpenInfo *poOpenInfo) if (pszDumpCatalog != nullptr) { GDALPDFDumper oDumper(pszFilename, pszDumpCatalog); - oDumper.Dump(poDS->GetCatalog()); + auto poCatalog = poDS->GetCatalog(); + if (poCatalog) + oDumper.Dump(poCatalog); } int nBandsGuessed = 0; @@ -5090,6 +5092,7 @@ PDFDataset *PDFDataset::Open(GDALOpenInfo *poOpenInfo) #ifdef HAVE_PDFIUM if (bUseLib.test(PDFLIB_PDFIUM)) { + CPLAssert(poPagePdfium); CFX_FloatRect rect = poPagePdfium->page->GetBBox(); dfX1 = rect.left; dfX2 = rect.right; @@ -5134,6 +5137,7 @@ PDFDataset *PDFDataset::Open(GDALOpenInfo *poOpenInfo) #ifdef HAVE_PDFIUM if (bUseLib.test(PDFLIB_PDFIUM)) { + CPLAssert(poPagePdfium); dfRotation = poPagePdfium->page->GetPageRotation() * 90; } #endif diff --git a/gcore/gdaljp2structure.cpp b/gcore/gdaljp2structure.cpp index b3ae58eba05f..e55f0787b93a 100644 --- a/gcore/gdaljp2structure.cpp +++ b/gcore/gdaljp2structure.cpp @@ -29,6 +29,7 @@ #include "cpl_port.h" #include "gdaljp2metadata.h" +#include #include #include #if HAVE_FCNTL_H @@ -2177,7 +2178,8 @@ static void GDALGetJPEG2000StructureInternal(CPLXMLNode *psParent, VSILFILE *fp, CPLXMLNode *psBinaryContent = CPLCreateXMLNode(nullptr, CXT_Element, "BinaryContent"); GByte *pabyBoxData = oBox.ReadBoxData(); - int nBoxLength = static_cast(nBoxDataLength); + const int nBoxLength = static_cast( + std::min(nBoxDataLength, INT_MAX / 2 - 1)); char *pszBinaryContent = static_cast(VSIMalloc(2 * nBoxLength + 1)); if (pabyBoxData && pszBinaryContent) diff --git a/gcore/gdalmultidim.cpp b/gcore/gdalmultidim.cpp index 2e380faaafd1..ab7ece1b95f1 100644 --- a/gcore/gdalmultidim.cpp +++ b/gcore/gdalmultidim.cpp @@ -3116,6 +3116,7 @@ bool GDALAbstractMDArray::ProcessPerChunk(const GUInt64 *arrayStartIdx, goto end; } + assert(dimIdx > 0); dimIdx--; // cppcheck-suppress negativeContainerIndex switch (stack[dimIdx].return_point) diff --git a/ogr/ogr_p.h b/ogr/ogr_p.h index 6fde155e9c1f..f4ee626786d1 100644 --- a/ogr/ogr_p.h +++ b/ogr/ogr_p.h @@ -276,7 +276,8 @@ inline uint64_t OGRRoundValueIEEE754(uint64_t nVal, int nBitsPrecision) return nVal; if (nNullifiedBits >= MANTISSA_SIZE) nNullifiedBits = MANTISSA_SIZE; - nVal &= std::numeric_limits::max() << nNullifiedBits; + nVal >>= nNullifiedBits; + nVal <<= nNullifiedBits; return nVal; } diff --git a/ogr/ogrlinestring.cpp b/ogr/ogrlinestring.cpp index 8d99615b4cec..9d59218fb9a4 100644 --- a/ogr/ogrlinestring.cpp +++ b/ogr/ogrlinestring.cpp @@ -2528,6 +2528,8 @@ void OGRSimpleCurve::segmentize(double dfMaxLength) const double dfSquareMaxLength = dfMaxLength * dfMaxLength; // First pass to compute new number of points + constexpr double REL_EPSILON_LENGTH_SQUARE = 1e-5; + constexpr double REL_EPSILON_ROUND = 1e-2; for (int i = 0; i < nPointCount; i++) { nNewPointCount++; @@ -2539,10 +2541,11 @@ void OGRSimpleCurve::segmentize(double dfMaxLength) const double dfX = paoPoints[i + 1].x - paoPoints[i].x; const double dfY = paoPoints[i + 1].y - paoPoints[i].y; const double dfSquareDist = dfX * dfX + dfY * dfY; - if (dfSquareDist - dfSquareMaxLength > 1e-5 * dfSquareMaxLength) + if (dfSquareDist - dfSquareMaxLength > + REL_EPSILON_LENGTH_SQUARE * dfSquareMaxLength) { - const double dfIntermediatePoints = - floor(sqrt(dfSquareDist / dfSquareMaxLength) - 1e-2); + const double dfIntermediatePoints = floor( + sqrt(dfSquareDist / dfSquareMaxLength) - REL_EPSILON_ROUND); const int nIntermediatePoints = DoubleToIntClamp(dfIntermediatePoints); @@ -2620,28 +2623,33 @@ void OGRSimpleCurve::segmentize(double dfMaxLength) const double dfX = paoPoints[i + 1].x - paoPoints[i].x; const double dfY = paoPoints[i + 1].y - paoPoints[i].y; const double dfSquareDist = dfX * dfX + dfY * dfY; - if (dfSquareDist - dfSquareMaxLength > 1e-5 * dfSquareMaxLength) + + // Must be kept in sync with the initial pass loop + if (dfSquareDist - dfSquareMaxLength > + REL_EPSILON_LENGTH_SQUARE * dfSquareMaxLength) { - const double dfIntermediatePoints = - floor(sqrt(dfSquareDist / dfSquareMaxLength) - 1e-2); + const double dfIntermediatePoints = floor( + sqrt(dfSquareDist / dfSquareMaxLength) - REL_EPSILON_ROUND); const int nIntermediatePoints = DoubleToIntClamp(dfIntermediatePoints); for (int j = 1; j <= nIntermediatePoints; j++) { - paoNewPoints[nNewPointCount + j - 1].x = + // coverity[overflow_const] + const int newI = nNewPointCount + j - 1; + paoNewPoints[newI].x = paoPoints[i].x + j * dfX / (nIntermediatePoints + 1); - paoNewPoints[nNewPointCount + j - 1].y = + paoNewPoints[newI].y = paoPoints[i].y + j * dfY / (nIntermediatePoints + 1); if (padfZ != nullptr) { // No interpolation. - padfNewZ[nNewPointCount + j - 1] = padfZ[i]; + padfNewZ[newI] = padfZ[i]; } if (padfM != nullptr) { // No interpolation. - padfNewM[nNewPointCount + j - 1] = padfM[i]; + padfNewM[newI] = padfM[i]; } } diff --git a/ogr/ogrsf_frmts/arrow_common/ograrrowlayer.hpp b/ogr/ogrsf_frmts/arrow_common/ograrrowlayer.hpp index ac5d7d96b256..b364925ea9a9 100644 --- a/ogr/ogrsf_frmts/arrow_common/ograrrowlayer.hpp +++ b/ogr/ogrsf_frmts/arrow_common/ograrrowlayer.hpp @@ -2385,7 +2385,7 @@ inline OGRFeature *OGRArrowLayer::ReadFeature( arrow::LargeBinaryArray::offset_type out_length = 0; const uint8_t *data = castArray->GetValue(nIdxInBatch, &out_length); - if (out_length <= INT_MAX) + if (out_length <= INT_MAX - 1) { poFeature->SetField(i, static_cast(out_length), data); } diff --git a/ogr/ogrsf_frmts/geojson/libjson/json_util.c b/ogr/ogrsf_frmts/geojson/libjson/json_util.c index 0fcb0d4298d3..505834d83ce6 100644 --- a/ogr/ogrsf_frmts/geojson/libjson/json_util.c +++ b/ogr/ogrsf_frmts/geojson/libjson/json_util.c @@ -188,7 +188,7 @@ int json_object_to_fd(int fd, struct json_object *obj, int flags) } static int _json_object_to_fd(int fd, struct json_object *obj, int flags, const char *filename) { - int ret; + ssize_t ret; const char *json_str; unsigned int wpos, wsize; @@ -204,7 +204,7 @@ static int _json_object_to_fd(int fd, struct json_object *obj, int flags, const wpos = 0; while (wpos < wsize) { - if ((ret = (int)write(fd, json_str + wpos, wsize - wpos)) < 0) + if ((ret = write(fd, json_str + wpos, wsize - wpos)) < 0) { _json_c_set_last_err("json_object_to_file: error writing file %s: %s\n", filename, strerror(errno)); diff --git a/ogr/ogrsf_frmts/gmlas/ogrgmlaslayer.cpp b/ogr/ogrsf_frmts/gmlas/ogrgmlaslayer.cpp index 4635659c873c..dccaf32d23d1 100644 --- a/ogr/ogrsf_frmts/gmlas/ogrgmlaslayer.cpp +++ b/ogr/ogrsf_frmts/gmlas/ogrgmlaslayer.cpp @@ -1128,7 +1128,7 @@ void OGRGMLASLayer::InsertNewField(int nInsertPos, if (strcmp(poFeature->GetFieldAsString(szLAYER_NAME), GetName()) == 0) { int nFieldIndex = poFeature->GetFieldAsInteger(szFIELD_INDEX); - if (nFieldIndex >= nInsertPos) + if (nFieldIndex >= nInsertPos && nFieldIndex < INT_MAX) { poFeature->SetField(szFIELD_INDEX, nFieldIndex + 1); CPL_IGNORE_RET_VAL( diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp index ad16578e40d5..be706dbebb4a 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp @@ -8231,7 +8231,10 @@ void OGR_GPKG_FillArrowArray_Step(sqlite3_context *pContext, int /*argc*/, } if (iField == psHelper->m_nFieldCount) + { + std::unique_lock oLock(psFillArrowArray->oMutex); psFillArrowArray->nCountRows++; + } return; error: @@ -8655,7 +8658,13 @@ int OGRGeoPackageTableLayer::GetNextArrowArray(struct ArrowArrayStream *stream, sizeof(struct ArrowArray)); memset(task->m_psArrowArray.get(), 0, sizeof(struct ArrowArray)); - if (task->m_bMemoryLimitReached) + const bool bMemoryLimitReached = [&task]() + { + std::unique_lock oLock(task->m_oMutex); + return task->m_bMemoryLimitReached; + }(); + + if (bMemoryLimitReached) { m_nIsCompatOfOptimizedGetNextArrowArray = false; stopThread(); diff --git a/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp b/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp index 3302b5d938d9..fadf1938b908 100644 --- a/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp +++ b/ogr/ogrsf_frmts/mvt/ogrmvtdataset.cpp @@ -4265,24 +4265,35 @@ OGRErr OGRMVTWriterDataset::PreGenerateForTileReal( oBuffer.assign(static_cast(pCompressed), nCompressedSize); CPLFree(pCompressed); - std::unique_ptr> poLockGuard; + const auto InsertIntoDb = [&]() + { + m_nTempTiles++; + sqlite3_bind_int(m_hInsertStmt, 1, nZ); + sqlite3_bind_int(m_hInsertStmt, 2, nTileX); + sqlite3_bind_int(m_hInsertStmt, 3, nTileY); + sqlite3_bind_text(m_hInsertStmt, 4, osTargetName.c_str(), -1, + SQLITE_STATIC); + sqlite3_bind_int64(m_hInsertStmt, 5, nSerial); + sqlite3_bind_blob(m_hInsertStmt, 6, oBuffer.data(), + static_cast(oBuffer.size()), SQLITE_STATIC); + sqlite3_bind_int(m_hInsertStmt, 7, + static_cast(poGPBFeature->getType())); + sqlite3_bind_double(m_hInsertStmt, 8, dfAreaOrLength); + int rc = sqlite3_step(m_hInsertStmt); + sqlite3_reset(m_hInsertStmt); + return rc; + }; + + int rc; if (m_bThreadPoolOK) - poLockGuard = std::make_unique>(m_oDBMutex); - - m_nTempTiles++; - sqlite3_bind_int(m_hInsertStmt, 1, nZ); - sqlite3_bind_int(m_hInsertStmt, 2, nTileX); - sqlite3_bind_int(m_hInsertStmt, 3, nTileY); - sqlite3_bind_text(m_hInsertStmt, 4, osTargetName.c_str(), -1, - SQLITE_STATIC); - sqlite3_bind_int64(m_hInsertStmt, 5, nSerial); - sqlite3_bind_blob(m_hInsertStmt, 6, oBuffer.data(), - static_cast(oBuffer.size()), SQLITE_STATIC); - sqlite3_bind_int(m_hInsertStmt, 7, - static_cast(poGPBFeature->getType())); - sqlite3_bind_double(m_hInsertStmt, 8, dfAreaOrLength); - int rc = sqlite3_step(m_hInsertStmt); - sqlite3_reset(m_hInsertStmt); + { + std::lock_guard oLock(m_oDBMutex); + rc = InsertIntoDb(); + } + else + { + rc = InsertIntoDb(); + } if (!(rc == SQLITE_OK || rc == SQLITE_DONE)) { diff --git a/ogr/ogrsf_frmts/ntf/ntfrecord.cpp b/ogr/ogrsf_frmts/ntf/ntfrecord.cpp index 0bf6bebec369..1fe02fb7ed63 100644 --- a/ogr/ogrsf_frmts/ntf/ntfrecord.cpp +++ b/ogr/ogrsf_frmts/ntf/ntfrecord.cpp @@ -74,6 +74,7 @@ NTFRecord::NTFRecord(VSILFILE *fp) : nType(99), nLength(0), pszData(nullptr) if (pszData == nullptr) { nLength = nNewLength - 2; + // coverity[overflow_sink] pszData = static_cast(VSI_MALLOC_VERBOSE(nLength + 1)); if (pszData == nullptr) { diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp index 20d715ea3eef..18936e188acb 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp @@ -30,6 +30,7 @@ #include "filegdbtable.h" #include +#include #include #include #include @@ -3664,6 +3665,7 @@ OGRGeometry *FileGDBOGRGeometryConverterImpl::CreateCurveGeometry( returnError(); } const int nMaxSize = static_cast(nMaxSize64); + // coverity[overflow_sink] GByte *pabyExtShapeBuffer = static_cast(VSI_MALLOC_VERBOSE(nMaxSize)); if (pabyExtShapeBuffer == nullptr) @@ -3860,11 +3862,19 @@ FileGDBOGRGeometryConverterImpl::GetAsGeometry(const OGRField *psField) ReadVarUInt64NoCheck(pabyCur, m); const double dfMScale = SanitizeScale(poGeomField->GetMScale()); - const double dfM = - m == 0 - ? std::numeric_limits::quiet_NaN() - : (m - 1U) / dfMScale + poGeomField->GetMOrigin(); - return new OGRPoint(dfX, dfY, dfZ, dfM); + if (m == 0) + { + return new OGRPoint( + dfX, dfY, dfZ, + std::numeric_limits::quiet_NaN()); + } + else + { + assert(m >= 1U); + const double dfM = + (m - 1U) / dfMScale + poGeomField->GetMOrigin(); + return new OGRPoint(dfX, dfY, dfZ, dfM); + } } return new OGRPoint(dfX, dfY, dfZ); } diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_freelist.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_freelist.cpp index c666c1bbce44..56768752d458 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_freelist.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_freelist.cpp @@ -32,6 +32,7 @@ #include "filegdbtable_priv.h" #include +#include #include #include @@ -140,6 +141,7 @@ void FileGDBTable::AddEntryToFreelist(uint64_t nOffset, uint32_t nSize) VSIFCloseL(fp); return; } + assert(iSlot < 100); // Read the last page index of the identified slot uint32_t nPageIdx = @@ -284,6 +286,7 @@ uint64_t FileGDBTable::GetOffsetOfFreeAreaFromFreeList(uint32_t nSize) VSIFCloseL(fp); return OFFSET_MINUS_ONE; } + assert(iSlot < 100); // Read the last page index of the identified slot uint32_t nPageIdx = diff --git a/ogr/ogrsf_frmts/osm/ogrosmdatasource.cpp b/ogr/ogrsf_frmts/osm/ogrosmdatasource.cpp index 53226246e321..895574462710 100644 --- a/ogr/ogrsf_frmts/osm/ogrosmdatasource.cpp +++ b/ogr/ogrsf_frmts/osm/ogrosmdatasource.cpp @@ -925,6 +925,7 @@ void OGROSMDataSource::LookupNodesSQLite() if (nToQuery > static_cast(LIMIT_IDS_PER_REQUEST)) nToQuery = static_cast(LIMIT_IDS_PER_REQUEST); + assert(nToQuery > 0); sqlite3_stmt *hStmt = m_pahSelectNodeStmt[nToQuery - 1]; for (unsigned int i = iCur; i < iCur + nToQuery; i++) { diff --git a/ogr/ogrsf_frmts/pmtiles/pmtiles/pmtiles.hpp b/ogr/ogrsf_frmts/pmtiles/pmtiles/pmtiles.hpp index 82ed8c56f5d0..c09f44d02020 100644 --- a/ogr/ogrsf_frmts/pmtiles/pmtiles/pmtiles.hpp +++ b/ogr/ogrsf_frmts/pmtiles/pmtiles/pmtiles.hpp @@ -576,6 +576,9 @@ inline std::tuple make_root_leaves(const std::fun if (root_bytes.length() < 16384 - 127) { return std::make_tuple(root_bytes, leaves_bytes, num_leaves); } + if (leaf_size > std::numeric_limits::max() / 2) { + return std::make_tuple(compressed, "", 0); + } leaf_size *= 2; } } diff --git a/ogr/ogrsf_frmts/shape/ogrshape.h b/ogr/ogrsf_frmts/shape/ogrshape.h index 471471f5f1a7..b907abff3bab 100644 --- a/ogr/ogrsf_frmts/shape/ogrshape.h +++ b/ogr/ogrsf_frmts/shape/ogrshape.h @@ -339,6 +339,7 @@ class OGRShapeDataSource final : public OGRDataSource VSILFILE *m_psLockFile = nullptr; CPLJoinableThread *m_hRefreshLockFileThread = nullptr; bool m_bExitRefreshLockFileThread = false; + bool m_bRefreshLockFileThreadStarted = false; double m_dfRefreshLockDelay = 0; std::vector GetLayerNames() const; diff --git a/ogr/ogrsf_frmts/shape/ogrshapedatasource.cpp b/ogr/ogrsf_frmts/shape/ogrshapedatasource.cpp index 8b8d432e1373..69e45b7d4b50 100644 --- a/ogr/ogrsf_frmts/shape/ogrshapedatasource.cpp +++ b/ogr/ogrsf_frmts/shape/ogrshapedatasource.cpp @@ -1403,6 +1403,7 @@ void OGRShapeDataSource::RefreshLockFile(void *_self) OGRShapeDataSource *self = static_cast(_self); CPLAssert(self->m_psLockFile); CPLAcquireMutex(self->m_poRefreshLockFileMutex, 1000); + self->m_bRefreshLockFileThreadStarted = true; CPLCondSignal(self->m_poRefreshLockFileCond); unsigned int nInc = 0; while (!(self->m_bExitRefreshLockFileThread)) @@ -1500,6 +1501,7 @@ bool OGRShapeDataSource::UncompressIfNeeded() } m_psLockFile = f; m_bExitRefreshLockFileThread = false; + m_bRefreshLockFileThreadStarted = false; // Config option mostly for testing purposes // coverity[tainted_data] m_dfRefreshLockDelay = CPLAtof(CPLGetConfigOption( @@ -1516,7 +1518,10 @@ bool OGRShapeDataSource::UncompressIfNeeded() else { CPLAcquireMutex(m_poRefreshLockFileMutex, 1000); - CPLCondWait(m_poRefreshLockFileCond, m_poRefreshLockFileMutex); + while (!m_bRefreshLockFileThreadStarted) + { + CPLCondWait(m_poRefreshLockFileCond, m_poRefreshLockFileMutex); + } CPLReleaseMutex(m_poRefreshLockFileMutex); } } diff --git a/ogr/ogrsf_frmts/vfk/vfkreader.cpp b/ogr/ogrsf_frmts/vfk/vfkreader.cpp index 16fbdc85e0ff..05f61e78ce08 100644 --- a/ogr/ogrsf_frmts/vfk/vfkreader.cpp +++ b/ogr/ogrsf_frmts/vfk/vfkreader.cpp @@ -314,7 +314,7 @@ int VFKReader::ReadDataBlocks(bool bSuppressGeometry) \return number of data records or -1 on error */ -int VFKReader::ReadDataRecords(IVFKDataBlock *poDataBlock) +int64_t VFKReader::ReadDataRecords(IVFKDataBlock *poDataBlock) { const char *pszName = nullptr; IVFKDataBlock *poDataBlockCurrent = nullptr; @@ -342,7 +342,7 @@ int VFKReader::ReadDataRecords(IVFKDataBlock *poDataBlock) int iLine = 0; int nSkipped = 0; int nDupl = 0; - int nRecords = 0; + int64_t nRecords = 0; bool bInHeader = true; CPLString osBlockNameLast; char *pszLine = nullptr; diff --git a/ogr/ogrsf_frmts/vfk/vfkreader.h b/ogr/ogrsf_frmts/vfk/vfkreader.h index 6d3898350017..7ce3fbfe2399 100644 --- a/ogr/ogrsf_frmts/vfk/vfkreader.h +++ b/ogr/ogrsf_frmts/vfk/vfkreader.h @@ -470,7 +470,7 @@ class IVFKReader virtual bool IsValid() const = 0; virtual bool HasFileField() const = 0; virtual int ReadDataBlocks(bool = false) = 0; - virtual int ReadDataRecords(IVFKDataBlock * = nullptr) = 0; + virtual int64_t ReadDataRecords(IVFKDataBlock * = nullptr) = 0; virtual int LoadGeometry() = 0; virtual int GetDataBlockCount() const = 0; diff --git a/ogr/ogrsf_frmts/vfk/vfkreaderp.h b/ogr/ogrsf_frmts/vfk/vfkreaderp.h index 735ecece239c..8891158fa392 100644 --- a/ogr/ogrsf_frmts/vfk/vfkreaderp.h +++ b/ogr/ogrsf_frmts/vfk/vfkreaderp.h @@ -108,7 +108,7 @@ class VFKReader : public IVFKReader } int ReadDataBlocks(bool = false) override; - int ReadDataRecords(IVFKDataBlock * = nullptr) override; + int64_t ReadDataRecords(IVFKDataBlock * = nullptr) override; int LoadGeometry() override; int GetDataBlockCount() const override @@ -166,7 +166,7 @@ class VFKReaderSQLite : public VFKReader } int ReadDataBlocks(bool = false) override; - int ReadDataRecords(IVFKDataBlock * = nullptr) override; + int64_t ReadDataRecords(IVFKDataBlock * = nullptr) override; sqlite3_stmt *PrepareStatement(const char *); OGRErr ExecuteSQL(const char *, CPLErr = CE_Failure); diff --git a/ogr/ogrsf_frmts/vfk/vfkreadersqlite.cpp b/ogr/ogrsf_frmts/vfk/vfkreadersqlite.cpp index 84c71e5326a5..6a49db943a5e 100644 --- a/ogr/ogrsf_frmts/vfk/vfkreadersqlite.cpp +++ b/ogr/ogrsf_frmts/vfk/vfkreadersqlite.cpp @@ -338,13 +338,13 @@ int VFKReaderSQLite::ReadDataBlocks(bool bSuppressGeometry) \return number of data records or -1 on error */ -int VFKReaderSQLite::ReadDataRecords(IVFKDataBlock *poDataBlock) +int64_t VFKReaderSQLite::ReadDataRecords(IVFKDataBlock *poDataBlock) { CPLString osSQL; IVFKDataBlock *poDataBlockCurrent = nullptr; sqlite3_stmt *hStmt = nullptr; const char *pszName = nullptr; - int nDataRecords = 0; + int64_t nDataRecords = 0; bool bReadVfk = !m_bDbSource; bool bReadDb = false; @@ -360,7 +360,7 @@ int VFKReaderSQLite::ReadDataRecords(IVFKDataBlock *poDataBlock) hStmt = PrepareStatement(osSQL.c_str()); if (ExecuteSQL(hStmt) == OGRERR_NONE) { - nDataRecords = sqlite3_column_int(hStmt, 0); + nDataRecords = sqlite3_column_int64(hStmt, 0); if (nDataRecords > 0) bReadDb = true; /* -> read from DB */ else diff --git a/ogr/ogrsf_frmts/vrt/ogrvrtdriver.cpp b/ogr/ogrsf_frmts/vrt/ogrvrtdriver.cpp index 65bfa072e3a1..f86a32a817e8 100644 --- a/ogr/ogrsf_frmts/vrt/ogrvrtdriver.cpp +++ b/ogr/ogrsf_frmts/vrt/ogrvrtdriver.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -130,9 +131,14 @@ static GDALDataset *OGRVRTDriverOpen(GDALOpenInfo *poOpenInfo) "configuration option"); return nullptr; } + if (static_cast(sStatBuf.st_size) > + std::numeric_limits::max() - 1) + { + return nullptr; + } // It is the right file, now load the full XML definition. - const int nLen = static_cast(sStatBuf.st_size); + const size_t nLen = static_cast(sStatBuf.st_size); pszXML = static_cast(VSI_MALLOC_VERBOSE(nLen + 1)); if (pszXML == nullptr) @@ -140,8 +146,7 @@ static GDALDataset *OGRVRTDriverOpen(GDALOpenInfo *poOpenInfo) pszXML[nLen] = '\0'; VSIFSeekL(poOpenInfo->fpL, 0, SEEK_SET); - if (static_cast(VSIFReadL(pszXML, 1, nLen, poOpenInfo->fpL)) != - nLen) + if (VSIFReadL(pszXML, 1, nLen, poOpenInfo->fpL) != nLen) { CPLFree(pszXML); return nullptr; diff --git a/ogr/ogrsf_frmts/wfs/ogrwfsdatasource.cpp b/ogr/ogrsf_frmts/wfs/ogrwfsdatasource.cpp index 3bb8ef68f0b2..cbfcec134998 100644 --- a/ogr/ogrsf_frmts/wfs/ogrwfsdatasource.cpp +++ b/ogr/ogrsf_frmts/wfs/ogrwfsdatasource.cpp @@ -1376,6 +1376,7 @@ int OGRWFSDataSource::Open(const char *pszFilename, int bUpdateIn, OGRLayer::GetSupportedSRSListRetType apoSupportedCRSList; if (psOtherSRS) { + if (pszDefaultSRS) { auto poSRS = std::unique_ptrbits[0]; + // coverity[overflow_const] if ((context->bits[0] = (t + (static_cast(len) << 3)) & 0xffffffff) < t) context->bits[1]++; /* Carry from low to high */ diff --git a/port/cpl_spawn.cpp b/port/cpl_spawn.cpp index 3fb75d7274ea..64b7a3bd1a38 100644 --- a/port/cpl_spawn.cpp +++ b/port/cpl_spawn.cpp @@ -474,7 +474,7 @@ int CPLPipeRead(CPL_FILE_HANDLE fin, void *data, int length) { while (true) { - const int n = static_cast(read(fin, pabyData, nRemain)); + const auto n = read(fin, pabyData, nRemain); if (n < 0) { if (errno == EINTR) @@ -485,7 +485,7 @@ int CPLPipeRead(CPL_FILE_HANDLE fin, void *data, int length) else if (n == 0) return FALSE; pabyData += n; - nRemain -= n; + nRemain -= static_cast(n); break; } } @@ -515,7 +515,7 @@ int CPLPipeWrite(CPL_FILE_HANDLE fout, const void *data, int length) { while (true) { - const int n = static_cast(write(fout, pabyData, nRemain)); + const auto n = write(fout, pabyData, nRemain); if (n < 0) { if (errno == EINTR) @@ -524,7 +524,7 @@ int CPLPipeWrite(CPL_FILE_HANDLE fout, const void *data, int length) return FALSE; } pabyData += n; - nRemain -= n; + nRemain -= static_cast(n); break; } } diff --git a/port/cpl_vsi_mem.cpp b/port/cpl_vsi_mem.cpp index caf3a5436c22..17f57ef9fa88 100644 --- a/port/cpl_vsi_mem.cpp +++ b/port/cpl_vsi_mem.cpp @@ -344,7 +344,11 @@ int VSIMemHandle::Close() int VSIMemHandle::Seek(vsi_l_offset nOffset, int nWhence) { - CPL_SHARED_LOCK oLock(poFile->m_oMutex); + vsi_l_offset nLength; + { + CPL_SHARED_LOCK oLock(poFile->m_oMutex); + nLength = poFile->nLength; + } bExtendFileAtNextWrite = false; if (nWhence == SEEK_CUR) @@ -361,7 +365,7 @@ int VSIMemHandle::Seek(vsi_l_offset nOffset, int nWhence) } else if (nWhence == SEEK_END) { - m_nOffset = poFile->nLength + nOffset; + m_nOffset = nLength + nOffset; } else { @@ -371,7 +375,7 @@ int VSIMemHandle::Seek(vsi_l_offset nOffset, int nWhence) bEOF = false; - if (m_nOffset > poFile->nLength) + if (m_nOffset > nLength) { if (bUpdate) // Writable files are zero-extended by seek past end. { @@ -399,13 +403,7 @@ vsi_l_offset VSIMemHandle::Tell() size_t VSIMemHandle::Read(void *pBuffer, size_t nSize, size_t nCount) { - CPL_SHARED_LOCK oLock(poFile->m_oMutex); - - if (!m_bReadAllowed) - { - m_bError = true; - return 0; - } + const vsi_l_offset nOffset = m_nOffset; size_t nBytesToRead = nSize * nCount; if (nBytesToRead == 0) @@ -417,21 +415,32 @@ size_t VSIMemHandle::Read(void *pBuffer, size_t nSize, size_t nCount) return 0; } - if (poFile->nLength <= m_nOffset || nBytesToRead + m_nOffset < nBytesToRead) + if (!m_bReadAllowed) { - bEOF = true; + m_bError = true; return 0; } - if (nBytesToRead + m_nOffset > poFile->nLength) + { - nBytesToRead = static_cast(poFile->nLength - m_nOffset); - nCount = nBytesToRead / nSize; - bEOF = true; + CPL_SHARED_LOCK oLock(poFile->m_oMutex); + + if (poFile->nLength <= nOffset || nBytesToRead + nOffset < nBytesToRead) + { + bEOF = true; + return 0; + } + if (nBytesToRead + nOffset > poFile->nLength) + { + nBytesToRead = static_cast(poFile->nLength - nOffset); + nCount = nBytesToRead / nSize; + bEOF = true; + } + + if (nBytesToRead) + memcpy(pBuffer, poFile->pabyData + nOffset, + static_cast(nBytesToRead)); } - if (nBytesToRead) - memcpy(pBuffer, poFile->pabyData + m_nOffset, - static_cast(nBytesToRead)); m_nOffset += nBytesToRead; return nCount; @@ -465,46 +474,50 @@ size_t VSIMemHandle::PRead(void *pBuffer, size_t nSize, size_t VSIMemHandle::Write(const void *pBuffer, size_t nSize, size_t nCount) { - CPL_EXCLUSIVE_LOCK oLock(poFile->m_oMutex); + const vsi_l_offset nOffset = m_nOffset; if (!bUpdate) { errno = EACCES; return 0; } + + const size_t nBytesToWrite = nSize * nCount; + if (bExtendFileAtNextWrite) { bExtendFileAtNextWrite = false; - if (!poFile->SetLength(m_nOffset)) + CPL_EXCLUSIVE_LOCK oLock(poFile->m_oMutex); + if (!poFile->SetLength(nOffset)) return 0; } - const size_t nBytesToWrite = nSize * nCount; - if (nCount > 0 && nBytesToWrite / nCount != nSize) - { - return 0; - } - if (nBytesToWrite + m_nOffset < nBytesToWrite) { - return 0; - } + CPL_EXCLUSIVE_LOCK oLock(poFile->m_oMutex); - if (nBytesToWrite + m_nOffset > poFile->nLength) - { - if (!poFile->SetLength(nBytesToWrite + m_nOffset)) + if (nCount > 0 && nBytesToWrite / nCount != nSize) + { return 0; + } + if (nBytesToWrite + nOffset < nBytesToWrite) + { + return 0; + } + + if (nBytesToWrite + nOffset > poFile->nLength) + { + if (!poFile->SetLength(nBytesToWrite + nOffset)) + return 0; + } + + if (nBytesToWrite) + memcpy(poFile->pabyData + nOffset, pBuffer, nBytesToWrite); + + time(&poFile->mTime); } - if (nBytesToWrite) - memcpy(poFile->pabyData + m_nOffset, pBuffer, nBytesToWrite); - // Coverity seems to be confused by the fact that we access m_nOffset - // under a shared lock in most places, except here under an exclusive lock - // which is fine - // coverity[missing_lock] m_nOffset += nBytesToWrite; - time(&poFile->mTime); - return nCount; } @@ -684,8 +697,12 @@ VSIVirtualHandle *VSIMemFilesystemHandler::Open(const char *pszFilename, #endif if (strchr(pszAccess, 'a')) { - CPL_SHARED_LOCK oLock(poFile->m_oMutex); - poHandle->m_nOffset = poFile->nLength; + vsi_l_offset nOffset; + { + CPL_SHARED_LOCK oLock(poFile->m_oMutex); + nOffset = poFile->nLength; + } + poHandle->m_nOffset = nOffset; } return poHandle; diff --git a/port/cpl_vsil_s3.cpp b/port/cpl_vsil_s3.cpp index 6be5177127e0..5225e45b9f1e 100644 --- a/port/cpl_vsil_s3.cpp +++ b/port/cpl_vsil_s3.cpp @@ -3814,6 +3814,7 @@ int IVSIS3LikeFSHandlerWithMultipartUpload::CopyFileRestartable( while (!bStop) { oCV.wait(oLock); + // coverity[ uninit_use_in_call] oLock.unlock(); const bool bInterrupt = !pProgressFunc(double(iCurChunk) / nChunkCount, diff --git a/port/cpl_vsisimple.cpp b/port/cpl_vsisimple.cpp index 744b540a08cd..a8d545ab2bb8 100644 --- a/port/cpl_vsisimple.cpp +++ b/port/cpl_vsisimple.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #if HAVE_SYS_STAT_H #include #endif @@ -1401,8 +1402,11 @@ GIntBig CPLGetPhysicalRAM(void) { const long nPhysPages = sysconf(_SC_PHYS_PAGES); const long nPageSize = sysconf(_SC_PAGESIZE); - if (nPhysPages < 0 || nPageSize < 0) + if (nPhysPages <= 0 || nPageSize <= 0 || + nPhysPages > std::numeric_limits::max() / nPageSize) + { return 0; + } GIntBig nVal = static_cast(nPhysPages) * nPageSize; #ifdef __linux diff --git a/port/cpl_worker_thread_pool.cpp b/port/cpl_worker_thread_pool.cpp index 9087cf0270f7..444055a1879b 100644 --- a/port/cpl_worker_thread_pool.cpp +++ b/port/cpl_worker_thread_pool.cpp @@ -86,6 +86,16 @@ CPLWorkerThreadPool::~CPLWorkerThreadPool() CPLListDestroy(psWaitingWorkerThreadsList); } +/************************************************************************/ +/* GetThreadCount() */ +/************************************************************************/ + +int CPLWorkerThreadPool::GetThreadCount() const +{ + std::unique_lock oGuard(m_mutex); + return m_nMaxThreads; +} + /************************************************************************/ /* WorkerThreadFunction() */ /************************************************************************/ @@ -130,7 +140,12 @@ void CPLWorkerThreadPool::WorkerThreadFunction(void *user_data) */ bool CPLWorkerThreadPool::SubmitJob(CPLThreadFunc pfnFunc, void *pData) { - CPLAssert(m_nMaxThreads > 0); +#ifdef DEBUG + { + std::unique_lock oGuard(m_mutex); + CPLAssert(m_nMaxThreads > 0); + } +#endif bool bMustIncrementWaitingWorkerThreadsAfterSubmission = false; if (threadLocalCurrentThreadPool == this) @@ -234,6 +249,7 @@ bool CPLWorkerThreadPool::SubmitJob(CPLThreadFunc pfnFunc, void *pData) { std::lock_guard oGuardWT(psWorkerThread->m_mutex); + // coverity[ uninit_use_in_call] oGuard.unlock(); psWorkerThread->m_cv.notify_one(); } @@ -257,7 +273,12 @@ bool CPLWorkerThreadPool::SubmitJob(CPLThreadFunc pfnFunc, void *pData) bool CPLWorkerThreadPool::SubmitJobs(CPLThreadFunc pfnFunc, const std::vector &apData) { - CPLAssert(m_nMaxThreads > 0); +#ifdef DEBUG + { + std::unique_lock oGuard(m_mutex); + CPLAssert(m_nMaxThreads > 0); + } +#endif if (threadLocalCurrentThreadPool == this) { @@ -361,6 +382,7 @@ bool CPLWorkerThreadPool::SubmitJobs(CPLThreadFunc pfnFunc, #endif { std::lock_guard oGuardWT(psWorkerThread->m_mutex); + // coverity[ uninit_use_in_call] oGuard.unlock(); psWorkerThread->m_cv.notify_one(); } @@ -467,11 +489,14 @@ bool CPLWorkerThreadPool::Setup(int nThreads, CPLThreadFunc pfnInitFunc, bool bRet = true; for (int i = static_cast(aWT.size()); i < nThreads; i++) { - std::unique_ptr wt(new CPLWorkerThread); + auto wt = std::make_unique(); wt->pfnInitFunc = pfnInitFunc; wt->pInitData = pasInitData ? pasInitData[i] : nullptr; wt->poTP = this; - wt->bMarkedAsWaiting = false; + { + std::lock_guard oGuard(wt->m_mutex); + wt->bMarkedAsWaiting = false; + } wt->hThread = CPLCreateJoinableThread(WorkerThreadFunction, wt.get()); if (wt->hThread == nullptr) { @@ -575,8 +600,12 @@ CPLWorkerThreadPool::GetNextJob(CPLWorkerThread *psWorkerThread) #endif std::unique_lock oGuardThisThread(psWorkerThread->m_mutex); + // coverity[uninit_use_in_call] oGuard.unlock(); - psWorkerThread->m_cv.wait(oGuardThisThread); + while (psWorkerThread->bMarkedAsWaiting && eState != CPLWTS_STOP) + { + psWorkerThread->m_cv.wait(oGuardThisThread); + } } } diff --git a/port/cpl_worker_thread_pool.h b/port/cpl_worker_thread_pool.h index a6a0519220f2..77ecdcb2518c 100644 --- a/port/cpl_worker_thread_pool.h +++ b/port/cpl_worker_thread_pool.h @@ -80,7 +80,7 @@ class CPL_DLL CPLWorkerThreadPool CPL_DISALLOW_COPY_ASSIGN(CPLWorkerThreadPool) std::vector> aWT{}; - std::mutex m_mutex{}; + mutable std::mutex m_mutex{}; std::condition_variable m_cv{}; volatile CPLWorkerThreadState eState = CPLWTS_OK; CPLList *psJobQueue = nullptr; @@ -112,10 +112,7 @@ class CPL_DLL CPLWorkerThreadPool void WaitEvent(); /** Return the number of threads setup */ - int GetThreadCount() const - { - return m_nMaxThreads; - } + int GetThreadCount() const; }; /** Job queue */ From 100b9cf09209a13b3c0518bc0bc69ec94c3a178c Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 24 Jun 2024 14:52:22 +0200 Subject: [PATCH 0237/1119] Doc: MapInfo: document supported encodings --- .../drivers/vector/mapinfo_encodings.csv | 37 +++++++++++++++++++ doc/source/drivers/vector/mitab.rst | 8 +++- ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp | 1 + 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 doc/source/drivers/vector/mapinfo_encodings.csv diff --git a/doc/source/drivers/vector/mapinfo_encodings.csv b/doc/source/drivers/vector/mapinfo_encodings.csv new file mode 100644 index 000000000000..06caa9eee73a --- /dev/null +++ b/doc/source/drivers/vector/mapinfo_encodings.csv @@ -0,0 +1,37 @@ +"ENCODING value","MapInfo charset",description +""""" (empty string)",Neutral,"No character conversions performed." +"ISO-8859-1","ISO8859_1","ISO 8859-1 (UNIX)" +"ISO-8859-2","ISO8859_2","ISO 8859-2 (UNIX)" +"ISO-8859-3","ISO8859_3","ISO 8859-3 (UNIX)" +"ISO-8859-4","ISO8859_4","ISO 8859-4 (UNIX)" +"ISO-8859-5","ISO8859_5","ISO 8859-5 (UNIX)" +"ISO-8859-6","ISO8859_6","ISO 8859-6 (UNIX)" +"ISO-8859-7","ISO8859_7","ISO 8859-7 (UNIX)" +"ISO-8859-8","ISO8859_8","ISO 8859-8 (UNIX)" +"ISO-8859-9","ISO8859_9","ISO 8859-9 (UNIX)" +"EUC-JP","PackedEUCJapaese","UNIX, standard Japanese implementation." +"CP1252","WindowsLatin1","" +"CP1250","WindowsLatin2","" +"CP1256","WindowsArabic","" +"CP1251","WindowsCyrillic","" +"CP1257","WindowsBalticRim","" +"CP1253","WindowsGreek","" +"CP1255","WindowsHebrew","" +"CP1254","WindowsTurkish","Windows Eastern Europe" +"CP950","WindowsTradChinese","Windows Traditional Chinese" +"CP936","WindowsSimpChinese","Windows Simplified Chinese" +"CP932","WindowsJapanese","" +"CP949","WindowsKorean","" +"CP437","CodePage437","DOS Code Page 437 = IBM Extended ASCII" +"CP850","CodePage850","DOS Code Page 850 = Multilingual" +"CP852","CodePage852","DOS Code Page 852 = Eastern Europe" +"CP855","CodePage855","DOS Code Page 855 = Cyrillic" +"CP857","CodePage857","" +"CP860","CodePage860","DOS Code Page 860 = Portuguese" +"CP861","CodePage861","DOS Code Page 861 = Icelandic" +"CP863","CodePage863","DOS Code Page 863 = French Canadian" +"CP864","CodePage864","DOS Code Page 864 = Arabic" +"CP865","CodePage865","DOS Code Page 865 = Nordic" +"CP869","CodePage869","DOS Code Page 869 = Modern Greek" +"(no iconv match)","LICS","Lotus worksheet release 1,2 character set" +"(no iconv match)","LMBCS","Lotus worksheet release 3,4 character set" diff --git a/doc/source/drivers/vector/mitab.rst b/doc/source/drivers/vector/mitab.rst index 6e4e4d444bc3..df01b9e3df80 100644 --- a/doc/source/drivers/vector/mitab.rst +++ b/doc/source/drivers/vector/mitab.rst @@ -150,9 +150,15 @@ The following layer creation options are supported: Define the encoding for field names and field values. The encoding name is specified in the format supported by :cpp:func:`CPLRecode` (e.g. ISO-8859-1, CP1251, CP1252 ...) and - internally converted to MapInfo charsets names. Default value is '' + internally converted to MapInfo charsets names. Default value is '' (empty string) that equals to 'Neutral' MapInfo charset. + Currently supported values for the encoding name are: + + .. csv-table:: MapInfo encodings + :file: mapinfo_encodings.csv + :header-rows: 1 + - .. lco:: DESCRIPTION :since: 3.1.0 diff --git a/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp b/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp index 6183d7c6e69e..cce88646f5c3 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp @@ -591,6 +591,7 @@ const char *IMapInfoFile::GetCharset() const // Table is adopted from // http://www.i-signum.com/Formation/download/MB_ReferenceGuide.pdf pp. 127-128 +// NOTE: if modifying this table, please keep doc/source/drivers/vector/mapinfo_encodings.csv in sync static const char *const apszCharsets[][2] = { {"Neutral", ""}, // No character conversions performed. {"ISO8859_1", "ISO-8859-1"}, // ISO 8859-1 (UNIX) From a44e40295ccb0551fe5cef4088584549815149f3 Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Mon, 24 Jun 2024 22:56:52 +1000 Subject: [PATCH 0238/1119] Update gdal2tiles.rst typo webp->jpeg in jpeg options section (#10286) --- doc/source/programs/gdal2tiles.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/programs/gdal2tiles.rst b/doc/source/programs/gdal2tiles.rst index 2e51559bd161..164e478aa9e6 100644 --- a/doc/source/programs/gdal2tiles.rst +++ b/doc/source/programs/gdal2tiles.rst @@ -264,7 +264,7 @@ WEBP options WEBP tiledriver support is new to GDAL 3.6. It is enabled by using --tiledriver=WEBP. -The following configuration options are available to further customize the webp output: +The following configuration options are available to further customize the WebP output: .. option:: --webp-quality= @@ -288,7 +288,7 @@ JPEG tiledriver support is new to GDAL 3.9. It is enabled by using --tiledriver= Note that JPEG does not support transparency, hence edge tiles will display black pixels in areas not covered by the source raster. -The following configuration options are available to further customize the webp output: +The following configuration options are available to further customize the JPEG output: .. option:: ---jpeg-quality=JPEG_QUALITY From 1015b1c32040a3d9db614489d29c2f68f23378d0 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 24 Jun 2024 21:20:19 +0200 Subject: [PATCH 0239/1119] ogr2ogr: error out if WKT argument of -clipsrc/-clipdst is an invalid geometry (#10292) If specifying -makevalid, MakeValid() will be run on it to make it valid. For consistency, change behavior of -clipsrc/-clipdst {datasetname} to not automatically make valid invalid clip geometries, but do it only if -makevalid is specified. Fixes #10289 --- apps/gdal_utils_priv.h | 1 + apps/ogr2ogr_bin.cpp | 3 +- apps/ogr2ogr_lib.cpp | 104 +++++++++++++++++++++---- autotest/utilities/test_ogr2ogr_lib.py | 74 ++++++++++++++++-- 4 files changed, 162 insertions(+), 20 deletions(-) diff --git a/apps/gdal_utils_priv.h b/apps/gdal_utils_priv.h index 5f148e118e57..2aac09d0dd1f 100644 --- a/apps/gdal_utils_priv.h +++ b/apps/gdal_utils_priv.h @@ -85,6 +85,7 @@ struct GDALVectorTranslateOptionsForBinary CPLStringList aosOpenOptions{}; std::string osFormat; GDALVectorTranslateAccessMode eAccessMode = ACCESS_CREATION; + bool bShowUsageIfError = false; /* Allowed input drivers. */ CPLStringList aosAllowInputDrivers{}; diff --git a/apps/ogr2ogr_bin.cpp b/apps/ogr2ogr_bin.cpp index 9e12a64c647c..1fa30d46b5ff 100644 --- a/apps/ogr2ogr_bin.cpp +++ b/apps/ogr2ogr_bin.cpp @@ -104,7 +104,8 @@ MAIN_START(nArgc, papszArgv) if (psOptions == nullptr) { - Usage(); + if (sOptionsForBinary.bShowUsageIfError) + Usage(); goto exit; } diff --git a/apps/ogr2ogr_lib.cpp b/apps/ogr2ogr_lib.cpp index 097e5ba33f2c..0d24991f801d 100644 --- a/apps/ogr2ogr_lib.cpp +++ b/apps/ogr2ogr_lib.cpp @@ -616,7 +616,8 @@ static OGRLayer *GetLayerAndOverwriteIfNecessary(GDALDataset *poDstDS, static std::unique_ptr LoadGeometry(const std::string &osDS, const std::string &osSQL, const std::string &osLyr, - const std::string &osWhere) + const std::string &osWhere, + bool bMakeValid) { auto poDS = std::unique_ptr( GDALDataset::Open(osDS.c_str(), GDAL_OF_VECTOR)); @@ -661,16 +662,39 @@ static std::unique_ptr LoadGeometry(const std::string &osDS, { if (!poSrcGeom->IsValid()) { - CPLError(CE_Warning, CPLE_AppDefined, - "Geometry of feature " CPL_FRMT_GIB " of %s " - "is invalid. Trying to make it valid", - poFeat->GetFID(), osDS.c_str()); + if (!bMakeValid) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Geometry of feature " CPL_FRMT_GIB " of %s " + "is invalid. You can try to make it valid by " + "specifying -makevalid, but the results of " + "the operation should be manually inspected.", + poFeat->GetFID(), osDS.c_str()); + oGC.empty(); + break; + } auto poValid = std::unique_ptr(poSrcGeom->MakeValid()); if (poValid) { + CPLError(CE_Warning, CPLE_AppDefined, + "Geometry of feature " CPL_FRMT_GIB " of %s " + "was invalid and has been made valid, " + "but the results of the operation " + "should be manually inspected.", + poFeat->GetFID(), osDS.c_str()); + oGC.addGeometryDirectly(poValid.release()); } + else + { + CPLError(CE_Failure, CPLE_AppDefined, + "Geometry of feature " CPL_FRMT_GIB " of %s " + "is invalid, and could not been made valid.", + poFeat->GetFID(), osDS.c_str()); + oGC.empty(); + break; + } } else { @@ -2302,7 +2326,8 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, { psOptions->poClipSrc = LoadGeometry(psOptions->osClipSrcDS, psOptions->osClipSrcSQL, - psOptions->osClipSrcLayer, psOptions->osClipSrcWhere); + psOptions->osClipSrcLayer, psOptions->osClipSrcWhere, + psOptions->bMakeValid); if (psOptions->poClipSrc == nullptr) { CPLError(CE_Failure, CPLE_IllegalArg, @@ -2328,12 +2353,37 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, *pbUsageError = TRUE; return nullptr; } + if (psOptions->poClipSrc && !psOptions->poClipSrc->IsValid()) + { + if (!psOptions->bMakeValid) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "-clipsrc geometry is invalid. You can try to make it " + "valid with -makevalid, but the results of the operation " + "should be manually inspected."); + return nullptr; + } + auto poValid = + std::unique_ptr(psOptions->poClipSrc->MakeValid()); + if (!poValid) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "-clipsrc geometry is invalid and cannot be made valid."); + return nullptr; + } + CPLError(CE_Warning, CPLE_AppDefined, + "-clipsrc geometry was invalid and has been made valid, " + "but the results of the operation " + "should be manually inspected."); + psOptions->poClipSrc = std::move(poValid); + } if (!psOptions->osClipDstDS.empty()) { psOptions->poClipDst = LoadGeometry(psOptions->osClipDstDS, psOptions->osClipDstSQL, - psOptions->osClipDstLayer, psOptions->osClipDstWhere); + psOptions->osClipDstLayer, psOptions->osClipDstWhere, + psOptions->bMakeValid); if (psOptions->poClipDst == nullptr) { CPLError(CE_Failure, CPLE_IllegalArg, @@ -2341,6 +2391,30 @@ GDALDatasetH GDALVectorTranslate(const char *pszDest, GDALDatasetH hDstDS, return nullptr; } } + if (psOptions->poClipDst && !psOptions->poClipDst->IsValid()) + { + if (!psOptions->bMakeValid) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "-clipdst geometry is invalid. You can try to make it " + "valid with -makevalid, but the results of the operation " + "should be manually inspected."); + return nullptr; + } + auto poValid = + std::unique_ptr(psOptions->poClipDst->MakeValid()); + if (!poValid) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "-clipdst geometry is invalid and cannot be made valid."); + return nullptr; + } + CPLError(CE_Warning, CPLE_AppDefined, + "-clipdst geometry was invalid and has been made valid, " + "but the results of the operation " + "should be manually inspected."); + psOptions->poClipDst = std::move(poValid); + } GDALDataset *poDS = GDALDataset::FromHandle(hSrcDS); GDALDataset *poODS = nullptr; @@ -7708,9 +7782,10 @@ GDALVectorTranslateOptions *GDALVectorTranslateOptionsNew( psOptions->poClipSrc.reset(poGeom); if (psOptions->poClipSrc == nullptr) { - CPLError(CE_Failure, CPLE_IllegalArg, - "Invalid geometry. Must be a valid POLYGON or " - "MULTIPOLYGON WKT"); + CPLError( + CE_Failure, CPLE_IllegalArg, + "Invalid -clipsrc geometry. Must be a valid POLYGON or " + "MULTIPOLYGON WKT"); return nullptr; } } @@ -7762,9 +7837,10 @@ GDALVectorTranslateOptions *GDALVectorTranslateOptionsNew( psOptions->poClipDst.reset(poGeom); if (psOptions->poClipDst == nullptr) { - CPLError(CE_Failure, CPLE_IllegalArg, - "Invalid geometry. Must be a valid POLYGON or " - "MULTIPOLYGON WKT"); + CPLError( + CE_Failure, CPLE_IllegalArg, + "Invalid -clipdst geometry. Must be a valid POLYGON or " + "MULTIPOLYGON WKT"); return nullptr; } } @@ -7812,6 +7888,8 @@ GDALVectorTranslateOptions *GDALVectorTranslateOptionsNew( catch (const std::exception &err) { CPLError(CE_Failure, CPLE_AppDefined, "%s", err.what()); + if (psOptionsForBinary) + psOptionsForBinary->bShowUsageIfError = true; return nullptr; } } diff --git a/autotest/utilities/test_ogr2ogr_lib.py b/autotest/utilities/test_ogr2ogr_lib.py index 352564bd20f4..b94847000ba0 100755 --- a/autotest/utilities/test_ogr2ogr_lib.py +++ b/autotest/utilities/test_ogr2ogr_lib.py @@ -1206,12 +1206,58 @@ def test_ogr2ogr_lib_clipsrc_discard_lower_dimensionality(): ############################################################################### -# Test -clipsrc with a clip layer with an invalid polygon +# Test -clipsrc/-clipdst with a clip layer with an invalid polygon (specified "inline" as WKT) + + +@pytest.mark.require_geos +@gdaltest.enable_exceptions() +@pytest.mark.parametrize("clipSrc", [True, False]) +def test_ogr2ogr_lib_clip_invalid_polygon_inline(tmp_vsimem, clipSrc): + + srcDS = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + srs = osr.SpatialReference() + srs.ImportFromEPSG(4326) + srcLayer = srcDS.CreateLayer("test", srs=srs, geom_type=ogr.wkbLineString) + f = ogr.Feature(srcLayer.GetLayerDefn()) + f.SetGeometry(ogr.CreateGeometryFromWkt("POINT(0.25 0.25)")) + srcLayer.CreateFeature(f) + f = ogr.Feature(srcLayer.GetLayerDefn()) + f.SetGeometry(ogr.CreateGeometryFromWkt("POINT(-0.5 0.5)")) + srcLayer.CreateFeature(f) + + # Intersection of above geometry with clipSrc bounding box is a point + with pytest.raises(Exception, match="geometry is invalid"): + gdal.VectorTranslate( + "", + srcDS, + format="Memory", + clipSrc="POLYGON((0 0,1 1,0 1,1 0,0 0))" if clipSrc else None, + clipDst="POLYGON((0 0,1 1,0 1,1 0,0 0))" if not clipSrc else None, + ) + + with gdal.quiet_errors(): + ds = gdal.VectorTranslate( + "", + srcDS, + format="Memory", + makeValid=True, + clipSrc="POLYGON((0 0,1 1,0 1,1 0,0 0))" if clipSrc else None, + clipDst="POLYGON((0 0,1 1,0 1,1 0,0 0))" if not clipSrc else None, + ) + lyr = ds.GetLayer(0) + assert lyr.GetFeatureCount() == 1 + ds = None + + +############################################################################### +# Test -clipsrc with a clip layer with an invalid polygon (in a dataset) @pytest.mark.require_driver("GPKG") -@pytest.mark.require_geos(3, 8) -def test_ogr2ogr_lib_clipsrc_invalid_polygon(tmp_vsimem): +@pytest.mark.require_geos +@gdaltest.enable_exceptions() +@pytest.mark.parametrize("clipSrc", [True, False]) +def test_ogr2ogr_lib_clip_invalid_polygon(tmp_vsimem, clipSrc): srcDS = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) srs = osr.SpatialReference() @@ -1235,8 +1281,24 @@ def test_ogr2ogr_lib_clipsrc_invalid_polygon(tmp_vsimem): clip_ds = None # Intersection of above geometry with clipSrc bounding box is a point + with pytest.raises(Exception, match=r"cannot load.*clip geometry"): + gdal.VectorTranslate( + "", + srcDS, + format="Memory", + clipSrc=clip_path if clipSrc else None, + clipDst=clip_path if not clipSrc else None, + ) + with gdal.quiet_errors(): - ds = gdal.VectorTranslate("", srcDS, format="Memory", clipSrc=clip_path) + ds = gdal.VectorTranslate( + "", + srcDS, + format="Memory", + makeValid=True, + clipSrc=clip_path if clipSrc else None, + clipDst=clip_path if not clipSrc else None, + ) lyr = ds.GetLayer(0) assert lyr.GetFeatureCount() == 1 ds = None @@ -1247,7 +1309,7 @@ def test_ogr2ogr_lib_clipsrc_invalid_polygon(tmp_vsimem): @pytest.mark.require_driver("GPKG") -@pytest.mark.require_geos(3, 8) +@pytest.mark.require_geos def test_ogr2ogr_lib_clipsrc_3d_polygon(tmp_vsimem): srcDS = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) @@ -1417,7 +1479,7 @@ def test_ogr2ogr_lib_clipdst_discard_lower_dimensionality(): ############################################################################### -# Test /-clipsrc-clipdst with reprojection +# Test -clipsrc / -clipdst with reprojection @pytest.mark.require_geos From 49465322f146408e685568fb26f7237d463756b0 Mon Sep 17 00:00:00 2001 From: Javier Jimenez Shaw Date: Tue, 25 Jun 2024 13:29:27 +0200 Subject: [PATCH 0240/1119] parse dms in ISG format --- autotest/gdrivers/data/isg/header_dms.isg | 28 ++++++++++++++++ autotest/gdrivers/isg.py | 14 ++++++++ frmts/aaigrid/aaigriddataset.cpp | 39 +++++++++++++++++------ 3 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 autotest/gdrivers/data/isg/header_dms.isg diff --git a/autotest/gdrivers/data/isg/header_dms.isg b/autotest/gdrivers/data/isg/header_dms.isg new file mode 100644 index 000000000000..37f4300d7a7a --- /dev/null +++ b/autotest/gdrivers/data/isg/header_dms.isg @@ -0,0 +1,28 @@ +begin_of_head ================================================ +model name : GSIGEO2024bata +model year : 2024 +model type : gravimetric +data type : geoid +data units : meters +data format : grid +data ordering : N-to-S, W-to-E +ref ellipsoid : GRS80 +ref frame : --- +height datum : --- +tide system : --- +coord type : geodetic +coord units : dms +map projection : --- +EPSG code : 6668 +lat min = 15°00'00" +lat max = 50°00'00" +lon min = 120°00'00" +lon max = 160°00'00" +delta lat = 0°01'00" +delta lon = 0°01'30" +nrows = 2101 +ncols = 1601 +nodata = -9999.0000 +creation date = 27/03/2024 +ISG format = 2.0 +end_of_head ================================================== diff --git a/autotest/gdrivers/isg.py b/autotest/gdrivers/isg.py index 4a22e6bba634..89aae7c3c305 100755 --- a/autotest/gdrivers/isg.py +++ b/autotest/gdrivers/isg.py @@ -123,3 +123,17 @@ def test_isg_header_larger_than_1024bytes(): ds = gdal.Open("data/isg/header_larger_than_1024bytes.isg") expected_gt = [12.99375, 0.0125, 0.0, 47.00416666666666, 0.0, -0.008333333333333333] assert ds.GetGeoTransform() == pytest.approx(expected_gt, rel=1e-8) + + +############################################################################### +# Test if we can read dms angles + + +def test_isg_dms(): + + gdal.ErrorReset() + # Header of https://www.gsi.go.jp/butsuri/data/GSIGEO2024beta.zip + ds = gdal.Open("data/isg/header_dms.isg") + assert gdal.GetLastErrorMsg() == "" + expected_gt = [119.9875, 0.025, 0.0, 50.0083333333, 0.0, -0.01666666666] + assert ds.GetGeoTransform() == pytest.approx(expected_gt, rel=1e-8) diff --git a/frmts/aaigrid/aaigriddataset.cpp b/frmts/aaigrid/aaigriddataset.cpp index 4fc099611788..96d8325a8be1 100644 --- a/frmts/aaigrid/aaigriddataset.cpp +++ b/frmts/aaigrid/aaigriddataset.cpp @@ -863,18 +863,37 @@ int ISGDataset::ParseHeader(const char *pszHeader, const char *) "ISG: coord type = %s not supported", osCoordType.c_str()); return FALSE; } - if (!osCoordUnits.empty() && osCoordUnits != "deg") + + auto parseDMS = [](auto str) { - CPLError(CE_Failure, CPLE_NotSupported, - "ISG: coord units = %s not supported", osCoordUnits.c_str()); - return FALSE; + std::string degreeSymbol{"\xc2\xb0"}; + str.replaceAll(degreeSymbol, "D"); + return CPLDMSToDec(str); + }; + + bool useDMS = false; + if (!osCoordUnits.empty()) + { + if (osCoordUnits == "dms") + { + // CPLDMSToDec does not support the non ascii char for degree used in ISG. + // just replace it with "D" to make it compatible. + useDMS = true; + } + else if (osCoordUnits != "deg") + { + CPLError(CE_Failure, CPLE_NotSupported, + "ISG: coord units = %s not supported", + osCoordUnits.c_str()); + return FALSE; + } } - double dfLatMin = CPLAtof(osLatMin); - double dfLatMax = CPLAtof(osLatMax); - double dfLonMin = CPLAtof(osLonMin); - double dfLonMax = CPLAtof(osLonMax); - double dfDeltaLon = CPLAtof(osDeltaLon); - double dfDeltaLat = CPLAtof(osDeltaLat); + double dfLatMin = useDMS ? parseDMS(osLatMin) : CPLAtof(osLatMin); + double dfLatMax = useDMS ? parseDMS(osLatMax) : CPLAtof(osLatMax); + double dfLonMin = useDMS ? parseDMS(osLonMin) : CPLAtof(osLonMin); + double dfLonMax = useDMS ? parseDMS(osLonMax) : CPLAtof(osLonMax); + double dfDeltaLon = useDMS ? parseDMS(osDeltaLon) : CPLAtof(osDeltaLon); + double dfDeltaLat = useDMS ? parseDMS(osDeltaLat) : CPLAtof(osDeltaLat); if (dfVersion >= 2.0) { dfLatMin -= dfDeltaLat / 2.0; From cf857f24bd7f00466c1ba5320636371b4df3a06f Mon Sep 17 00:00:00 2001 From: Scott Staniewicz Date: Tue, 25 Jun 2024 08:14:16 -0400 Subject: [PATCH 0241/1119] Doc: gdal_merge.py: Add missing `-q` documentation I tried to match the `-q` from `gdal_calc`, as I didn't see a common `options/quiet.rst`. --- doc/source/programs/gdal_merge.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/source/programs/gdal_merge.rst b/doc/source/programs/gdal_merge.rst index d9f04c80c17c..2895eb857818 100644 --- a/doc/source/programs/gdal_merge.rst +++ b/doc/source/programs/gdal_merge.rst @@ -71,6 +71,10 @@ target pixel in the resulting raster nor will it overwrite a valid pixel value. If not specified the aggregate extents of all input files will be used. +.. option:: -q, -quiet + + Suppress progress messages. + .. option:: -v Generate verbose output of mosaicing operations as they are done. From de5a7f8c7002eebeea6a430179d29c20ed9c4e69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A9=8D=E4=B8=B9=E5=B0=BC=20Dan=20Jacobson?= Date: Tue, 25 Jun 2024 21:26:15 +0800 Subject: [PATCH 0242/1119] Doc: Update ogrinfo.rst with tip how to workaround need for input file (#10300) Fixes #10296 --- doc/source/programs/ogrinfo.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/source/programs/ogrinfo.rst b/doc/source/programs/ogrinfo.rst index dd54cff6c4fd..66be7c7106bb 100644 --- a/doc/source/programs/ogrinfo.rst +++ b/doc/source/programs/ogrinfo.rst @@ -529,4 +529,10 @@ Adding a column to an input file: ogrinfo input.shp -sql "ALTER TABLE input ADD fieldX float" +Sometimes there is no input file involved in a calculation. In such cases one may +use the ``:memory:`` input file which is a in-memory empty SQLite file (and the SQLite SQL dialect will be implicitly used). + +.. code-block:: bash + + ogrinfo :memory: -sql "SELECT ST_Buffer(ST_GeomFromText('POINT(0 0)'), 1)" From 6899da0a7d93235982e8edf1bcd32a422d055ff2 Mon Sep 17 00:00:00 2001 From: Andrew Bell Date: Tue, 25 Jun 2024 14:09:08 -0400 Subject: [PATCH 0243/1119] Viewshed: support observers to left and right of input raster (#10264) --- alg/viewshed.cpp | 291 ++++++++++++++++------- alg/viewshed.h | 52 +++- autotest/cpp/test_viewshed.cpp | 230 +++++++++++++----- autotest/utilities/test_gdal_viewshed.py | 2 +- 4 files changed, 428 insertions(+), 147 deletions(-) diff --git a/alg/viewshed.cpp b/alg/viewshed.cpp index 17e2f4ad8b11..18af2999a9eb 100644 --- a/alg/viewshed.cpp +++ b/alg/viewshed.cpp @@ -204,6 +204,8 @@ GDALDatasetH GDALViewshedGenerate( gdal::Viewshed v(oOpts); + if (!pfnProgress) + pfnProgress = GDALDummyProgress; v.run(hBand, pfnProgress, pProgressArg); return GDALDataset::FromHandle(v.output().release()); @@ -269,7 +271,7 @@ double CalcHeightEdge(int i, int j, double Za, double Zb) } // unnamed namespace -/// Calculate the output extent of the output raster in terms of the input raster. +/// Calculate the extent of the output raster in terms of the input raster. /// /// @param nX observer X position in the input raster /// @param nY observer Y position in the input raster @@ -280,12 +282,19 @@ bool Viewshed::calcOutputExtent(int nX, int nY) oOutExtent.xStop = GDALGetRasterBandXSize(pSrcBand); oOutExtent.yStop = GDALGetRasterBandYSize(pSrcBand); - if (nX < 0 || nX >= oOutExtent.xStop || nY < 0 || nY >= oOutExtent.yStop) + if (!oOutExtent.containsY(nY)) { CPLError(CE_Failure, CPLE_AppDefined, - "The observer location falls outside of the DEM area"); + "Observer position above or below the raster " + "not currently supported"); return false; } + if (!oOutExtent.contains(nX, nY)) + { + CPLError(CE_Warning, CPLE_AppDefined, + "NOTE: The observer location falls outside of the DEM area"); + //ABELL - Make sure observer Z is specified. + } constexpr double EPSILON = 1e-8; if (oOpts.maxDistance > 0) @@ -308,15 +317,27 @@ bool Viewshed::calcOutputExtent(int nX, int nY) EPSILON) + (adfInvTransform[5] < 0 ? 1 : 0)); - oOutExtent.xStart = std::max(nXStart, 0); - oOutExtent.yStart = std::max(nYStart, 0); - oOutExtent.xStop = std::min(nXStop, oOutExtent.xStop); - oOutExtent.yStop = std::min(nYStop, oOutExtent.yStop); + // If the limits are invalid, set the window size to zero to trigger the error below. + if (nXStart >= oOutExtent.xStop || nXStop < 0 || + nYStart >= oOutExtent.yStop || nYStop < 0) + { + oOutExtent = Window(); + } + else + { + oOutExtent.xStart = std::max(nXStart, 0); + oOutExtent.xStop = std::min(nXStop, oOutExtent.xStop); + + oOutExtent.yStart = std::max(nYStart, 0); + oOutExtent.yStop = std::min(nYStop, oOutExtent.yStop); + } } if (oOutExtent.xSize() == 0 || oOutExtent.ySize() == 0) { - CPLError(CE_Failure, CPLE_AppDefined, "Invalid target raster size"); + CPLError(CE_Failure, CPLE_AppDefined, + "Invalid target raster size due to transform " + "and/or distance limitation."); return false; } return true; @@ -396,15 +417,18 @@ bool Viewshed::emitProgress(double fraction) /// /// @param nYOffset Y offset of the line being adjusted. /// @param nX X location of the observer. -/// @param pdfNx Pointer to the data at the nX location of the line being adjusted +/// @param vThisLineVal Line height data. /// @return [left, right) Leftmost and one past the rightmost cell in the line within /// the max distance std::pair Viewshed::adjustHeight(int nYOffset, int nX, - double *const pdfNx) + std::vector &vThisLineVal) { int nLeft = 0; int nRight = oCurExtent.xSize(); + // Find the starting point in the raster (nX may be outside) + int nXStart = oCurExtent.clampX(nX); + // If there is a height adjustment factor other than zero or a max distance, // calculate the adjusted height of the cell, stopping if we've exceeded the max // distance. @@ -414,8 +438,10 @@ std::pair Viewshed::adjustHeight(int nYOffset, int nX, const double dfLineX = adfTransform[2] * nYOffset; const double dfLineY = adfTransform[5] * nYOffset; - double *pdfHeight = pdfNx; - for (int nXOffset = 0; nXOffset >= -nX; nXOffset--, pdfHeight--) + // Go left + double *pdfHeight = vThisLineVal.data() + nXStart; + for (int nXOffset = nXStart - nX; nXOffset >= -nX; + nXOffset--, pdfHeight--) { double dfX = adfTransform[1] * nXOffset + dfLineX; double dfY = adfTransform[4] * nXOffset + dfLineY; @@ -428,9 +454,10 @@ std::pair Viewshed::adjustHeight(int nYOffset, int nX, *pdfHeight -= dfHeightAdjFactor * dfR2 + dfZObserver; } - pdfHeight = pdfNx + 1; - for (int nXOffset = 1; nXOffset < oCurExtent.xSize() - nX; - nXOffset++, pdfHeight++) + // Go right. + pdfHeight = vThisLineVal.data() + nXStart + 1; + for (int nXOffset = nXStart - nX + 1; + nXOffset < oCurExtent.xSize() - nX; nXOffset++, pdfHeight++) { double dfX = adfTransform[1] * nXOffset + dfLineX; double dfY = adfTransform[4] * nXOffset + dfLineY; @@ -445,7 +472,8 @@ std::pair Viewshed::adjustHeight(int nYOffset, int nX, } else { - double *pdfHeight = pdfNx - nX; + // No curvature adjustment. Just normalize for the observer height. + double *pdfHeight = vThisLineVal.data(); for (int i = 0; i < oCurExtent.xSize(); ++i) { *pdfHeight -= dfZObserver; @@ -547,20 +575,95 @@ double doMax(int nXOffset, int nYOffset, double dfThisPrev, double dfLast, return std::max(dfEdge, dfDiagonal); } -double doLine(int nXOffset, [[maybe_unused]] int nYOffset, double dfThisPrev, - [[maybe_unused]] double dfLast, - [[maybe_unused]] double dfLastPrev) +} // unnamed namespace + +/// Process the part of the first line to the left of the observer. +/// +/// @param nX X coordinate of the observer. +/// @param iStart X coordinate of the first cell to the left of the observer to be procssed. +/// @param iEnd X coordinate one past the last cell to be processed. +/// @param vResult Vector in which to store the visibility/height results. +/// @param vThisLineVal Height of each cell in the line being processed. +void Viewshed::processFirstLineLeft(int nX, int iStart, int iEnd, + std::vector &vResult, + std::vector &vThisLineVal) { - return CalcHeightLine(nXOffset, dfThisPrev); + // If end is to the right of start, everything is taken care of by right processing. + if (iEnd >= iStart) + return; + + iStart = oCurExtent.clampX(iStart); + + double *pThis = vThisLineVal.data() + iStart; + + // If the start cell is next to the observer, just mark it visible. + if (iStart + 1 == nX || iStart + 1 == oCurExtent.xStop) + { + if (oOpts.outputMode == OutputMode::Normal) + vResult[iStart] = oOpts.visibleVal; + else + setOutput(vResult[iStart], *pThis, *pThis); + iStart--; + pThis--; + } + + // Go from the observer to the left, calculating Z as we go. + for (int iPixel = iStart; iPixel > iEnd; iPixel--, pThis--) + { + int nXOffset = std::abs(iPixel - nX); + double dfZ = CalcHeightLine(nXOffset, *(pThis + 1)); + setOutput(vResult[iPixel], *pThis, dfZ); + } + // For cells outside of the [start, end) range, set the outOfRange value. + std::fill(vResult.begin(), vResult.begin() + iEnd + 1, oOpts.outOfRangeVal); } -} // unnamed namespace +/// Process the part of the first line to the right of the observer. +/// +/// @param nX X coordinate of the observer. +/// @param iStart X coordinate of the first cell to the right of the observer to be processed. +/// @param iEnd X coordinate one past the last cell to be processed. +/// @param vResult Vector in which to store the visibility/height results. +/// @param vThisLineVal Height of each cell in the line being processed. +void Viewshed::processFirstLineRight(int nX, int iStart, int iEnd, + std::vector &vResult, + std::vector &vThisLineVal) +{ + // If start is to the right of end, everything is taken care of by left processing. + if (iStart >= iEnd) + return; + + iStart = oCurExtent.clampX(iStart); + + double *pThis = vThisLineVal.data() + iStart; + + // If the start cell is next to the observer, just mark it visible. + if (iStart - 1 == nX || iStart == oCurExtent.xStart) + { + if (oOpts.outputMode == OutputMode::Normal) + vResult[iStart] = oOpts.visibleVal; + else + setOutput(vResult[iStart], *pThis, *pThis); + iStart++; + pThis++; + } + + // Go from the observer to the right, calculating Z as we go. + for (int iPixel = iStart; iPixel < iEnd; iPixel++, pThis++) + { + int nXOffset = std::abs(iPixel - nX); + double dfZ = CalcHeightLine(nXOffset, *(pThis - 1)); + setOutput(vResult[iPixel], *pThis, dfZ); + } + // For cells outside of the [start, end) range, set the outOfRange value. + std::fill(vResult.begin() + iEnd, vResult.end(), oOpts.outOfRangeVal); +} /// Process a line to the left of the observer. /// /// @param nX X coordinate of the observer. /// @param nYOffset Offset of the line being processed from the observer -/// @param iStart X coordinate of the first cell to the left of the observer to be procssed. +/// @param iStart X coordinate of the first cell to the left of the observer to be processed. /// @param iEnd X coordinate one past the last cell to be processed. /// @param vResult Vector in which to store the visibility/height results. /// @param vThisLineVal Height of each cell in the line being processed. @@ -570,16 +673,33 @@ void Viewshed::processLineLeft(int nX, int nYOffset, int iStart, int iEnd, std::vector &vThisLineVal, std::vector &vLastLineVal) { + // If start to the left of end, everything is taken care of by processing right. + if (iStart <= iEnd) + return; + iStart = oCurExtent.clampX(iStart); + + nYOffset = std::abs(nYOffset); double *pThis = vThisLineVal.data() + iStart; double *pLast = vLastLineVal.data() + iStart; - nYOffset = std::abs(nYOffset); + // If the observer is to the right of the raster, mark the first cell to the left as + // visible. This may mark an out-of-range cell with a value, but this will be fixed + // with the out of range assignment at the end. + if (iStart == oCurExtent.xStop - 1) + { + if (oOpts.outputMode == OutputMode::Normal) + vResult[iStart] = oOpts.visibleVal; + else + setOutput(vResult[iStart], *pThis, *pThis); + iStart--; + pThis--; + pLast--; + } // Go from the observer to the left, calculating Z as we go. for (int iPixel = iStart; iPixel > iEnd; iPixel--, pThis--, pLast--) { int nXOffset = std::abs(iPixel - nX); - double dfZ; if (nXOffset == nYOffset) { @@ -602,7 +722,7 @@ void Viewshed::processLineLeft(int nX, int nYOffset, int iStart, int iEnd, /// /// @param nX X coordinate of the observer. /// @param nYOffset Offset of the line being processed from the observer -/// @param iStart X coordinate of the first cell to the right of the observer to be procssed. +/// @param iStart X coordinate of the first cell to the right of the observer to be processed. /// @param iEnd X coordinate one past the last cell to be processed. /// @param vResult Vector in which to store the visibility/height results. /// @param vThisLineVal Height of each cell in the line being processed. @@ -612,10 +732,28 @@ void Viewshed::processLineRight(int nX, int nYOffset, int iStart, int iEnd, std::vector &vThisLineVal, std::vector &vLastLineVal) { + // If start is to the right of end, everything is taken care of by processing left. + if (iStart >= iEnd) + return; + iStart = oCurExtent.clampX(iStart); + + nYOffset = std::abs(nYOffset); double *pThis = vThisLineVal.data() + iStart; double *pLast = vLastLineVal.data() + iStart; - nYOffset = std::abs(nYOffset); + // If the observer is to the left of the raster, mark the first cell to the right as + // visible. This may mark an out-of-range cell with a value, but this will be fixed + // with the out of range assignment at the end. + if (iStart == 0) + { + if (oOpts.outputMode == OutputMode::Normal) + vResult[iStart] = oOpts.visibleVal; + else + setOutput(vResult[0], *pThis, *pThis); + iStart++; + pThis++; + pLast++; + } // Go from the observer to the right, calculating Z as we go. for (int iPixel = iStart; iPixel < iEnd; iPixel++, pThis++, pLast++) @@ -634,6 +772,7 @@ void Viewshed::processLineRight(int nX, int nYOffset, int iStart, int iEnd, oZcalc(nXOffset, nYOffset, *(pThis - 1), *pLast, *(pLast - 1)); setOutput(vResult[iPixel], *pThis, dfZ); } + // For cells outside of the [start, end) range, set the outOfRange value. std::fill(vResult.begin() + iEnd, vResult.end(), oOpts.outOfRangeVal); } @@ -656,7 +795,7 @@ void Viewshed::setOutput(double &dfResult, double &dfCellVal, double dfZ) dfCellVal = std::max(dfCellVal, dfZ); } -/// Process the first line (the one with the X coordinate the same as the observer). +/// Process the first line (the one with the Y coordinate the same as the observer). /// /// @param nX X location of the observer /// @param nY Y location of the observer @@ -667,60 +806,39 @@ void Viewshed::setOutput(double &dfResult, double &dfCellVal, double dfZ) bool Viewshed::processFirstLine(int nX, int nY, int nLine, std::vector &vLastLineVal) { - int nYOffset = nLine - nY; // Should be zero. + int nYOffset = nLine - nY; + assert(nYOffset == 0); std::vector vResult(oOutExtent.xSize()); std::vector vThisLineVal(oOutExtent.xSize()); if (!readLine(nLine, vThisLineVal.data())) return false; - // This bit is only relevant for the first line. - dfZObserver = oOpts.observer.z + vThisLineVal[nX]; + // If the observer is outside of the raster, take the specified value as the Z height, + // otherwise, take it as an offset from the raster height at that location. + dfZObserver = oOpts.observer.z; + if (oCurExtent.containsX(nX)) + { + dfZObserver += vThisLineVal[nX]; + if (oOpts.outputMode == OutputMode::Normal) + vResult[nX] = oOpts.visibleVal; + } dfHeightAdjFactor = CalcHeightAdjFactor(poDstDS.get(), oOpts.curveCoeff); - // In DEM mode the base is the pre-adjustment value. - // In ground mode the base is zero. + // In DEM mode the base is the pre-adjustment value. In ground mode the base is zero. if (oOpts.outputMode == OutputMode::DEM) vResult = vThisLineVal; - const auto [iLeft, iRight] = - adjustHeight(nYOffset, nX, vThisLineVal.data() + nX); - - if (oOpts.outputMode == OutputMode::Normal) - { - vResult[nX] = oOpts.visibleVal; - if (nX - 1 >= 0) - vResult[nX - 1] = oOpts.visibleVal; - if (nX + 1 < oCurExtent.xSize()) - vResult[nX + 1] = oOpts.visibleVal; - } - else - { - // The minimum observable value isn't well-defined at a distance of one. - // We use the actual cell height. - if (nX - 1 >= 0) - setOutput(vResult[nX - 1], vThisLineVal[nX - 1], - vThisLineVal[nX - 1]); - if (nX + 1 < oCurExtent.xSize()) - setOutput(vResult[nX + 1], vThisLineVal[nX + 1], - vThisLineVal[nX + 1]); - } + // iLeft and iRight are the processing limits for the line. + const auto [iLeft, iRight] = adjustHeight(nYOffset, nX, vThisLineVal); - auto t1 = - std::async(std::launch::async, - [&, left = iLeft]() - { - processLineLeft(nX, nYOffset, nX - 2, left - 1, vResult, - vThisLineVal, vLastLineVal); - }); + auto t1 = std::async( + std::launch::async, [&, left = iLeft]() + { processFirstLineLeft(nX, nX - 1, left - 1, vResult, vThisLineVal); }); - auto t2 = - std::async(std::launch::async, - [&, right = iRight]() - { - processLineRight(nX, nYOffset, nX + 2, right, vResult, - vThisLineVal, vLastLineVal); - }); + auto t2 = std::async( + std::launch::async, [&, right = iRight]() + { processFirstLineRight(nX, nX + 1, right, vResult, vThisLineVal); }); t1.wait(); t2.wait(); @@ -760,21 +878,23 @@ bool Viewshed::processLine(int nX, int nY, int nLine, vResult = vThisLineVal; // Adjust height of the read line. - const auto [iLeft, iRight] = - adjustHeight(nYOffset, nX, vThisLineVal.data() + nX); + const auto [iLeft, iRight] = adjustHeight(nYOffset, nX, vThisLineVal); // Handle the initial position on the line. - if (iLeft < iRight) + if (oCurExtent.containsX(nX)) { - double dfZ; - if (std::abs(nYOffset) == 1) - dfZ = vThisLineVal[nX]; + if (iLeft < iRight) + { + double dfZ; + if (std::abs(nYOffset) == 1) + dfZ = vThisLineVal[nX]; + else + dfZ = CalcHeightLine(nYOffset, vLastLineVal[nX]); + setOutput(vResult[nX], vThisLineVal[nX], dfZ); + } else - dfZ = CalcHeightLine(nYOffset, vLastLineVal[nX]); - setOutput(vResult[nX], vThisLineVal[nX], dfZ); + vResult[nX] = oOpts.outOfRangeVal; } - else - vResult[nX] = oOpts.outOfRangeVal; // process left half then right half of line auto t1 = @@ -820,8 +940,6 @@ bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress, nLineCount = 0; pSrcBand = static_cast(band); - if (!pfnProgress) - pfnProgress = GDALDummyProgress; oProgress = std::bind(pfnProgress, _1, _2, pProgressArg); if (!emitProgress(0)) @@ -842,6 +960,16 @@ bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress, double dfX, dfY; GDALApplyGeoTransform(adfInvTransform.data(), oOpts.observer.x, oOpts.observer.y, &dfX, &dfY); + if (!GDALIsValueInRange(dfX)) + { + CPLError(CE_Failure, CPLE_AppDefined, "Observer X value out of range"); + return false; + } + if (!GDALIsValueInRange(dfY)) + { + CPLError(CE_Failure, CPLE_AppDefined, "Observer Y value out of range"); + return false; + } int nX = static_cast(dfX); int nY = static_cast(dfY); @@ -850,6 +978,7 @@ bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress, return false; // normalize horizontal index to [ 0, oOutExtent.xSize() ) + //ABELL - verify this won't underflow. oCurExtent = oOutExtent; oCurExtent.shiftX(-oOutExtent.xStart); nX -= oOutExtent.xStart; @@ -858,8 +987,6 @@ bool Viewshed::run(GDALRasterBandH band, GDALProgressFunc pfnProgress, if (!createOutputDataset()) return false; - oZcalc = doLine; - std::vector vFirstLineVal(oCurExtent.xSize()); if (!processFirstLine(nX, nY, nY, vFirstLineVal)) diff --git a/alg/viewshed.h b/alg/viewshed.h index 176ff1bf8d9e..349ccc8e6ba3 100644 --- a/alg/viewshed.h +++ b/alg/viewshed.h @@ -25,6 +25,7 @@ * DEALINGS IN THE SOFTWARE. ****************************************************************************/ +#include #include #include #include @@ -77,7 +78,7 @@ class Viewshed }; /** - * A window in a raster including pixels in [xStart, xEnd) and [yStart, yEnd). + * A window in a raster including pixels in [xStart, xStop) and [yStart, yStop). */ struct Window { @@ -87,18 +88,52 @@ class Viewshed int yStop{}; //!< Y end position /// \brief Window size in the X direction. - int xSize() + int xSize() const { return xStop - xStart; } /// \brief Window size in the Y direction. - int ySize() + int ySize() const { return yStop - yStart; } - /// \brief Shift the X dimension by nShift + /// \brief Determine if the X window contains the index. + /// \param nX Index to check + /// \return True if the index is contained, false otherwise. + bool containsX(int nX) const + { + return nX >= xStart && nX < xStop; + } + + /// \brief Determine if the Y window contains the index. + /// \param nY Index to check + /// \return True if the index is contained, false otherwise. + bool containsY(int nY) const + { + return nY >= xStart && nY < yStop; + } + + /// \brief Determine if the window contains the index. + /// \param nX X coordinate of the index to check + /// \param nY Y coordinate of the index to check + /// \return True if the index is contained, false otherwise. + bool contains(int nX, int nY) const + { + return containsX(nX) && containsY(nY); + } + + /// \brief Clamp the argument to be in the window in the X dimension. + /// \param nX Value to clamp. + /// \return Clamped value. + int clampX(int nX) const + { + return xSize() ? std::clamp(nX, xStart, xStop - 1) : xStart; + } + + /// \brief Shift the X dimension by nShift. + /// \param nShift Amount to shift void shiftX(int nShift) { xStart += nShift; @@ -192,6 +227,12 @@ class Viewshed std::vector &vLastLineVal); bool processFirstLine(int nX, int nY, int nLine, std::vector &vLastLineVal); + void processFirstLineLeft(int nX, int iStart, int iEnd, + std::vector &vResult, + std::vector &vThisLineVal); + void processFirstLineRight(int nX, int iStart, int iEnd, + std::vector &vResult, + std::vector &vThisLineVal); void processLineLeft(int nX, int nYOffset, int iStart, int iEnd, std::vector &vResult, std::vector &vThisLineVal, @@ -200,7 +241,8 @@ class Viewshed std::vector &vResult, std::vector &vThisLineVal, std::vector &vLastLineVal); - std::pair adjustHeight(int iLine, int nX, double *const pdfNx); + std::pair adjustHeight(int iLine, int nX, + std::vector &thisLineVal); bool calcOutputExtent(int nX, int nY); bool createOutputDataset(); bool lineProgress(); diff --git a/autotest/cpp/test_viewshed.cpp b/autotest/cpp/test_viewshed.cpp index 0bda10763dfd..7311265cb834 100644 --- a/autotest/cpp/test_viewshed.cpp +++ b/autotest/cpp/test_viewshed.cpp @@ -62,21 +62,19 @@ Viewshed::Options stdOptions(const Coord &observer) return stdOptions(observer.first, observer.second); } -DatasetPtr runViewshed(int8_t *in, int edgeLength, +DatasetPtr runViewshed(const int8_t *in, int xlen, int ylen, const Viewshed::Options &opts) { Viewshed v(opts); GDALDriver *driver = (GDALDriver *)GDALGetDriverByName("MEM"); - GDALDataset *dataset = - driver->Create("", edgeLength, edgeLength, 1, GDT_Int8, nullptr); + GDALDataset *dataset = driver->Create("", xlen, ylen, 1, GDT_Int8, nullptr); EXPECT_TRUE(dataset); dataset->SetGeoTransform(identity.data()); GDALRasterBand *band = dataset->GetRasterBand(1); EXPECT_TRUE(band); - CPLErr err = - band->RasterIO(GF_Write, 0, 0, edgeLength, edgeLength, in, edgeLength, - edgeLength, GDT_Int8, 0, 0, nullptr); + CPLErr err = band->RasterIO(GF_Write, 0, 0, xlen, ylen, (int8_t *)in, xlen, + ylen, GDT_Int8, 0, 0, nullptr); EXPECT_EQ(err, CE_None); EXPECT_TRUE(v.run(band)); @@ -88,8 +86,9 @@ DatasetPtr runViewshed(int8_t *in, int edgeLength, TEST(Viewshed, all_visible) { // clang-format off - const int edge = 3; - std::array in + const int xlen = 3; + const int ylen = 3; + std::array in { 1, 2, 3, 4, 5, 6, @@ -98,12 +97,12 @@ TEST(Viewshed, all_visible) // clang-format on SCOPED_TRACE("all_visible"); - DatasetPtr output = runViewshed(in.data(), edge, stdOptions(1, 1)); + DatasetPtr output = runViewshed(in.data(), xlen, ylen, stdOptions(1, 1)); - std::array out; + std::array out; GDALRasterBand *band = output->GetRasterBand(1); - CPLErr err = band->RasterIO(GF_Read, 0, 0, edge, edge, out.data(), edge, - edge, GDT_Int8, 0, 0, nullptr); + CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen, + ylen, GDT_Int8, 0, 0, nullptr); EXPECT_EQ(err, CE_None); for (uint8_t o : out) @@ -113,8 +112,9 @@ TEST(Viewshed, all_visible) TEST(Viewshed, simple_height) { // clang-format off - const int edge = 5; - std::array in + const int xlen = 5; + const int ylen = 5; + std::array in { -1, 0, 1, 0, -1, -1, 2, 0, 4, -1, @@ -123,7 +123,7 @@ TEST(Viewshed, simple_height) -1, 0, 0, 3, -1 }; - std::array observable + std::array observable { 4, 2, 0, 4, 8, 3, 2, 0, 4, 3, @@ -136,17 +136,18 @@ TEST(Viewshed, simple_height) { SCOPED_TRACE("simple_height:normal"); - DatasetPtr output = runViewshed(in.data(), 5, stdOptions(2, 2)); + DatasetPtr output = + runViewshed(in.data(), xlen, ylen, stdOptions(2, 2)); - std::array out; + std::array out; GDALRasterBand *band = output->GetRasterBand(1); - CPLErr err = band->RasterIO(GF_Read, 0, 0, edge, edge, out.data(), edge, - edge, GDT_Int8, 0, 0, nullptr); + CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen, + ylen, GDT_Int8, 0, 0, nullptr); EXPECT_EQ(err, CE_None); // We expect the cell to be observable when the input is higher than the observable // height. - std::array expected; + std::array expected; for (size_t i = 0; i < in.size(); ++i) expected[i] = (in[i] >= observable[i] ? 127 : 0); @@ -154,19 +155,20 @@ TEST(Viewshed, simple_height) } { - std::array dem; + std::array dem; SCOPED_TRACE("simple_height:dem"); Viewshed::Options opts = stdOptions(2, 2); opts.outputMode = Viewshed::OutputMode::DEM; - DatasetPtr output = runViewshed(in.data(), 5, opts); + + DatasetPtr output = runViewshed(in.data(), xlen, ylen, opts); GDALRasterBand *band = output->GetRasterBand(1); - CPLErr err = band->RasterIO(GF_Read, 0, 0, edge, edge, dem.data(), edge, - edge, GDT_Float64, 0, 0, nullptr); + CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, dem.data(), xlen, + ylen, GDT_Float64, 0, 0, nullptr); EXPECT_EQ(err, CE_None); // DEM values are observable values clamped at 0. Not sure why. - std::array expected = observable; + std::array expected = observable; for (double &d : expected) d = std::max(0.0, d); @@ -175,18 +177,18 @@ TEST(Viewshed, simple_height) } { - std::array ground; + std::array ground; SCOPED_TRACE("simple_height:ground"); Viewshed::Options opts = stdOptions(2, 2); opts.outputMode = Viewshed::OutputMode::Ground; - DatasetPtr output = runViewshed(in.data(), 5, opts); + DatasetPtr output = runViewshed(in.data(), xlen, ylen, opts); GDALRasterBand *band = output->GetRasterBand(1); - CPLErr err = band->RasterIO(GF_Read, 0, 0, edge, edge, ground.data(), - edge, edge, GDT_Float64, 0, 0, nullptr); + CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, ground.data(), + xlen, ylen, GDT_Float64, 0, 0, nullptr); EXPECT_EQ(err, CE_None); - std::array expected; + std::array expected; for (size_t i = 0; i < expected.size(); ++i) expected[i] = std::max(0.0, observable[i] - in[i]); @@ -198,46 +200,34 @@ TEST(Viewshed, simple_height) // Addresses cases in #9501 TEST(Viewshed, dem_vs_ground) { - auto process = [](const std::array &in, Viewshed::Options &opts) + // Run gdal_viewshed on the input 8 x 1 array in both ground and dem mode and + // verify the results are what are expected. + auto run = [](const std::array &in, Coord observer, + const std::array &ground, + const std::array &dem) { - Viewshed v(opts); - - GDALDriver *driver = (GDALDriver *)GDALGetDriverByName("MEM"); - // 8 cols x 1 row - GDALDataset *dataset = driver->Create("", 8, 1, 1, GDT_Int8, nullptr); - EXPECT_TRUE(dataset); - dataset->SetGeoTransform(identity.data()); - GDALRasterBand *band = dataset->GetRasterBand(1); - EXPECT_TRUE(band); - CPLErr err = band->RasterIO(GF_Write, 0, 0, 8, 1, (void *)in.data(), 8, - 1, GDT_Int8, 0, 0, nullptr); - EXPECT_EQ(err, CE_None); - - EXPECT_TRUE(v.run(band)); - return v.output(); - }; + const int xlen = 8; + const int ylen = 1; - auto run = [&process](const std::array &in, Coord observer, - const std::array &ground, - const std::array &dem) - { + std::array out; Viewshed::Options opts = stdOptions(observer); - - std::array out; opts.outputMode = Viewshed::OutputMode::Ground; - DatasetPtr ds = process(in, opts); + + // Verify ground mode. + DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts); GDALRasterBand *band = ds->GetRasterBand(1); - CPLErr err = band->RasterIO(GF_Read, 0, 0, 8, 1, out.data(), 8, 1, - GDT_Float64, 0, 0, nullptr); + CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen, + ylen, GDT_Float64, 0, 0, nullptr); EXPECT_EQ(err, CE_None); for (size_t i = 0; i < ground.size(); ++i) EXPECT_DOUBLE_EQ(out[i], ground[i]); + // Verify DEM mode. opts.outputMode = Viewshed::OutputMode::DEM; - ds = process(in, opts); + ds = runViewshed(in.data(), xlen, ylen, opts); band = ds->GetRasterBand(1); - err = band->RasterIO(GF_Read, 0, 0, 8, 1, out.data(), 8, 1, GDT_Float64, - 0, 0, nullptr); + err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen, ylen, + GDT_Float64, 0, 0, nullptr); EXPECT_EQ(err, CE_None); for (size_t i = 0; i < dem.size(); ++i) EXPECT_DOUBLE_EQ(out[i], dem[i]); @@ -257,4 +247,126 @@ TEST(Viewshed, dem_vs_ground) {0, 0, 0, 3 / 2.0, 2, 15 / 4.0, 24 / 5.0, 35 / 6.0}); } +// Test an observer to the right of the raster. +TEST(Viewshed, oor_right) +{ + // clang-format off + const int xlen = 5; + const int ylen = 3; + std::array in + { + 1, 2, 0, 4, 1, + 0, 0, 2, 1, 0, + 1, 0, 0, 3, 3 + }; + // clang-format on + + { + Viewshed::Options opts = stdOptions(6, 1); + opts.outputMode = Viewshed::OutputMode::DEM; + DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts); + GDALRasterBand *band = ds->GetRasterBand(1); + std::array out; + CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen, + ylen, GDT_Float64, 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + + // clang-format off + std::array expected + { + 16 / 3.0, 29 / 6.0, 13 / 3.0, 1, 1, + 3, 2.5, 4 / 3.0, 0, 0, + 13 / 3.0, 23 / 6.0, 10 / 3.0, 3, 3 + }; + // clang-format on + + for (size_t i = 0; i < out.size(); ++i) + EXPECT_DOUBLE_EQ(out[i], expected[i]); + } + + { + Viewshed::Options opts = stdOptions(6, 2); + opts.outputMode = Viewshed::OutputMode::DEM; + DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts); + GDALRasterBand *band = ds->GetRasterBand(1); + std::array out; + CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen, + ylen, GDT_Float64, 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + + // clang-format off + std::array expected + { + 26 / 5.0, 17 / 4.0, 11 / 3.0, .5, 1, + 6, 4.5, 3, 1.5, 0, + 9, 7.5, 6, 4.5, 3 + }; + // clang-format on + + for (size_t i = 0; i < out.size(); ++i) + EXPECT_DOUBLE_EQ(out[i], expected[i]); + } +} + +// Test an observer to the right of the raster. +TEST(Viewshed, oor_left) +{ + // clang-format off + const int xlen = 5; + const int ylen = 3; + std::array in + { + 1, 2, 0, 4, 1, + 0, 0, 2, 1, 0, + 1, 0, 0, 3, 3 + }; + // clang-format on + + { + Viewshed::Options opts = stdOptions(-2, 1); + opts.outputMode = Viewshed::OutputMode::DEM; + DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts); + GDALRasterBand *band = ds->GetRasterBand(1); + std::array out; + CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen, + ylen, GDT_Float64, 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + + // clang-format off + std::array expected + { + 1, 1, 2, 2.5, 4.5, + 0, 0, 0, 2.5, 3, + 1, 1, 1, 1.5, 3.5 + }; + // clang-format on + + for (size_t i = 0; i < out.size(); ++i) + EXPECT_DOUBLE_EQ(out[i], expected[i]); + } + + { + Viewshed::Options opts = stdOptions(-2, 2); + opts.outputMode = Viewshed::OutputMode::DEM; + DatasetPtr ds = runViewshed(in.data(), xlen, ylen, opts); + GDALRasterBand *band = ds->GetRasterBand(1); + std::array out; + CPLErr err = band->RasterIO(GF_Read, 0, 0, xlen, ylen, out.data(), xlen, + ylen, GDT_Float64, 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + + // clang-format off + std::array expected + { + 1, .5, 5 / 3.0, 2.25, 4.2, + 0, .5, 1, 2.5, 3.1, + 1, 1.5, 2, 2.5, 3.6 + }; + // clang-format on + + for (size_t i = 0; i < out.size(); ++i) + EXPECT_DOUBLE_EQ(out[i], expected[i]); + } +} + } // namespace gdal diff --git a/autotest/utilities/test_gdal_viewshed.py b/autotest/utilities/test_gdal_viewshed.py index e2504f66c2d5..42dadb733cf8 100755 --- a/autotest/utilities/test_gdal_viewshed.py +++ b/autotest/utilities/test_gdal_viewshed.py @@ -374,7 +374,7 @@ def test_gdal_viewshed_invalid_observer_point(gdal_viewshed_path, tmp_path): _, err = gdaltest.runexternal_out_and_err( f"{gdal_viewshed_path} -ox 0 -oy 0 ../gdrivers/data/n43.tif {tmp_path}/tmp.tif" ) - assert "The observer location falls outside of the DEM area" in err + assert "Observer position above or below" in err ############################################################################### From f160dfd68e518d14149aed751662065515bb065b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 25 Jun 2024 22:31:51 +0200 Subject: [PATCH 0244/1119] ecw.rst: mention libecwj2-3.3.patch --- doc/source/drivers/raster/ecw.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/drivers/raster/ecw.rst b/doc/source/drivers/raster/ecw.rst index d3af220f94c4..3029927610d3 100644 --- a/doc/source/drivers/raster/ecw.rst +++ b/doc/source/drivers/raster/ecw.rst @@ -301,4 +301,5 @@ See Also - Support for non-GDAL specific issues should be directed to the `Hexagon Geospatial public forum `__ +- Community contributed `patches `__ to apply to ECW SDK 3.3 sources - `GDAL ECW Build Hints `__ From 08cbb83d7b1b0424192323bb603805650951f14b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 26 Jun 2024 00:02:01 +0200 Subject: [PATCH 0245/1119] GeoJSON: avoid multiple debug messages about mixed-geometry type --- ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp b/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp index 0a067bf46abf..6861de10d797 100644 --- a/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp +++ b/ogr/ogrsf_frmts/geojson/ogrgeojsonreader.cpp @@ -1954,7 +1954,7 @@ bool OGRGeoJSONUpdateLayerGeomType(bool &bFirstGeom, { // ok } - else if (eGeomType != eLayerGeomType) + else if (eGeomType != eLayerGeomType && eLayerGeomType != wkbUnknown) { CPLDebug("GeoJSON", "Detected layer of mixed-geometry type features."); eLayerGeomType = wkbUnknown; From 54f2209e2cb975318f0c7cc5814b8230c75c2006 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 25 Jun 2024 22:44:19 +0200 Subject: [PATCH 0246/1119] Add OGRCurve::reversePoints(), and deprecated OGRLinearRing::reverseWindingOrder() --- autotest/cpp/test_ogr.cpp | 25 +++++++++++++++++++ ogr/ogr_geometry.h | 13 ++++++++-- ogr/ogrcompoundcurve.cpp | 18 +++++++++++++ ogr/ogrcurve.cpp | 12 +++++++++ ogr/ogrcurvecollection.cpp | 25 +++++++++++++++++++ ogr/ogrlinearring.cpp | 15 +++-------- ogr/ogrpgeogeometry.cpp | 8 +++--- .../parquet/ogrparquetwriterlayer.cpp | 2 +- 8 files changed, 100 insertions(+), 18 deletions(-) diff --git a/autotest/cpp/test_ogr.cpp b/autotest/cpp/test_ogr.cpp index 90806d2e0da7..498ba17f627f 100644 --- a/autotest/cpp/test_ogr.cpp +++ b/autotest/cpp/test_ogr.cpp @@ -4175,4 +4175,29 @@ TEST_F(test_ogr, OGRGeometry_removeEmptyParts) } } +// Test OGRCurve::reversePoints() +TEST_F(test_ogr, OGRCurve_reversePoints) +{ + { + OGRGeometry *poGeom = nullptr; + OGRGeometryFactory::createFromWkt( + "COMPOUNDCURVE ZM (CIRCULARSTRING ZM (0 0 10 20,1 1 11 21,2 0 12 " + "22),(2 0 12 22,3 0 13 2))", + nullptr, &poGeom); + ASSERT_NE(poGeom, nullptr); + poGeom->toCurve()->reversePoints(); + char *pszWKT = nullptr; + poGeom->exportToWkt(&pszWKT, wkbVariantIso); + EXPECT_TRUE(pszWKT != nullptr); + if (pszWKT) + { + EXPECT_STREQ( + pszWKT, "COMPOUNDCURVE ZM ((3 0 13 2,2 0 12 22),CIRCULARSTRING " + "ZM (2 0 12 22,1 1 11 21,0 0 10 20))"); + } + CPLFree(pszWKT); + delete poGeom; + } +} + } // namespace diff --git a/ogr/ogr_geometry.h b/ogr/ogr_geometry.h index 0c46c2fa6a89..3a957c5e8e08 100644 --- a/ogr/ogr_geometry.h +++ b/ogr/ogr_geometry.h @@ -1408,6 +1408,7 @@ class CPL_DLL OGRCurve : public OGRGeometry virtual double get_GeodesicArea( const OGRSpatialReference *poSRSOverride = nullptr) const = 0; virtual int isClockwise() const; + virtual void reversePoints() = 0; /** Down-cast to OGRSimpleCurve*. * Implies prior checking that wkbFlatten(getGeometryType()) == @@ -1716,7 +1717,7 @@ class CPL_DLL OGRSimpleCurve : public OGRCurve void addSubLineString(const OGRLineString *, int nStartVertex = 0, int nEndVertex = -1); - void reversePoints(void); + void reversePoints() override; virtual OGRPointIterator *getPointIterator() const override; // non-standard from OGRGeometry @@ -1901,7 +1902,12 @@ class CPL_DLL OGRLinearRing : public OGRLineString // Non standard. virtual const char *getGeometryName() const override; virtual OGRLinearRing *clone() const override; - virtual void reverseWindingOrder(); + + //! @cond Doxygen_Suppress + void reverseWindingOrder() + CPL_WARN_DEPRECATED("Use reversePoints() instead"); + //! @endcond + virtual void closeRings() override; OGRBoolean isPointInRing(const OGRPoint *pt, int bTestEnvelope = TRUE) const; @@ -2153,6 +2159,8 @@ class CPL_DLL OGRCurveCollection bool hasEmptyParts() const; void removeEmptyParts(); + void reversePoints(); + OGRErr transform(OGRGeometry *poGeom, OGRCoordinateTransformation *poCT); void flattenTo2D(OGRGeometry *poGeom); void segmentize(double dfMaxLength); @@ -2317,6 +2325,7 @@ class CPL_DLL OGRCompoundCurve : public OGRCurve double dfToleranceEps = DEFAULT_TOLERANCE_EPSILON); OGRCurve *stealCurve(int); virtual OGRPointIterator *getPointIterator() const override; + void reversePoints() override; // Non-standard from OGRGeometry. virtual OGRwkbGeometryType getGeometryType() const override; diff --git a/ogr/ogrcompoundcurve.cpp b/ogr/ogrcompoundcurve.cpp index c3728dc7dd1e..0b2448f241c1 100644 --- a/ogr/ogrcompoundcurve.cpp +++ b/ogr/ogrcompoundcurve.cpp @@ -988,3 +988,21 @@ void OGRCompoundCurve::removeEmptyParts() { oCC.removeEmptyParts(); } + +/************************************************************************/ +/* reversePoints() */ +/************************************************************************/ + +/** + * \brief Reverse point order. + * + * This method updates the points in this curve in place + * reversing the point ordering (first for last, etc) and component ordering. + * + * @since 3.10 + */ +void OGRCompoundCurve::reversePoints() + +{ + oCC.reversePoints(); +} diff --git a/ogr/ogrcurve.cpp b/ogr/ogrcurve.cpp index b12b146cec1b..73ad9befdcd5 100644 --- a/ogr/ogrcurve.cpp +++ b/ogr/ogrcurve.cpp @@ -854,3 +854,15 @@ int OGRCurve::isClockwise() const return dfSum < 0; } + +/** + * \fn void OGRCurve::reversePoints(); + * + * \brief Reverse point order. + * + * This method updates the points in this curve in place + * reversing the point ordering (first for last, etc) and component ordering + * for a compound curve. + * + * @since 3.10 + */ diff --git a/ogr/ogrcurvecollection.cpp b/ogr/ogrcurvecollection.cpp index cde915c87a04..e17429c45d6a 100644 --- a/ogr/ogrcurvecollection.cpp +++ b/ogr/ogrcurvecollection.cpp @@ -791,4 +791,29 @@ void OGRCurveCollection::removeEmptyParts() } } +/************************************************************************/ +/* reversePoints() */ +/************************************************************************/ + +/** + * \brief Reverse point order. + * + * This method updates the points in this curve in place + * reversing the point ordering (first for last, etc) and component ordering. + * + * @since 3.10 + */ +void OGRCurveCollection::reversePoints() + +{ + for (int i = 0; i < nCurveCount / 2; ++i) + { + std::swap(papoCurves[i], papoCurves[nCurveCount - 1 - i]); + } + for (int i = 0; i < nCurveCount; ++i) + { + papoCurves[i]->reversePoints(); + } +} + //! @endcond diff --git a/ogr/ogrlinearring.cpp b/ogr/ogrlinearring.cpp index e98f92a62a3a..f9a11cf70528 100644 --- a/ogr/ogrlinearring.cpp +++ b/ogr/ogrlinearring.cpp @@ -448,24 +448,17 @@ OGRLinearRing *OGRLinearRing::clone() const /* reverseWindingOrder() */ /************************************************************************/ +//! @cond Doxygen_Suppress /** Reverse order of points. */ void OGRLinearRing::reverseWindingOrder() { - OGRPoint pointA; - OGRPoint pointB; - - for (int i = 0; i < nPointCount / 2; i++) - { - getPoint(i, &pointA); - const int pos = nPointCount - i - 1; - getPoint(pos, &pointB); - setPoint(i, &pointB); - setPoint(pos, &pointA); - } + reversePoints(); } +//! @endcond + /************************************************************************/ /* closeRing() */ /************************************************************************/ diff --git a/ogr/ogrpgeogeometry.cpp b/ogr/ogrpgeogeometry.cpp index ccb5387b7a7f..57a781519ea8 100644 --- a/ogr/ogrpgeogeometry.cpp +++ b/ogr/ogrpgeogeometry.cpp @@ -1004,7 +1004,7 @@ id,WKT assert(poRing); // Outer ring must be clockwise. if (!poRing->isClockwise()) - poRing->reverseWindingOrder(); + poRing->reversePoints(); } else { @@ -1012,7 +1012,7 @@ id,WKT assert(poRing); // Inner rings should be anti-clockwise. if (poRing->isClockwise()) - poRing->reverseWindingOrder(); + poRing->reversePoints(); } int nRingNumPoints = poRing->getNumPoints(); @@ -1260,7 +1260,7 @@ id,WKT assert(poRing != nullptr); // Outer ring must be clockwise. if (!poRing->isClockwise()) - poRing->reverseWindingOrder(); + poRing->reversePoints(); } else { @@ -1268,7 +1268,7 @@ id,WKT assert(poRing != nullptr); // Inner rings should be anti-clockwise. if (poRing->isClockwise()) - poRing->reverseWindingOrder(); + poRing->reversePoints(); } int nRingNumPoints = poRing->getNumPoints(); diff --git a/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp b/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp index 5241ac22228a..05203ca985f9 100644 --- a/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp +++ b/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp @@ -1148,7 +1148,7 @@ void OGRParquetWriterLayer::FixupGeometryBeforeWriting(OGRGeometry *poGeom) if ((bFirstRing && poRing->isClockwise()) || (!bFirstRing && !poRing->isClockwise())) { - poRing->reverseWindingOrder(); + poRing->reversePoints(); } bFirstRing = false; } From e20037f47b0e173e18bc61bb9b08d45c9cdc6157 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 25 Jun 2024 22:44:40 +0200 Subject: [PATCH 0247/1119] GML geometry reader: add support for gml:OrientableCurve Fixes #10301 --- autotest/ogr/ogr_gml_geom.py | 30 +++++++++++++++++++++++ ogr/gml2ogrgeometry.cpp | 46 ++++++++++++++++++++++++++---------- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/autotest/ogr/ogr_gml_geom.py b/autotest/ogr/ogr_gml_geom.py index 46ddda5f0e60..2343e8f7bf3b 100755 --- a/autotest/ogr/ogr_gml_geom.py +++ b/autotest/ogr/ogr_gml_geom.py @@ -1579,6 +1579,17 @@ def test_gml_invalid_geoms(): '0 0 4 0 4 4 0 4 0 0', "POLYGON ((0 0,4 0,4 4,0 4,0 0))", ), + ("", None), + ("", None), + ("", None), + ( + "", + None, + ), + ( + "0 0", + None, + ), ("", None), ("", None), ("", None), @@ -3003,3 +3014,22 @@ def test_gml_read_gml_ArcByCenterPoint_projected_crs_northing_easting(): """ ) assert g is not None + + +############################################################################### +# Test reading an OrientableCurve + + +def test_gml_OrientableCurve(): + + g = ogr.CreateGeometryFromGML( + """ 0 1 2 3 """ + ) + assert g is not None + assert g.ExportToWkt() == "LINESTRING (0 1,2 3)" + + g = ogr.CreateGeometryFromGML( + """ 0 1 2 3 """ + ) + assert g is not None + assert g.ExportToWkt() == "LINESTRING (2 3,0 1)" diff --git a/ogr/gml2ogrgeometry.cpp b/ogr/gml2ogrgeometry.cpp index 1b5714777939..70cbe1706b84 100644 --- a/ogr/gml2ogrgeometry.cpp +++ b/ogr/gml2ogrgeometry.cpp @@ -2898,19 +2898,7 @@ static std::unique_ptr GML2OGRGeometry_XMLNode_Internal( // correct orientation of the line string if (bEdgeOrientation != bOrientation) { - int iStartCoord = 0; - int iEndCoord = poLineString->getNumPoints() - 1; - OGRPoint oTempStartPoint; - OGRPoint oTempEndPoint; - while (iStartCoord < iEndCoord) - { - poLineString->getPoint(iStartCoord, &oTempStartPoint); - poLineString->getPoint(iEndCoord, &oTempEndPoint); - poLineString->setPoint(iStartCoord, &oTempEndPoint); - poLineString->setPoint(iEndCoord, &oTempStartPoint); - iStartCoord++; - iEndCoord--; - } + poLineString->reversePoints(); } return poLineString; } @@ -3642,6 +3630,38 @@ static std::unique_ptr GML2OGRGeometry_XMLNode_Internal( return poGeom; } + /* -------------------------------------------------------------------- */ + /* OrientableCurve */ + /* -------------------------------------------------------------------- */ + if (EQUAL(pszBaseGeometry, "OrientableCurve")) + { + // Find baseCurve. + const CPLXMLNode *psChild = FindBareXMLChild(psNode, "baseCurve"); + + psChild = GetChildElement(psChild); + if (psChild == nullptr) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Missing for OrientableCurve."); + return nullptr; + } + + auto poGeom = GML2OGRGeometry_XMLNode_Internal( + psChild, nPseudoBoolGetSecondaryGeometryOption, nRecLevel + 1, + nSRSDimension, pszSRSName); + if (!poGeom || !OGR_GT_IsCurve(poGeom->getGeometryType())) + { + CPLError(CE_Failure, CPLE_AppDefined, + "baseCurve of OrientableCurve is not a curve."); + return nullptr; + } + if (!GetElementOrientation(psNode)) + { + poGeom->toCurve()->reversePoints(); + } + return poGeom; + } + /* -------------------------------------------------------------------- */ /* OrientableSurface */ /* -------------------------------------------------------------------- */ From 9a15d7a0fd944f8a770130adc47c9d75b7820500 Mon Sep 17 00:00:00 2001 From: Krzysztof Dyba <35004826+kadyb@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:13:54 +0200 Subject: [PATCH 0248/1119] gdal2tiles: update links in `generate_leaflet()` (#10303) * update links in `generate_leaflet()` * remove OSM Toner (Fixes #10304) --- swig/python/gdal-utils/osgeo_utils/gdal2tiles.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py b/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py index 69639d3823ec..a7ce514be53e 100644 --- a/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py +++ b/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py @@ -3979,13 +3979,10 @@ def generate_leaflet(self) -> str: // Base layers // .. OpenStreetMap - var osm = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {attribution: '© OpenStreetMap contributors', minZoom: %(minzoom)s, maxZoom: %(maxzoom)s}); + var osm = L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {attribution: '© OpenStreetMap contributors', minZoom: %(minzoom)s, maxZoom: %(maxzoom)s}); // .. CartoDB Positron - var cartodb = L.tileLayer('http://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {attribution: '© OpenStreetMap contributors, © CartoDB', minZoom: %(minzoom)s, maxZoom: %(maxzoom)s}); - - // .. OSM Toner - var toner = L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png', {attribution: 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.', minZoom: %(minzoom)s, maxZoom: %(maxzoom)s}); + var cartodb = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {attribution: '© OpenStreetMap contributors, © CartoDB', minZoom: %(minzoom)s, maxZoom: %(maxzoom)s}); // .. White background var white = L.tileLayer("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAAA1BMVEX///+nxBvIAAAAH0lEQVQYGe3BAQ0AAADCIPunfg43YAAAAAAAAAAA5wIhAAAB9aK9BAAAAABJRU5ErkJggg==", {minZoom: %(minzoom)s, maxZoom: %(maxzoom)s}); @@ -4002,7 +3999,7 @@ def generate_leaflet(self) -> str: layers: [osm] }); - var basemaps = {"OpenStreetMap": osm, "CartoDB Positron": cartodb, "Stamen Toner": toner, "Without background": white} + var basemaps = {"OpenStreetMap": osm, "CartoDB Positron": cartodb, "Without background": white} var overlaymaps = {"Layer": lyr} // Title @@ -4018,7 +4015,7 @@ def generate_leaflet(self) -> str: title.addTo(map); // Note - var src = 'Generated by GDAL2Tiles, Copyright © 2008 Klokan Petr Pridal, GDAL & OSGeo GSoC'; + var src = 'Generated by GDAL2Tiles, Copyright © 2008 Klokan Petr Pridal, GDAL & OSGeo GSoC'; var title = L.control({position: 'bottomleft'}); title.onAdd = function(map) { this._div = L.DomUtil.create('div', 'ctl src'); From a66c71d0e1e19a5ef9d94b33bef90074173be9c2 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 26 Jun 2024 13:40:36 +0200 Subject: [PATCH 0249/1119] Doc: advertize 3.9.1 release --- doc/source/about_no_title.rst | 4 ++-- doc/source/download.rst | 8 ++++---- doc/source/download_past.rst | 6 ++++++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/doc/source/about_no_title.rst b/doc/source/about_no_title.rst index 17c7344a53b4..d7163f525f89 100644 --- a/doc/source/about_no_title.rst +++ b/doc/source/about_no_title.rst @@ -1,11 +1,11 @@ -GDAL is a translator library for raster and vector geospatial data formats that is released under an MIT style Open Source :ref:`license` by the `Open Source Geospatial Foundation`_. As a library, it presents a single raster abstract data model and single vector abstract data model to the calling application for all supported formats. It also comes with a variety of useful command line utilities for data translation and processing. The `NEWS`_ page describes the May 2024 GDAL/OGR 3.9.0 release. +GDAL is a translator library for raster and vector geospatial data formats that is released under an MIT style Open Source :ref:`license` by the `Open Source Geospatial Foundation`_. As a library, it presents a single raster abstract data model and single vector abstract data model to the calling application for all supported formats. It also comes with a variety of useful command line utilities for data translation and processing. The `NEWS`_ page describes the June 2024 GDAL/OGR 3.9.1 release. .. image:: ../images/OSGeo_project.png :alt: OSGeo project :target: `Open Source Geospatial Foundation`_ .. _`Open Source Geospatial Foundation`: http://www.osgeo.org/ -.. _`NEWS`: https://github.com/OSGeo/gdal/blob/v3.9.0/NEWS.md +.. _`NEWS`: https://github.com/OSGeo/gdal/blob/v3.9.1/NEWS.md See :ref:`software_using_gdal` diff --git a/doc/source/download.rst b/doc/source/download.rst index d9c77ab3353a..4b16fc5bcbdc 100644 --- a/doc/source/download.rst +++ b/doc/source/download.rst @@ -18,11 +18,11 @@ Source Code Current Release ............... -* **2024-05-10** `gdal-3.9.0.tar.gz`_ `3.9.0 Release Notes`_ (`3.9.0 md5`_) +* **2024-06-26** `gdal-3.9.1.tar.gz`_ `3.9.1 Release Notes`_ (`3.9.1 md5`_) -.. _`3.9.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.9.0/NEWS.md -.. _`gdal-3.9.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.9.0/gdal-3.9.0.tar.gz -.. _`3.9.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.9.0/gdal-3.9.0.tar.gz.md5 +.. _`3.9.1 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.9.1/NEWS.md +.. _`gdal-3.9.1.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.9.1/gdal-3.9.1.tar.gz +.. _`3.9.1 md5`: https://github.com/OSGeo/gdal/releases/download/v3.9.1/gdal-3.9.1.tar.gz.md5 Past Releases ............. diff --git a/doc/source/download_past.rst b/doc/source/download_past.rst index 1e20d7db418c..608ddfccfad4 100644 --- a/doc/source/download_past.rst +++ b/doc/source/download_past.rst @@ -5,6 +5,12 @@ Past Releases ============= +* **2024-05-10** `gdal-3.9.0.tar.gz`_ `3.9.0 Release Notes`_ (`3.9.0 md5`_) + +.. _`3.9.0 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.9.0/NEWS.md +.. _`gdal-3.9.0.tar.gz`: https://github.com/OSGeo/gdal/releases/download/v3.9.0/gdal-3.9.0.tar.gz +.. _`3.9.0 md5`: https://github.com/OSGeo/gdal/releases/download/v3.9.0/gdal-3.9.0.tar.gz.md5 + * **2024-04-04** `gdal-3.8.5.tar.gz`_ `3.8.5 Release Notes`_ (`3.8.5 md5`_) .. _`3.8.5 Release Notes`: https://github.com/OSGeo/gdal/blob/v3.8.5/NEWS.md From 618ef7bddf4cad9ae3d5730c8ec3746b84509bea Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Wed, 26 Jun 2024 10:50:54 -0400 Subject: [PATCH 0250/1119] Doc: Add some dev info from wiki --- doc/source/development/dev_practices.rst | 49 +++++++++++++++++------- doc/source/development/testing.rst | 7 ++++ 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/doc/source/development/dev_practices.rst b/doc/source/development/dev_practices.rst index 4bf9ad5af4cc..e218b144fd13 100644 --- a/doc/source/development/dev_practices.rst +++ b/doc/source/development/dev_practices.rst @@ -31,7 +31,7 @@ C/C++ standards --------------- The current C and C++ standards adopted by GDAL/OGR are C99 and C++17 -(last updated per :ref:`rfc-98`)` +(last updated per :ref:`rfc-98`). Variable naming --------------- @@ -47,8 +47,7 @@ and potentially semantics of a variable. The following are some prefixes used in GDAL/OGR. - *a*: array -- *b*: C/C++ bool. In C code that pre-dates C99 adoption, it is also used for - ints with only TRUE/FALSE values. +- *b*: C/C++ bool. In C code that pre-dates C99 adoption, it is also used for ints with only TRUE/FALSE values. - *by*: byte (GByte / unsigned char). - *df*: floating point value (double precision) - *e*: enumeration @@ -78,25 +77,45 @@ variables. It may also be noted that the standard convention for variable names is to capitalize each word in a variable name. +Function and class naming +------------------------- + +- Functions and classes should have a selective enough namespace ("GDAL" or "OGR" prefix, or use of C++ namespace) to avoid symbol collision. + +File naming and code formatting +------------------------------- + +- All source files (.h, .c, .cpp, .py, etc.) should have a header with copyright attribution and the text of the GDAL X/MIT license. +- Use lower case filenames. +- Use .cpp extension for C++ files (not .cc). +- C/C++ code formatting rules are defined in :source_file:`.clang-format`. Python code formatting + is enforced by Black. The pre-commit utility should be used to enforce them automatically. See :ref:`commit_hooks`. + Memory allocation ----------------- -As per :ref:`rfc-19`, you can use VSIMalloc2(x, y) instead of -doing CPLMalloc(x \* y) or VSIMalloc(x \* y). VSIMalloc2 will detect +Large memory allocations should be performed using the :cpp:func:`VSIMalloc` family of functions, which will return ``nullptr`` on allocation failure. +As per :ref:`rfc-19`, you can use ``VSIMalloc2(x, y)`` instead of +doing ``CPLMalloc(x * y)`` or ``VSIMalloc(x * y)``. :cpp:func:`VSIMalloc2` will detect potential overflows in the multiplication and return a NULL pointer if it happens. This can be useful in GDAL raster drivers where x and y are related to the raster dimensions or raster block sizes. Similarly, -VSIMalloc3(x, y, z) can be used as a replacement for CPLMalloc(x \* y \* -z). +``VSIMalloc3(x, y, z)`` can be used as a replacement for ``CPLMalloc(x * y * z)``. -File naming and code formatting -------------------------------- +When working with standard library data structures such as ``std::vector`` that may throw ``std::bad_alloc``, a try/catch block should be used around blocks that may allocate a large amount of memory. -- Use lower case filenames. -- Use .cpp extension for C++ files (not .cc). -- Code formatting rules are defined in :source_file:`.clang-format`. The - pre-commit utility can be used to enforce them automatically. +Adding a new driver +------------------- +- If the driver depends on a third-party library, compilation of the driver must be made conditional on the presence of the library. Drivers should try to re-use existing library dependencies as much as possible, e.g. Expat for SAX XML parsing. +- For a vector driver, check that the Open() method of the driver (often delegated to a Open() method of the datasource) is selective enough (i.e. it will not accept data files that are not meant for the driver), and robust enough (it will not crash for small variations w.r.t content that it would recognize). Check that it can deal with unusual filenames. For a GDAL driver, similar checks, as well for the optional Identify() method. +- A set of tests covering the driver should be added to the Python test suite. If appropriate, small sample data files may be added to autotest/gdrivers/data or autotest/ogr/data. The ``test_ogrsf`` utility and ``GDALTest`` class may simplify testing of basic driver functionality. +- A documentation page should be created for the driver. Documentation should, at a minimum, briefly describe the format handled by the driver and, when relevant, describe the particular syntax for the connection string, creation options, configuration options, etc. The documentation should provide a link to a more detailed format description and mention needed third-party libraries. + +Writing tests +------------- + +See :ref:`writing_tests`. Git usage --------- @@ -183,10 +202,12 @@ actually fixes it) Details here... +.. _commit_hooks: + Commit hooks ^^^^^^^^^^^^ -GDAL provides pre-commit hooks to run code linters before a commit is made. The +GDAL provides pre-commit hooks to run code formatters and linters before a commit is made. The hooks are cloned with the repository and can be installed using `pre-commit `_: diff --git a/doc/source/development/testing.rst b/doc/source/development/testing.rst index df55a4dfe43b..72f5bdfca7a2 100644 --- a/doc/source/development/testing.rst +++ b/doc/source/development/testing.rst @@ -110,6 +110,8 @@ follows: ``~/.valgrindrc`` file. +.. _writing_tests: + Recommendations on how to write new tests ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -294,3 +296,8 @@ and pull requests events. A somewhat nicer looking output of line coverage results for the latest master build, generated by ``lcov``, is also available at https://gdalautotest-coverage-results.github.io/coverage_html/index.html + +Post-commit testing +^^^^^^^^^^^^^^^^^^^ + +A weekly static analysis is run by `Coverity `__. Developers/maintainers can request access on the `GDAL project page `__. From 1542f5863e72ab6418c316b37532cceb2b09c437 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 26 Jun 2024 21:51:37 +0200 Subject: [PATCH 0251/1119] validate_gpkg.py: make it robust to CURRENT_TIMESTAMP instead of 'now' As found in Spatialite generated DBs, and as accepted by CITE GeoPackage ETS --- .../osgeo_utils/samples/validate_gpkg.py | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/swig/python/gdal-utils/osgeo_utils/samples/validate_gpkg.py b/swig/python/gdal-utils/osgeo_utils/samples/validate_gpkg.py index 118075bcc5b3..1fcfd91d3d41 100644 --- a/swig/python/gdal-utils/osgeo_utils/samples/validate_gpkg.py +++ b/swig/python/gdal-utils/osgeo_utils/samples/validate_gpkg.py @@ -186,12 +186,26 @@ def _check_structure(self, columns, expected_columns, req, table_name): ("Wrong notnull for %s of %s. " + "Expected %s, got %s") % (name, table_name, expected_notnull, notnull), ) - self._assert( - default == expected_default, - req, - ("Wrong default for %s of %s. " + "Expected %s, got %s") - % (name, table_name, expected_default, default), - ) + # GeoPackage ETS suite accepts CURRENT_TIMESTAMP instead of 'now' + # Spatialite at time of writting uses CURRENT_TIMESTAMP. + # https://github.com/opengeospatial/ets-gpkg12/blob/04b4a0b8c7d90755b016182e2992684f198da1ba/src/main/java/org/opengis/cite/gpkg12/TableVerifier.java#L173 + if ( + default != expected_default + and expected_default == "strftime('%Y-%m-%dT%H:%M:%fZ','now')" + and default.lower().replace(" ", "") + in ( + "strftime('%Y-%m-%dT%H:%M:%fZ','now')".lower(), + "strftime('%Y-%m-%dT%H:%M:%fZ',CURRENT_TIMESTAMP)".lower(), + ) + ): + pass + else: + self._assert( + default == expected_default, + req, + ("Wrong default for %s of %s. " + "Expected %s, got %s") + % (name, table_name, expected_default, default), + ) self._assert( pk == expected_pk, req, From 84f962e2081a1014e8a9bcdf203964dcd034dcce Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 26 Jun 2024 22:25:08 +0200 Subject: [PATCH 0252/1119] LIBKML: fix writing a .kmz to cloud storage Fixes #10313 --- autotest/ogr/ogr_libkml.py | 11 +++++++++ .../libkml/ogrlibkmldatasource.cpp | 24 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/autotest/ogr/ogr_libkml.py b/autotest/ogr/ogr_libkml.py index 1c616123436e..cc32eb283013 100755 --- a/autotest/ogr/ogr_libkml.py +++ b/autotest/ogr/ogr_libkml.py @@ -455,6 +455,17 @@ def test_ogr_libkml_write_kmz_use_doc_off(tmp_vsimem): ogr_libkml_check_write(tmp_vsimem / "libkml_use_doc_off.kmz") +def test_ogr_libkml_write_kmz_simulate_cloud(tmp_vsimem): + with gdal.config_option("CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "FORCED"): + ogr_libkml_write(tmp_vsimem / "test_ogr_libkml_write_kmz_simulate_cloud.kmz") + + ogr_libkml_check_write(tmp_vsimem / "test_ogr_libkml_write_kmz_simulate_cloud.kmz") + + with gdal.config_option("CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", "FORCED"): + with pytest.raises(Exception): + ogr_libkml_write("/i_do/not/exist.kmz") + + ############################################################################### # Test reading attributes with XML content in them # diff --git a/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp b/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp index ac600fe32475..5cf1c16b394e 100644 --- a/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp +++ b/ogr/ogrsf_frmts/libkml/ogrlibkmldatasource.cpp @@ -412,7 +412,16 @@ static KmlPtr OGRLIBKMLCreateOGCKml22(KmlFactory *poFactory, bool OGRLIBKMLDataSource::WriteKmz() { - void *hZIP = CPLCreateZip(m_pszName, nullptr); + std::string osTmpFilename; + if (!VSISupportsRandomWrite(m_pszName, false) || + EQUAL(CPLGetConfigOption("CPL_VSIL_USE_TEMP_FILE_FOR_RANDOM_WRITE", ""), + "FORCED")) + { + osTmpFilename = CPLGenerateTempFilename(CPLGetBasename(m_pszName)); + } + + void *hZIP = CPLCreateZip( + osTmpFilename.empty() ? m_pszName : osTmpFilename.c_str(), nullptr); if (!hZIP) { @@ -536,6 +545,19 @@ bool OGRLIBKMLDataSource::WriteKmz() } CPLCloseZip(hZIP); + + if (!osTmpFilename.empty()) + { + if (bRet) + { + bRet = CPLCopyFile(m_pszName, osTmpFilename.c_str()) == 0; + if (!bRet) + CPLError(CE_Failure, CPLE_FileIO, + "Cannot copy temporary file to %s", m_pszName); + } + VSIUnlink(osTmpFilename.c_str()); + } + return bRet; } From 7096d468d8172c6669164c6038097981a9bd9194 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 00:23:16 +0200 Subject: [PATCH 0253/1119] Python bindings: fix typos in gdal.Footprint() help message --- swig/include/python/gdal_python.i | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/swig/include/python/gdal_python.i b/swig/include/python/gdal_python.i index 6321e9cdc50f..017499fd9917 100644 --- a/swig/include/python/gdal_python.i +++ b/swig/include/python/gdal_python.i @@ -4200,15 +4200,15 @@ def Footprint(destNameOrDestDS, srcDS, **kwargs): 1. Special mode to get deserialized GeoJSON (in EPSG:4326 if dstSRS not specified): - >>> deserialized_geojson = gdal.FootPrint(None, src_ds, format="GeoJSON") + >>> deserialized_geojson = gdal.Footprint(None, src_ds, format="GeoJSON") 2. Special mode to get WKT: - >>> wkt = gdal.FootPrint(None, src_ds, format="WKT") + >>> wkt = gdal.Footprint(None, src_ds, format="WKT") 3. Get result in a GeoPackage - >>> gdal.FootPrintf("out.gpkg", src_ds, format="GPKG") + >>> gdal.Footprint("out.gpkg", src_ds, format="GPKG") """ From ca3d2de3164a2c6eb6459d4cf8b7870f226c8be6 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 01:26:06 +0200 Subject: [PATCH 0254/1119] configoptions.rst: remove inappropriate marker --- doc/source/user/configoptions.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/source/user/configoptions.rst b/doc/source/user/configoptions.rst index 1878022b9d23..69a0f90b6a39 100644 --- a/doc/source/user/configoptions.rst +++ b/doc/source/user/configoptions.rst @@ -147,7 +147,6 @@ Example: [.sentinel_s2_l1c] path=/vsis3/sentinel-s2-l1c AWS_REQUEST_PAYER=requester - \endverbatim From 9e793faa1124eb1ed9d18e129ec847784a3000ec Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 10:58:09 +0200 Subject: [PATCH 0255/1119] CI: fix s390x builds --- ci/travis/s390x/before_install.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ci/travis/s390x/before_install.sh b/ci/travis/s390x/before_install.sh index 5c85105472b8..9ddc8f5ab9f2 100755 --- a/ci/travis/s390x/before_install.sh +++ b/ci/travis/s390x/before_install.sh @@ -3,8 +3,9 @@ set -e sudo pip uninstall -y setuptools -sudo rm -rf /usr/local/lib/python* -sudo apt-get remove -f python +sudo rm -rf /usr/local/bin/* +sudo rm -rf /usr/local/lib/* +sudo apt-get remove -f python python3-pip sudo apt-get update sudo apt-get install -y software-properties-common From 16ab6316364fd12d281bc6e4a61fc1310d812183 Mon Sep 17 00:00:00 2001 From: Grant Date: Thu, 27 Jun 2024 10:29:16 +0100 Subject: [PATCH 0256/1119] gdal2tiles: use correct OpenStreetMap tile url per (#10321) Use OpenStreetMap's preferred tile url per: https://github.com/openstreetmap/operations/issues/737 --- swig/python/gdal-utils/osgeo_utils/gdal2tiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py b/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py index a7ce514be53e..2b3cb201733c 100644 --- a/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py +++ b/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py @@ -3979,7 +3979,7 @@ def generate_leaflet(self) -> str: // Base layers // .. OpenStreetMap - var osm = L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {attribution: '© OpenStreetMap contributors', minZoom: %(minzoom)s, maxZoom: %(maxzoom)s}); + var osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {attribution: '© OpenStreetMap contributors', minZoom: %(minzoom)s, maxZoom: %(maxzoom)s}); // .. CartoDB Positron var cartodb = L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {attribution: '© OpenStreetMap contributors, © CartoDB', minZoom: %(minzoom)s, maxZoom: %(maxzoom)s}); From b9d2971bce0a74acda16fe9a9b9ae6273d9243fc Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 26 Jun 2024 20:20:26 +0200 Subject: [PATCH 0257/1119] GTiff: make SetNoDataValue(double) work on a Int64/UInt64 band Refs / fixes #10306 --- autotest/cpp/test_gdal_gtiff.cpp | 28 ++++++++++++++++++++ frmts/gtiff/gtiffrasterband_write.cpp | 37 ++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/autotest/cpp/test_gdal_gtiff.cpp b/autotest/cpp/test_gdal_gtiff.cpp index 1ea518295fb6..b9a24c5a59b3 100644 --- a/autotest/cpp/test_gdal_gtiff.cpp +++ b/autotest/cpp/test_gdal_gtiff.cpp @@ -240,4 +240,32 @@ TEST_F(test_gdal_gtiff, raster_min_max) GDALClose(ds); } +// Test setting a nodata value with SetNoDataValue(double) on a int64 dataset +TEST_F(test_gdal_gtiff, set_nodata_value_on_int64) +{ + std::string osTmpFile = "/vsimem/temp.tif"; + auto poDS = + std::unique_ptr(GDALDriver::FromHandle(drv_)->Create( + osTmpFile.c_str(), 1, 1, 1, GDT_Int64, nullptr)); + EXPECT_EQ(poDS->GetRasterBand(1)->SetNoDataValue(1), CE_None); + { + int bGotNoData = false; + EXPECT_EQ(poDS->GetRasterBand(1)->GetNoDataValue(&bGotNoData), 1.0); + EXPECT_TRUE(bGotNoData); + } + { + int bGotNoData = false; + EXPECT_EQ(poDS->GetRasterBand(1)->GetNoDataValueAsInt64(&bGotNoData), + 1.0); + EXPECT_TRUE(bGotNoData); + } + int64_t nVal = 0; + EXPECT_EQ(poDS->GetRasterBand(1)->RasterIO(GF_Read, 0, 0, 1, 1, &nVal, 1, 1, + GDT_Int64, 0, 0, nullptr), + CE_None); + EXPECT_EQ(nVal, 1); + poDS.reset(); + VSIUnlink(osTmpFile.c_str()); +} + } // namespace diff --git a/frmts/gtiff/gtiffrasterband_write.cpp b/frmts/gtiff/gtiffrasterband_write.cpp index de8d09e6934e..0b7ee7dc8663 100644 --- a/frmts/gtiff/gtiffrasterband_write.cpp +++ b/frmts/gtiff/gtiffrasterband_write.cpp @@ -34,6 +34,7 @@ #include #include "cpl_vsi_virtual.h" +#include "gdal_priv_templates.hpp" #include "gtiff.h" #include "tifvsi.h" @@ -705,6 +706,33 @@ CPLErr GTiffRasterBand::SetColorTable(GDALColorTable *poCT) CPLErr GTiffRasterBand::SetNoDataValue(double dfNoData) { + const auto SetNoDataMembers = [this, dfNoData]() + { + m_bNoDataSet = true; + m_dfNoDataValue = dfNoData; + + m_poGDS->m_bNoDataSet = true; + m_poGDS->m_dfNoDataValue = dfNoData; + + if (eDataType == GDT_Int64 && GDALIsValueExactAs(dfNoData)) + { + m_bNoDataSetAsInt64 = true; + m_nNoDataValueInt64 = static_cast(dfNoData); + + m_poGDS->m_bNoDataSetAsInt64 = true; + m_poGDS->m_nNoDataValueInt64 = static_cast(dfNoData); + } + else if (eDataType == GDT_UInt64 && + GDALIsValueExactAs(dfNoData)) + { + m_bNoDataSetAsUInt64 = true; + m_nNoDataValueUInt64 = static_cast(dfNoData); + + m_poGDS->m_bNoDataSetAsUInt64 = true; + m_poGDS->m_nNoDataValueUInt64 = static_cast(dfNoData); + } + }; + m_poGDS->LoadGeoreferencingAndPamIfNeeded(); if (m_poGDS->m_bNoDataSet && @@ -713,8 +741,7 @@ CPLErr GTiffRasterBand::SetNoDataValue(double dfNoData) { ResetNoDataValues(false); - m_bNoDataSet = true; - m_dfNoDataValue = dfNoData; + SetNoDataMembers(); return CE_None; } @@ -767,11 +794,7 @@ CPLErr GTiffRasterBand::SetNoDataValue(double dfNoData) { ResetNoDataValues(true); - m_poGDS->m_bNoDataSet = true; - m_poGDS->m_dfNoDataValue = dfNoData; - - m_bNoDataSet = true; - m_dfNoDataValue = dfNoData; + SetNoDataMembers(); } return eErr; From 7f800a6f640699af9fa5e0568e40ef24e4a29d30 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 26 Jun 2024 20:21:19 +0200 Subject: [PATCH 0258/1119] gdal_rasterize: on a int64 band, set nodata value as int64 Fixes #10306 --- apps/gdal_rasterize_lib.cpp | 25 +++++++++++-------- autotest/utilities/test_gdal_rasterize_lib.py | 4 ++- doc/source/programs/gdal_rasterize.rst | 4 ++- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/apps/gdal_rasterize_lib.cpp b/apps/gdal_rasterize_lib.cpp index 39913ad306cb..12ebd7cb2466 100644 --- a/apps/gdal_rasterize_lib.cpp +++ b/apps/gdal_rasterize_lib.cpp @@ -482,7 +482,7 @@ static GDALDatasetH CreateOutputDataset( OGREnvelope sEnvelop, GDALDriverH hDriver, const char *pszDest, int nXSize, int nYSize, double dfXRes, double dfYRes, bool bTargetAlignedPixels, int nBandCount, GDALDataType eOutputType, char **papszCreationOptions, - const std::vector &adfInitVals, int bNoDataSet, double dfNoData) + const std::vector &adfInitVals, const char *pszNoData) { bool bFirstLayer = true; char *pszWKT = nullptr; @@ -598,12 +598,16 @@ static GDALDatasetH CreateOutputDataset( } }*/ - if (bNoDataSet) + if (pszNoData) { for (int iBand = 0; iBand < nBandCount; iBand++) { GDALRasterBandH hBand = GDALGetRasterBand(hDstDS, iBand + 1); - GDALSetRasterNoDataValue(hBand, dfNoData); + if (GDALGetRasterDataType(hBand) == GDT_Int64) + GDALSetRasterNoDataValueAsInt64(hBand, + CPLAtoGIntBig(pszNoData)); + else + GDALSetRasterNoDataValue(hBand, CPLAtof(pszNoData)); } } @@ -649,8 +653,7 @@ struct GDALRasterizeOptions char **papszCreationOptions; GDALDataType eOutputType; std::vector adfInitVals; - int bNoDataSet; - double dfNoData; + char *pszNoData; OGREnvelope sEnvelop; int nXSize, nYSize; OGRSpatialReferenceH hSRS; @@ -832,7 +835,7 @@ GDALDatasetH GDALRasterize(const char *pszDest, GDALDatasetH hDstDS, psOptions->bTargetAlignedPixels, static_cast(psOptions->anBandList.size()), eOutputType, psOptions->papszCreationOptions, psOptions->adfInitVals, - psOptions->bNoDataSet, psOptions->dfNoData); + psOptions->pszNoData); if (hDstDS == nullptr) { GDALDatasetReleaseResultSet(hSrcDataset, hLayer); @@ -910,7 +913,7 @@ GDALDatasetH GDALRasterize(const char *pszDest, GDALDatasetH hDstDS, psOptions->dfYRes, psOptions->bTargetAlignedPixels, static_cast(psOptions->anBandList.size()), eOutputType, psOptions->papszCreationOptions, psOptions->adfInitVals, - psOptions->bNoDataSet, psOptions->dfNoData); + psOptions->pszNoData); if (hDstDS == nullptr) { GDALRasterizeOptionsFree(psOptionsToFree); @@ -1020,8 +1023,7 @@ GDALRasterizeOptionsNew(char **papszArgv, psOptions->dfXRes = 0; psOptions->dfYRes = 0; psOptions->eOutputType = GDT_Unknown; - psOptions->bNoDataSet = FALSE; - psOptions->dfNoData = 0; + psOptions->pszNoData = nullptr; psOptions->nXSize = 0; psOptions->nYSize = 0; psOptions->hSRS = nullptr; @@ -1195,8 +1197,8 @@ GDALRasterizeOptionsNew(char **papszArgv, } else if (i < argc - 1 && EQUAL(papszArgv[i], "-a_nodata")) { - psOptions->dfNoData = CPLAtof(papszArgv[i + 1]); - psOptions->bNoDataSet = TRUE; + CPLFree(psOptions->pszNoData); + psOptions->pszNoData = CPLStrdup(papszArgv[i + 1]); i += 1; psOptions->bCreateOutput = true; } @@ -1472,6 +1474,7 @@ void GDALRasterizeOptionsFree(GDALRasterizeOptions *psOptions) CPLFree(psOptions->pszDialect); CPLFree(psOptions->pszBurnAttribute); CPLFree(psOptions->pszWHERE); + CPLFree(psOptions->pszNoData); OSRDestroySpatialReference(psOptions->hSRS); delete psOptions; diff --git a/autotest/utilities/test_gdal_rasterize_lib.py b/autotest/utilities/test_gdal_rasterize_lib.py index b2669d966a87..67b2ba8d6d42 100755 --- a/autotest/utilities/test_gdal_rasterize_lib.py +++ b/autotest/utilities/test_gdal_rasterize_lib.py @@ -712,11 +712,13 @@ def test_gdal_rasterize_lib_int64_attribute(): feature["val"] = val layer.CreateFeature(feature) + noData = -(1 << 63) target_ds = gdal.Rasterize( - "", vector_ds, format="MEM", attribute="val", width=2, height=2 + "", vector_ds, format="MEM", attribute="val", width=2, height=2, noData=noData ) assert target_ds is not None assert target_ds.GetRasterBand(1).DataType == gdal.GDT_Int64 + assert target_ds.GetRasterBand(1).GetNoDataValue() == noData assert struct.unpack("Q" * 4, target_ds.ReadRaster())[0] == val diff --git a/doc/source/programs/gdal_rasterize.rst b/doc/source/programs/gdal_rasterize.rst index 43744d1fa967..6067de7a1d9d 100644 --- a/doc/source/programs/gdal_rasterize.rst +++ b/doc/source/programs/gdal_rasterize.rst @@ -173,7 +173,9 @@ raster data is only supported since GDAL 2.1.0. .. option:: -ot - Force the output bands to be of the indicated data type. Defaults to ``Float64`` + Force the output bands to be of the indicated data type. Defaults to ``Float64``, + unless the attribute field to burn is of type ``Int64``, in which case ``Int64`` + is used for the output raster data type. .. option:: -optim {AUTO|VECTOR|RASTER} From badc324442d722ff6c173583217fb64ca3d011b3 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 26 Jun 2024 21:11:55 +0200 Subject: [PATCH 0259/1119] gdal_rasterize: restrict to defaulting to Int64 raster data type only if the output driver supports it (and the burned field is Int64) --- apps/gdal_rasterize_lib.cpp | 62 ++++++++++++++------------ doc/source/programs/gdal_rasterize.rst | 2 +- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/apps/gdal_rasterize_lib.cpp b/apps/gdal_rasterize_lib.cpp index 12ebd7cb2466..493922ee1068 100644 --- a/apps/gdal_rasterize_lib.cpp +++ b/apps/gdal_rasterize_lib.cpp @@ -793,6 +793,36 @@ GDALDatasetH GDALRasterize(const char *pszDest, GDALDatasetH hDstDS, } } + const auto GetOutputDataType = [&](OGRLayerH hLayer) + { + CPLAssert(bCreateOutput); + CPLAssert(hDriver); + GDALDataType eOutputType = psOptions->eOutputType; + if (eOutputType == GDT_Unknown && + psOptions->pszBurnAttribute != nullptr) + { + OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn(hLayer); + const int iBurnField = + OGR_FD_GetFieldIndex(hLayerDefn, psOptions->pszBurnAttribute); + if (iBurnField >= 0 && OGR_Fld_GetType(OGR_FD_GetFieldDefn( + hLayerDefn, iBurnField)) == OFTInteger64) + { + const char *pszMD = GDALGetMetadataItem( + hDriver, GDAL_DMD_CREATIONDATATYPES, nullptr); + if (pszMD && CPLStringList(CSLTokenizeString2(pszMD, " ", 0)) + .FindString("Int64") >= 0) + { + eOutputType = GDT_Int64; + } + } + } + if (eOutputType == GDT_Unknown) + { + eOutputType = GDT_Float64; + } + return eOutputType; + }; + /* -------------------------------------------------------------------- */ /* Process SQL request. */ /* -------------------------------------------------------------------- */ @@ -809,25 +839,7 @@ GDALDatasetH GDALRasterize(const char *pszDest, GDALDatasetH hDstDS, std::vector ahLayers; ahLayers.push_back(hLayer); - GDALDataType eOutputType = psOptions->eOutputType; - if (eOutputType == GDT_Unknown && - psOptions->pszBurnAttribute != nullptr) - { - OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn(hLayer); - int iBurnField = OGR_FD_GetFieldIndex( - hLayerDefn, psOptions->pszBurnAttribute); - if (iBurnField >= 0 && - OGR_Fld_GetType(OGR_FD_GetFieldDefn( - hLayerDefn, iBurnField)) == OFTInteger64) - { - eOutputType = GDT_Int64; - } - } - if (eOutputType == GDT_Unknown) - { - eOutputType = GDT_Float64; - } - + const GDALDataType eOutputType = GetOutputDataType(hLayer); hDstDS = CreateOutputDataset( ahLayers, psOptions->hSRS, psOptions->sEnvelop, hDriver, pszDest, psOptions->nXSize, psOptions->nYSize, @@ -885,18 +897,10 @@ GDALDatasetH GDALRasterize(const char *pszDest, GDALDatasetH hDstDS, GDALRasterizeOptionsFree(psOptionsToFree); return nullptr; } - if (eOutputType == GDT_Unknown && - psOptions->pszBurnAttribute != nullptr) + if (eOutputType == GDT_Unknown) { - OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn(hLayer); - int iBurnField = OGR_FD_GetFieldIndex( - hLayerDefn, psOptions->pszBurnAttribute); - if (iBurnField >= 0 && - OGR_Fld_GetType(OGR_FD_GetFieldDefn( - hLayerDefn, iBurnField)) == OFTInteger64) - { + if (GetOutputDataType(hLayer) == GDT_Int64) eOutputType = GDT_Int64; - } } ahLayers.push_back(hLayer); diff --git a/doc/source/programs/gdal_rasterize.rst b/doc/source/programs/gdal_rasterize.rst index 6067de7a1d9d..c382c30b7eee 100644 --- a/doc/source/programs/gdal_rasterize.rst +++ b/doc/source/programs/gdal_rasterize.rst @@ -175,7 +175,7 @@ raster data is only supported since GDAL 2.1.0. Force the output bands to be of the indicated data type. Defaults to ``Float64``, unless the attribute field to burn is of type ``Int64``, in which case ``Int64`` - is used for the output raster data type. + is used for the output raster data type if the output driver supports it. .. option:: -optim {AUTO|VECTOR|RASTER} From 01d25df6f1cae57a6b83c1bd88e63e78a239730a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 12:22:59 +0200 Subject: [PATCH 0260/1119] PG: fix ogr2ogr scenarios to PostgreSQL when there are several input layer names like schema_name.layer_name (3.9.0 regression) Fixes #10311 --- autotest/ogr/ogr_pg.py | 35 ++++++++++++++++++++++++++ ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp | 2 ++ 2 files changed, 37 insertions(+) diff --git a/autotest/ogr/ogr_pg.py b/autotest/ogr/ogr_pg.py index 8a8ad9a75357..a00a8f496dda 100755 --- a/autotest/ogr/ogr_pg.py +++ b/autotest/ogr/ogr_pg.py @@ -6121,3 +6121,38 @@ def test_ogr_pg_skip_conflicts(pg_ds): feat["beginnt"] = "2020-07-10T04:48:14Z" assert lyr.CreateFeature(feat) == ogr.OGRERR_NONE assert lyr.GetFeatureCount() == 2 + + +############################################################################### +# Test scenario of https://github.com/OSGeo/gdal/issues/10311 + + +@only_without_postgis +@gdaltest.enable_exceptions() +def test_ogr_pg_ogr2ogr_with_multiple_dotted_table_name(pg_ds): + + tmp_schema = "tmp_schema_issue_10311" + pg_ds.ExecuteSQL(f'CREATE SCHEMA "{tmp_schema}"') + try: + src_ds = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + lyr = src_ds.CreateLayer(tmp_schema + ".table1", geom_type=ogr.wkbNone) + lyr.CreateField(ogr.FieldDefn("str")) + f = ogr.Feature(lyr.GetLayerDefn()) + f["str"] = "foo" + lyr.CreateFeature(f) + lyr = src_ds.CreateLayer(tmp_schema + ".table2", geom_type=ogr.wkbNone) + lyr.CreateField(ogr.FieldDefn("str")) + f = ogr.Feature(lyr.GetLayerDefn()) + f["str"] = "bar" + lyr.CreateFeature(f) + + gdal.VectorTranslate(pg_ds.GetDescription(), src_ds) + + pg_ds = reconnect(pg_ds) + lyr = pg_ds.GetLayerByName(tmp_schema + ".table1") + assert lyr.GetFeatureCount() == 1 + lyr = pg_ds.GetLayerByName(tmp_schema + ".table2") + assert lyr.GetFeatureCount() == 1 + + finally: + pg_ds.ExecuteSQL(f'DROP SCHEMA "{tmp_schema}" CASCADE') diff --git a/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp b/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp index 62415a574683..1c2c6a93b8e0 100644 --- a/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp +++ b/ogr/ogrsf_frmts/pg/ogrpgdatasource.cpp @@ -2114,6 +2114,8 @@ OGRPGDataSource::FindSchema(const char *pszSchemaNameIn) return pszSchemaNameIn; } + EndCopy(); + std::string osSchemaName; std::string osCommand( "SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname ILIKE "); From d6a7db55201797ebe2b2ddf6eee9207f1fa21898 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 12:48:49 +0200 Subject: [PATCH 0261/1119] gdalenhance: fix memleak (fixes #10323) --- apps/gdalenhance.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/gdalenhance.cpp b/apps/gdalenhance.cpp index 39553a13b7e7..d00895656417 100644 --- a/apps/gdalenhance.cpp +++ b/apps/gdalenhance.cpp @@ -598,6 +598,8 @@ static int ComputeEqualizationLUTs(GDALDatasetH hDataset, int nLUTBins, panLUT[iLUT] = std::max(0, std::min(nLUTBins - 1, nValue)); } + CPLFree(panCumHist); + (*ppapanLUTs)[iBand] = panLUT; } From 447e4ad1fbc7b61fc4666b2982b05b4f0a3c99c3 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 17:52:29 +0200 Subject: [PATCH 0262/1119] typo fix [ci skip] --- swig/python/gdal-utils/osgeo_utils/samples/validate_gpkg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swig/python/gdal-utils/osgeo_utils/samples/validate_gpkg.py b/swig/python/gdal-utils/osgeo_utils/samples/validate_gpkg.py index 1fcfd91d3d41..30c772acf76e 100644 --- a/swig/python/gdal-utils/osgeo_utils/samples/validate_gpkg.py +++ b/swig/python/gdal-utils/osgeo_utils/samples/validate_gpkg.py @@ -187,7 +187,7 @@ def _check_structure(self, columns, expected_columns, req, table_name): % (name, table_name, expected_notnull, notnull), ) # GeoPackage ETS suite accepts CURRENT_TIMESTAMP instead of 'now' - # Spatialite at time of writting uses CURRENT_TIMESTAMP. + # Spatialite at time of writing uses CURRENT_TIMESTAMP. # https://github.com/opengeospatial/ets-gpkg12/blob/04b4a0b8c7d90755b016182e2992684f198da1ba/src/main/java/org/opengis/cite/gpkg12/TableVerifier.java#L173 if ( default != expected_default From e6ff99385a4b6d6dae5cba57a04c692edeebcff3 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 17:53:43 +0200 Subject: [PATCH 0263/1119] GTiff: handle TIFF color map that uses a 256 multiplication factor and add open option: .. oo:: COLOR_TABLE_MULTIPLIER :choices: AUTO, 1, 256, 257 :since: 3.10.0 Specifies the value by which to multiply GDAL color table entry values, usually in [0,255] range, to obtain a TIFF color map value, or on reading by which to divide TIFF color map values to get a GDAL color table entry. Since GDAL 2.3.0, GDAL consistently uses 257, but it might be necessary to use 256 for compatibility with files generated by other software. In AUTO mode, GDAL 3.10 or later can automatically detect the 256 multiplication factor when all values in the TIFF color map are multiple of that value. and same as creation option. Fixes #10310 --- autotest/gcore/tiff_write.py | 41 +++++++- doc/source/drivers/raster/gtiff.rst | 32 ++++++- frmts/gtiff/geotiff.cpp | 19 +++- frmts/gtiff/gt_overview.cpp | 22 ++++- frmts/gtiff/gtiffdataset.cpp | 1 + frmts/gtiff/gtiffdataset.h | 17 +++- frmts/gtiff/gtiffdataset_read.cpp | 109 +++++++++++++++------- frmts/gtiff/gtiffdataset_write.cpp | 129 +++++++++++++++++++------- frmts/gtiff/gtiffrasterband_write.cpp | 13 ++- 9 files changed, 299 insertions(+), 84 deletions(-) diff --git a/autotest/gcore/tiff_write.py b/autotest/gcore/tiff_write.py index 11a2dc8e3e0b..d2195a60a845 100755 --- a/autotest/gcore/tiff_write.py +++ b/autotest/gcore/tiff_write.py @@ -1068,6 +1068,7 @@ def test_tiff_write_26(): ct.SetColorEntry(1, (255, 255, 0, 255)) ct.SetColorEntry(2, (255, 0, 255, 255)) ct.SetColorEntry(3, (0, 255, 255, 255)) + ct.SetColorEntry(3, (0, 255, 255, 255)) ds.GetRasterBand(1).SetRasterColorTable(ct) @@ -1088,8 +1089,6 @@ def test_tiff_write_26(): ct = None ds = None - gdaltest.tiff_drv.Delete("tmp/ct8.tif") - ############################################################################### # Test color table in a 16 bit image @@ -11559,3 +11558,41 @@ def test_tiff_write_too_many_gcps(tmp_vsimem, with_initial_gcps): ds = gdal.Open(filename) assert ds.GetGCPCount() == 0 ds = None + + +############################################################################### +# Test writing/reading a TIFF color map using 256 as the multiplication factor +# https://github.com/OSGeo/gdal/issues/10310 + + +def test_tiff_write_colormap_256_mult_factor(tmp_vsimem): + + filename = str(tmp_vsimem / "test.tif") + ds = gdal.GetDriverByName("GTiff").Create( + filename, 1, 1, 1, gdal.GDT_Byte, ["COLOR_TABLE_MULTIPLIER=256"] + ) + ds.GetRasterBand(1).SetRasterColorInterpretation(gdal.GCI_PaletteIndex) + ct = gdal.ColorTable() + ct.SetColorEntry(0, (0, 0, 0, 255)) + ct.SetColorEntry(1, (1, 2, 3, 255)) + ct.SetColorEntry(2, (255, 255, 255, 255)) + ds.GetRasterBand(1).SetRasterColorTable(ct) + ds = None + + # Check we auto-guess correctly the 256 multiplication factor + ds = gdal.Open(filename) + ct = ds.GetRasterBand(1).GetRasterColorTable() + assert ( + ct.GetColorEntry(0) == (0, 0, 0, 255) + and ct.GetColorEntry(1) == (1, 2, 3, 255) + and ct.GetColorEntry(2) == (255, 255, 255, 255) + ), "Wrong color table entry." + + # Check we get wrong values when not specifying the appropriate multiplier + ds = gdal.OpenEx(filename, open_options=["COLOR_TABLE_MULTIPLIER=257"]) + ct = ds.GetRasterBand(1).GetRasterColorTable() + assert ( + ct.GetColorEntry(0) == (0, 0, 0, 255) + and ct.GetColorEntry(1) == (0, 1, 2, 255) + and ct.GetColorEntry(2) == (254, 254, 254, 255) + ), "Wrong color table entry." diff --git a/doc/source/drivers/raster/gtiff.rst b/doc/source/drivers/raster/gtiff.rst index 1f62e2f17eab..f338e3d77db3 100644 --- a/doc/source/drivers/raster/gtiff.rst +++ b/doc/source/drivers/raster/gtiff.rst @@ -341,15 +341,31 @@ This driver supports the following open options: blocks never written and save space; however, most non-GDAL packages cannot read such files. -- **IGNORE_COG_LAYOUT_BREAK=YES/NO** (GDAL >= 3.8): Updating a COG - (Cloud Optimized GeoTIFF) file generally breaks part of the optimizations, - but still produces a valid GeoTIFF file. +.. oo:: IGNORE_COG_LAYOUT_BREAK + :choices: YES, NO + :since: 3.8 + :default: NO + + Updating a COG (Cloud Optimized GeoTIFF) file generally breaks part of the + optimizations, but still produces a valid GeoTIFF file. Starting with GDAL 3.8, to avoid undesired loss of the COG characteristics, opening such a file in update mode will be rejected, unless this option is also set to YES (default is NO). This option has only effect on COG files and when opening in update mode, and is ignored on regular (Geo)TIFF files. +.. oo:: COLOR_TABLE_MULTIPLIER + :choices: AUTO, 1, 256, 257 + :since: 3.10.0 + + Specifies the value by which to multiply GDAL color table entry values, usually + in [0,255] range, to obtain a TIFF color map value, or on reading by which + to divide TIFF color map values to get a GDAL color table entry. Since GDAL 2.3.0, + GDAL consistently uses 257, but it might be necessary to use 256 for + compatibility with files generated by other software. + In AUTO mode, GDAL 3.10 or later can automatically detect the 256 multiplication + factor when all values in the TIFF color map are multiple of that value. + Creation Issues --------------- @@ -773,6 +789,16 @@ This driver supports the following creation options: .. note:: Write support for GeoTIFF 1.1 requires libgeotiff 1.6.0 or later. +- .. co:: COLOR_TABLE_MULTIPLIER + :choices: 1, 256, 257 + :since: 3.10.0 + :default: 257 + + Specifies the value by which to multiply GDAL color table entry values, usually + in [0,255] range, to obtain a TIFF color map value. Since GDAL 2.3.0, + GDAL consistently uses 257, but it might be necessary to use 256 for + compatibility with files generated by other software. + Subdatasets ~~~~~~~~~~~ diff --git a/frmts/gtiff/geotiff.cpp b/frmts/gtiff/geotiff.cpp index 7102d1adb2d5..21406e1dc551 100644 --- a/frmts/gtiff/geotiff.cpp +++ b/frmts/gtiff/geotiff.cpp @@ -727,7 +727,7 @@ void GTiffWriteJPEGTables(TIFF *hTIFF, const char *pszPhotometric, GTiffDataset::CreateLL(osTmpFilenameIn, nInMemImageWidth, nInMemImageHeight, (nBands <= 4) ? nBands : 1, (l_nBitsPerSample <= 8) ? GDT_Byte : GDT_UInt16, - 0.0, papszLocalParameters, &fpTmp, osTmp); + 0.0, 0, papszLocalParameters, &fpTmp, osTmp); CSLDestroy(papszLocalParameters); if (hTIFFTmp) { @@ -1541,6 +1541,14 @@ void GDALRegister_GTiff() " 1.1" " " #endif + " " ""; /* -------------------------------------------------------------------- */ @@ -1578,6 +1586,15 @@ void GDALRegister_GTiff() " " ""); poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); diff --git a/frmts/gtiff/gt_overview.cpp b/frmts/gtiff/gt_overview.cpp index 38ca89e434c2..644a1c9bc670 100644 --- a/frmts/gtiff/gt_overview.cpp +++ b/frmts/gtiff/gt_overview.cpp @@ -46,6 +46,7 @@ #include "gdal.h" #include "gdal_priv.h" #include "gtiff.h" +#include "gtiffdataset.h" #include "tiff.h" #include "tiffvers.h" #include "tifvsi.h" @@ -749,17 +750,28 @@ CPLErr GTIFFBuildOverviewsEx(const char *pszFilename, int nBands, panBlue = static_cast( CPLCalloc(nColorCount, sizeof(unsigned short))); + const int nColorTableMultiplier = std::max( + 1, + std::min( + 257, + atoi(CSLFetchNameValueDef( + papszOptions, "COLOR_TABLE_MULTIPLIER", + CPLSPrintf( + "%d", + GTiffDataset::DEFAULT_COLOR_TABLE_MULTIPLIER_257))))); + for (int iColor = 0; iColor < nColorCount; iColor++) { GDALColorEntry sRGB = {0, 0, 0, 0}; if (poCT->GetColorEntryAsRGB(iColor, &sRGB)) { - // TODO(schwehr): Check for underflow. - // Going from signed short to unsigned short. - panRed[iColor] = static_cast(257 * sRGB.c1); - panGreen[iColor] = static_cast(257 * sRGB.c2); - panBlue[iColor] = static_cast(257 * sRGB.c3); + panRed[iColor] = GTiffDataset::ClampCTEntry( + iColor, 1, sRGB.c1, nColorTableMultiplier); + panGreen[iColor] = GTiffDataset::ClampCTEntry( + iColor, 2, sRGB.c2, nColorTableMultiplier); + panBlue[iColor] = GTiffDataset::ClampCTEntry( + iColor, 3, sRGB.c3, nColorTableMultiplier); } } } diff --git a/frmts/gtiff/gtiffdataset.cpp b/frmts/gtiff/gtiffdataset.cpp index 40cd55c94bd7..bfa27d35e522 100644 --- a/frmts/gtiff/gtiffdataset.cpp +++ b/frmts/gtiff/gtiffdataset.cpp @@ -1117,6 +1117,7 @@ void GTiffDataset::ScanDirectories() poODS->ShareLockWithParentDataset(this); poODS->SetStructuralMDFromParent(this); poODS->m_pszFilename = CPLStrdup(m_pszFilename); + poODS->m_nColorTableMultiplier = m_nColorTableMultiplier; if (poODS->OpenOffset(VSI_TIFFOpenChild(m_hTIFF), nThisDir, eAccess) != CE_None || poODS->GetRasterCount() != GetRasterCount()) diff --git a/frmts/gtiff/gtiffdataset.h b/frmts/gtiff/gtiffdataset.h index 18b0827caf3b..a59e4efc1a89 100644 --- a/frmts/gtiff/gtiffdataset.h +++ b/frmts/gtiff/gtiffdataset.h @@ -205,6 +205,16 @@ class GTiffDataset final : public GDALPamDataset int m_nRefBaseMapping = 0; int m_nDisableMultiThreadedRead = 0; + public: + static constexpr int DEFAULT_COLOR_TABLE_MULTIPLIER_257 = 257; + + private: + //! Multiplication factor to go from GDAL [0,255] color table range to + // TIFF [0,65535] color map one. + // 0 is not a valid value, and means not specified by user through the + // COLOR_TABLE_MULTIPLIER open / creation option. + int m_nColorTableMultiplier = 0; + GTIFFKeysFlavorEnum m_eGeoTIFFKeysFlavor = GEOTIFF_KEYS_STANDARD; GeoTIFFVersionEnum m_eGeoTIFFVersion = GEOTIFF_VERSION_AUTO; @@ -550,8 +560,8 @@ class GTiffDataset final : public GDALPamDataset static TIFF *CreateLL(const char *pszFilename, int nXSize, int nYSize, int nBands, GDALDataType eType, double dfExtraSpaceForOverviews, - char **papszParamList, VSILFILE **pfpL, - CPLString &osTmpFilename); + int nColorTableMultiplier, char **papszParamList, + VSILFILE **pfpL, CPLString &osTmpFilename); CPLErr WriteEncodedTileOrStrip(uint32_t tile_or_strip, void *data, int bPreserveDataBuffer); @@ -560,6 +570,9 @@ class GTiffDataset final : public GDALPamDataset char **papszParamList, uint32_t nBitsPerSample); static const GTIFFTag *GetTIFFTags(); + + static unsigned short ClampCTEntry(int iColor, int iComp, int nCTEntryVal, + int nMultFactor); }; GTIFFKeysFlavorEnum GetGTIFFKeysFlavor(CSLConstList papszOptions); diff --git a/frmts/gtiff/gtiffdataset_read.cpp b/frmts/gtiff/gtiffdataset_read.cpp index 9e602fa2619d..b90694f8a654 100644 --- a/frmts/gtiff/gtiffdataset_read.cpp +++ b/frmts/gtiff/gtiffdataset_read.cpp @@ -3961,6 +3961,12 @@ GDALDataset *GTiffDataset::Open(GDALOpenInfo *poOpenInfo) poDS->m_bHasGotSiblingFiles = true; } + // Should be capped by 257, to avoid 65535 / m_nColorTableMultiplier to overflow 255 + poDS->m_nColorTableMultiplier = std::max( + 0, std::min(257, + atoi(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, + "COLOR_TABLE_MULTIPLIER", "0")))); + if (poDS->OpenOffset(l_hTIFF, TIFFCurrentDirOffset(l_hTIFF), poOpenInfo->eAccess, bAllowRGBAInterface, true) != CE_None) @@ -5230,54 +5236,87 @@ CPLErr GTiffDataset::OpenOffset(TIFF *hTIFFIn, toff_t nDirOffsetIn, } else { - unsigned short nMaxColor = 0; - m_poColorTable = new GDALColorTable(); const int nColorCount = 1 << m_nBitsPerSample; + if (m_nColorTableMultiplier == 0) + { + // TIFF color maps are in the [0, 65535] range, so some remapping must + // be done to get values in the [0, 255] range, but it is not clear + // how to do that exactly. Since GDAL 2.3.0 we have standardized on + // using a 257 multiplication factor (https://github.com/OSGeo/gdal/commit/eeec5b62e385d53e7f2edaba7b73c7c74bc2af39) + // but other software uses 256 (cf https://github.com/OSGeo/gdal/issues/10310) + // Do a first pass to check if all values are multiples of 256 or 257. + bool bFoundNonZeroEntry = false; + bool bAllValuesMultipleOf256 = true; + bool bAllValuesMultipleOf257 = true; + unsigned short nMaxColor = 0; + for (int iColor = 0; iColor < nColorCount; ++iColor) + { + if (panRed[iColor] > 0 || panGreen[iColor] > 0 || + panBlue[iColor] > 0) + { + bFoundNonZeroEntry = true; + } + if ((panRed[iColor] % 256) != 0 || + (panGreen[iColor] % 256) != 0 || + (panBlue[iColor] % 256) != 0) + { + bAllValuesMultipleOf256 = false; + } + if ((panRed[iColor] % 257) != 0 || + (panGreen[iColor] % 257) != 0 || + (panBlue[iColor] % 257) != 0) + { + bAllValuesMultipleOf257 = false; + } + + nMaxColor = std::max(nMaxColor, panRed[iColor]); + nMaxColor = std::max(nMaxColor, panGreen[iColor]); + nMaxColor = std::max(nMaxColor, panBlue[iColor]); + } + + if (nMaxColor > 0 && nMaxColor < 256) + { + // Bug 1384 - Some TIFF files are generated with color map entry + // values in range 0-255 instead of 0-65535 - try to handle these + // gracefully. + m_nColorTableMultiplier = 1; + CPLDebug("GTiff", + "TIFF ColorTable seems to be improperly scaled with " + "values all in [0,255] range, fixing up."); + } + else + { + if (!bAllValuesMultipleOf256 && !bAllValuesMultipleOf257) + { + CPLDebug("GTiff", + "The color map contains entries which are not " + "multiple of 256 or 257, so we don't know for " + "sure how to remap them to [0, 255]. Default to " + "using a 257 multiplication factor"); + } + m_nColorTableMultiplier = + (bFoundNonZeroEntry && bAllValuesMultipleOf256) + ? 256 + : DEFAULT_COLOR_TABLE_MULTIPLIER_257; + } + } + CPLAssert(m_nColorTableMultiplier > 0); + CPLAssert(m_nColorTableMultiplier <= 257); for (int iColor = nColorCount - 1; iColor >= 0; iColor--) { - // TODO(schwehr): Ensure the color entries are never negative? - const unsigned short divisor = 257; const GDALColorEntry oEntry = { - static_cast(panRed[iColor] / divisor), - static_cast(panGreen[iColor] / divisor), - static_cast(panBlue[iColor] / divisor), + static_cast(panRed[iColor] / m_nColorTableMultiplier), + static_cast(panGreen[iColor] / m_nColorTableMultiplier), + static_cast(panBlue[iColor] / m_nColorTableMultiplier), static_cast( m_bNoDataSet && static_cast(m_dfNoDataValue) == iColor ? 0 : 255)}; m_poColorTable->SetColorEntry(iColor, &oEntry); - - nMaxColor = std::max(nMaxColor, panRed[iColor]); - nMaxColor = std::max(nMaxColor, panGreen[iColor]); - nMaxColor = std::max(nMaxColor, panBlue[iColor]); - } - - // Bug 1384 - Some TIFF files are generated with color map entry - // values in range 0-255 instead of 0-65535 - try to handle these - // gracefully. - if (nMaxColor > 0 && nMaxColor < 256) - { - CPLDebug( - "GTiff", - "TIFF ColorTable seems to be improperly scaled, fixing up."); - - for (int iColor = nColorCount - 1; iColor >= 0; iColor--) - { - // TODO(schwehr): Ensure the color entries are never negative? - const GDALColorEntry oEntry = { - static_cast(panRed[iColor]), - static_cast(panGreen[iColor]), - static_cast(panBlue[iColor]), - m_bNoDataSet && static_cast(m_dfNoDataValue) == iColor - ? static_cast(0) - : static_cast(255)}; - - m_poColorTable->SetColorEntry(iColor, &oEntry); - } } } diff --git a/frmts/gtiff/gtiffdataset_write.cpp b/frmts/gtiff/gtiffdataset_write.cpp index 062ba5cd1bfc..3e0696bec063 100644 --- a/frmts/gtiff/gtiffdataset_write.cpp +++ b/frmts/gtiff/gtiffdataset_write.cpp @@ -2579,13 +2579,11 @@ CPLErr GTiffDataset::RegisterNewOverviewDataset(toff_t nOverviewOffset, /* CreateTIFFColorTable() */ /************************************************************************/ -static void CreateTIFFColorTable(GDALColorTable *poColorTable, int nBits, - std::vector &anTRed, - std::vector &anTGreen, - std::vector &anTBlue, - unsigned short *&panRed, - unsigned short *&panGreen, - unsigned short *&panBlue) +static void CreateTIFFColorTable( + GDALColorTable *poColorTable, int nBits, int nColorTableMultiplier, + std::vector &anTRed, std::vector &anTGreen, + std::vector &anTBlue, unsigned short *&panRed, + unsigned short *&panGreen, unsigned short *&panBlue) { int nColors; @@ -2608,9 +2606,12 @@ static void CreateTIFFColorTable(GDALColorTable *poColorTable, int nBits, poColorTable->GetColorEntryAsRGB(iColor, &sRGB); - anTRed[iColor] = static_cast(257 * sRGB.c1); - anTGreen[iColor] = static_cast(257 * sRGB.c2); - anTBlue[iColor] = static_cast(257 * sRGB.c3); + anTRed[iColor] = GTiffDataset::ClampCTEntry(iColor, 1, sRGB.c1, + nColorTableMultiplier); + anTGreen[iColor] = GTiffDataset::ClampCTEntry( + iColor, 2, sRGB.c2, nColorTableMultiplier); + anTBlue[iColor] = GTiffDataset::ClampCTEntry(iColor, 3, sRGB.c3, + nColorTableMultiplier); } else { @@ -2829,8 +2830,12 @@ CPLErr GTiffDataset::CreateOverviewsFromSrcOverviews(GDALDataset *poSrcDS, if (nPhotometric == PHOTOMETRIC_PALETTE && m_poColorTable != nullptr) { - CreateTIFFColorTable(m_poColorTable, nOvBitsPerSample, anTRed, anTGreen, - anTBlue, panRed, panGreen, panBlue); + if (m_nColorTableMultiplier == 0) + m_nColorTableMultiplier = DEFAULT_COLOR_TABLE_MULTIPLIER_257; + + CreateTIFFColorTable(m_poColorTable, nOvBitsPerSample, + m_nColorTableMultiplier, anTRed, anTGreen, anTBlue, + panRed, panGreen, panBlue); } int nOvrBlockXSize = 0; @@ -3116,8 +3121,12 @@ CPLErr GTiffDataset::IBuildOverviews(const char *pszResampling, int nOverviews, if (nPhotometric == PHOTOMETRIC_PALETTE && m_poColorTable != nullptr) { - CreateTIFFColorTable(m_poColorTable, nOvBitsPerSample, anTRed, anTGreen, - anTBlue, panRed, panGreen, panBlue); + if (m_nColorTableMultiplier == 0) + m_nColorTableMultiplier = DEFAULT_COLOR_TABLE_MULTIPLIER_257; + + CreateTIFFColorTable(m_poColorTable, nOvBitsPerSample, + m_nColorTableMultiplier, anTRed, anTGreen, anTBlue, + panRed, panGreen, panBlue); } /* -------------------------------------------------------------------- */ @@ -5002,8 +5011,8 @@ static GTiffProfile GetProfile(const char *pszProfile) TIFF *GTiffDataset::CreateLL(const char *pszFilename, int nXSize, int nYSize, int l_nBands, GDALDataType eType, double dfExtraSpaceForOverviews, - char **papszParamList, VSILFILE **pfpL, - CPLString &l_osTmpFilename) + int nColorTableMultiplier, char **papszParamList, + VSILFILE **pfpL, CPLString &l_osTmpFilename) { GTiffOneTimeInit(); @@ -5833,9 +5842,12 @@ TIFF *GTiffDataset::CreateLL(const char *pszFilename, int nXSize, int nYSize, { if (eType == GDT_Byte) { - panTRed[iColor] = static_cast(257 * iColor); - panTGreen[iColor] = static_cast(257 * iColor); - panTBlue[iColor] = static_cast(257 * iColor); + panTRed[iColor] = GTiffDataset::ClampCTEntry( + iColor, 1, iColor, nColorTableMultiplier); + panTGreen[iColor] = GTiffDataset::ClampCTEntry( + iColor, 2, iColor, nColorTableMultiplier); + panTBlue[iColor] = GTiffDataset::ClampCTEntry( + iColor, 3, iColor, nColorTableMultiplier); } else { @@ -6071,7 +6083,7 @@ int GTiffDataset::GuessJPEGQuality(bool &bOutHasQuantizationTable, CPLString osTmp; TIFF *hTIFFTmp = CreateLL(osTmpFilenameIn, 16, 16, (nBands <= 4) ? nBands : 1, - GetRasterBand(1)->GetRasterDataType(), 0.0, + GetRasterBand(1)->GetRasterDataType(), 0.0, 0, papszLocalParameters, &fpTmp, osTmp); CPLPopErrorHandler(); if (!hTIFFTmp) @@ -6221,11 +6233,19 @@ GDALDataset *GTiffDataset::Create(const char *pszFilename, int nXSize, VSILFILE *l_fpL = nullptr; CPLString l_osTmpFilename; + const int nColorTableMultiplier = std::max( + 1, + std::min(257, + atoi(CSLFetchNameValueDef( + papszParamList, "COLOR_TABLE_MULTIPLIER", + CPLSPrintf("%d", DEFAULT_COLOR_TABLE_MULTIPLIER_257))))); + /* -------------------------------------------------------------------- */ /* Create the underlying TIFF file. */ /* -------------------------------------------------------------------- */ TIFF *l_hTIFF = CreateLL(pszFilename, nXSize, nYSize, l_nBands, eType, 0, - papszParamList, &l_fpL, l_osTmpFilename); + nColorTableMultiplier, papszParamList, &l_fpL, + l_osTmpFilename); const bool bStreaming = !l_osTmpFilename.empty(); if (l_hTIFF == nullptr) @@ -6252,6 +6272,9 @@ GDALDataset *GTiffDataset::Create(const char *pszFilename, int nXSize, poDS->nRasterXSize = nXSize; poDS->nRasterYSize = nYSize; poDS->eAccess = GA_Update; + + poDS->m_nColorTableMultiplier = nColorTableMultiplier; + poDS->m_bCrystalized = false; poDS->m_nSamplesPerPixel = static_cast(l_nBands); poDS->m_pszFilename = CPLStrdup(pszFilename); @@ -6344,11 +6367,10 @@ GDALDataset *GTiffDataset::Create(const char *pszFilename, int nXSize, for (int iColor = nColorCount - 1; iColor >= 0; iColor--) { - const unsigned short divisor = 257; const GDALColorEntry oEntry = { - static_cast(panRed[iColor] / divisor), - static_cast(panGreen[iColor] / divisor), - static_cast(panBlue[iColor] / divisor), + static_cast(panRed[iColor] / nColorTableMultiplier), + static_cast(panGreen[iColor] / nColorTableMultiplier), + static_cast(panBlue[iColor] / nColorTableMultiplier), static_cast(255)}; poDS->m_poColorTable->SetColorEntry(iColor, &oEntry); @@ -6919,9 +6941,17 @@ GDALDataset *GTiffDataset::CreateCopy(const char *pszFilename, const int nXSize = poSrcDS->GetRasterXSize(); const int nYSize = poSrcDS->GetRasterYSize(); + + const int nColorTableMultiplier = std::max( + 1, + std::min(257, + atoi(CSLFetchNameValueDef( + papszOptions, "COLOR_TABLE_MULTIPLIER", + CPLSPrintf("%d", DEFAULT_COLOR_TABLE_MULTIPLIER_257))))); + TIFF *l_hTIFF = CreateLL(pszFilename, nXSize, nYSize, l_nBands, eType, - dfExtraSpaceForOverviews, papszCreateOptions, - &l_fpL, l_osTmpFilename); + dfExtraSpaceForOverviews, nColorTableMultiplier, + papszCreateOptions, &l_fpL, l_osTmpFilename); const bool bStreaming = !l_osTmpFilename.empty(); CSLDestroy(papszCreateOptions); @@ -7024,9 +7054,12 @@ GDALDataset *GTiffDataset::CreateCopy(const char *pszFilename, poCT->GetColorEntryAsRGB(iColor, &sRGB); - anTRed[iColor] = static_cast(257 * sRGB.c1); - anTGreen[iColor] = static_cast(257 * sRGB.c2); - anTBlue[iColor] = static_cast(257 * sRGB.c3); + anTRed[iColor] = GTiffDataset::ClampCTEntry( + iColor, 1, sRGB.c1, nColorTableMultiplier); + anTGreen[iColor] = GTiffDataset::ClampCTEntry( + iColor, 2, sRGB.c2, nColorTableMultiplier); + anTBlue[iColor] = GTiffDataset::ClampCTEntry( + iColor, 3, sRGB.c3, nColorTableMultiplier); } else { @@ -7061,9 +7094,12 @@ GDALDataset *GTiffDataset::CreateCopy(const char *pszFilename, poCT->GetColorEntryAsRGB(iColor, &sRGB); - panTRed[iColor] = static_cast(257 * sRGB.c1); - panTGreen[iColor] = static_cast(257 * sRGB.c2); - panTBlue[iColor] = static_cast(257 * sRGB.c3); + panTRed[iColor] = GTiffDataset::ClampCTEntry( + iColor, 1, sRGB.c1, nColorTableMultiplier); + panTGreen[iColor] = GTiffDataset::ClampCTEntry( + iColor, 2, sRGB.c2, nColorTableMultiplier); + panTBlue[iColor] = GTiffDataset::ClampCTEntry( + iColor, 3, sRGB.c3, nColorTableMultiplier); } else { @@ -7485,6 +7521,7 @@ GDALDataset *GTiffDataset::CreateCopy(const char *pszFilename, poDS->m_pszFilename = CPLStrdup(pszFilename); poDS->m_fpL = l_fpL; poDS->m_bIMDRPCMetadataLoaded = true; + poDS->m_nColorTableMultiplier = nColorTableMultiplier; const bool bAppend = CPLFetchBool(papszOptions, "APPEND_SUBDATASET", false); if (poDS->OpenOffset(l_hTIFF, @@ -8785,3 +8822,29 @@ CPLErr GTiffRasterBand::CreateMaskBand(int nFlagsIn) return GDALPamRasterBand::CreateMaskBand(nFlagsIn); } + +/************************************************************************/ +/* ClampCTEntry() */ +/************************************************************************/ + +/* static */ unsigned short GTiffDataset::ClampCTEntry(int iColor, int iComp, + int nCTEntryVal, + int nMultFactor) +{ + const int nVal = nCTEntryVal * nMultFactor; + if (nVal < 0) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Color table entry [%d][%d] = %d, clamped to 0", iColor, iComp, + nCTEntryVal); + return 0; + } + if (nVal > 65535) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Color table entry [%d][%d] = %d, clamped to 65535", iColor, + iComp, nCTEntryVal); + return 65535; + } + return static_cast(nVal); +} diff --git a/frmts/gtiff/gtiffrasterband_write.cpp b/frmts/gtiff/gtiffrasterband_write.cpp index de8d09e6934e..2cdbd126e4dd 100644 --- a/frmts/gtiff/gtiffrasterband_write.cpp +++ b/frmts/gtiff/gtiffrasterband_write.cpp @@ -651,6 +651,10 @@ CPLErr GTiffRasterBand::SetColorTable(GDALColorTable *poCT) unsigned short *panTBlue = static_cast( CPLMalloc(sizeof(unsigned short) * nColors)); + if (m_poGDS->m_nColorTableMultiplier == 0) + m_poGDS->m_nColorTableMultiplier = + GTiffDataset::DEFAULT_COLOR_TABLE_MULTIPLIER_257; + for (int iColor = 0; iColor < nColors; ++iColor) { if (iColor < poCT->GetColorEntryCount()) @@ -658,9 +662,12 @@ CPLErr GTiffRasterBand::SetColorTable(GDALColorTable *poCT) GDALColorEntry sRGB; poCT->GetColorEntryAsRGB(iColor, &sRGB); - panTRed[iColor] = static_cast(257 * sRGB.c1); - panTGreen[iColor] = static_cast(257 * sRGB.c2); - panTBlue[iColor] = static_cast(257 * sRGB.c3); + panTRed[iColor] = GTiffDataset::ClampCTEntry( + iColor, 1, sRGB.c1, m_poGDS->m_nColorTableMultiplier); + panTGreen[iColor] = GTiffDataset::ClampCTEntry( + iColor, 2, sRGB.c2, m_poGDS->m_nColorTableMultiplier); + panTBlue[iColor] = GTiffDataset::ClampCTEntry( + iColor, 3, sRGB.c3, m_poGDS->m_nColorTableMultiplier); } else { From b9a770af73d16f25daba1e415b9d7afa4a4949c7 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 18:01:56 +0200 Subject: [PATCH 0264/1119] GTiff: use std::unique_ptr instead of raw pointer --- frmts/gtiff/gtiffdataset.cpp | 4 +--- frmts/gtiff/gtiffdataset.h | 2 +- frmts/gtiff/gtiffdataset_read.cpp | 11 +++++------ frmts/gtiff/gtiffdataset_write.cpp | 6 +++--- frmts/gtiff/gtiffrasterband_read.cpp | 2 +- frmts/gtiff/gtiffrasterband_write.cpp | 11 ++--------- 6 files changed, 13 insertions(+), 23 deletions(-) diff --git a/frmts/gtiff/gtiffdataset.cpp b/frmts/gtiff/gtiffdataset.cpp index bfa27d35e522..3e6f28c6dfc5 100644 --- a/frmts/gtiff/gtiffdataset.cpp +++ b/frmts/gtiff/gtiffdataset.cpp @@ -308,9 +308,7 @@ std::tuple GTiffDataset::Finalize() bDroppedRef = true; } - if (m_poColorTable != nullptr) - delete m_poColorTable; - m_poColorTable = nullptr; + m_poColorTable.reset(); if (m_hTIFF) { diff --git a/frmts/gtiff/gtiffdataset.h b/frmts/gtiff/gtiffdataset.h index a59e4efc1a89..3065cc39bec0 100644 --- a/frmts/gtiff/gtiffdataset.h +++ b/frmts/gtiff/gtiffdataset.h @@ -155,7 +155,7 @@ class GTiffDataset final : public GDALPamDataset m_poMaskExtOvrDS{}; // Used with MASK_OVERVIEW_DATASET open option GTiffJPEGOverviewDS **m_papoJPEGOverviewDS = nullptr; std::vector m_aoGCPs{}; - GDALColorTable *m_poColorTable = nullptr; + std::unique_ptr m_poColorTable{}; char **m_papszMetadataFiles = nullptr; GByte *m_pabyBlockBuf = nullptr; char **m_papszCreationOptions = nullptr; diff --git a/frmts/gtiff/gtiffdataset_read.cpp b/frmts/gtiff/gtiffdataset_read.cpp index b90694f8a654..cd4d5aac82d0 100644 --- a/frmts/gtiff/gtiffdataset_read.cpp +++ b/frmts/gtiff/gtiffdataset_read.cpp @@ -4569,11 +4569,10 @@ void GTiffDataset::ApplyPamInfo() if (i == 1) { - auto poCT = poBand->GDALPamRasterBand::GetColorTable(); + const auto poCT = poBand->GDALPamRasterBand::GetColorTable(); if (poCT) { - delete m_poColorTable; - m_poColorTable = poCT->Clone(); + m_poColorTable.reset(poCT->Clone()); } } } @@ -5215,7 +5214,7 @@ CPLErr GTiffDataset::OpenOffset(TIFF *hTIFFIn, toff_t nDirOffsetIn, // data types (per #1882) if (m_nBitsPerSample <= 16 && m_nPhotometric == PHOTOMETRIC_MINISWHITE) { - m_poColorTable = new GDALColorTable(); + m_poColorTable = std::make_unique(); const int nColorCount = 1 << m_nBitsPerSample; for (int iColor = 0; iColor < nColorCount; ++iColor) @@ -5231,12 +5230,12 @@ CPLErr GTiffDataset::OpenOffset(TIFF *hTIFFIn, toff_t nDirOffsetIn, } else { - m_poColorTable = nullptr; + m_poColorTable.reset(); } } else { - m_poColorTable = new GDALColorTable(); + m_poColorTable = std::make_unique(); const int nColorCount = 1 << m_nBitsPerSample; diff --git a/frmts/gtiff/gtiffdataset_write.cpp b/frmts/gtiff/gtiffdataset_write.cpp index 3e0696bec063..ef03725782cd 100644 --- a/frmts/gtiff/gtiffdataset_write.cpp +++ b/frmts/gtiff/gtiffdataset_write.cpp @@ -2833,7 +2833,7 @@ CPLErr GTiffDataset::CreateOverviewsFromSrcOverviews(GDALDataset *poSrcDS, if (m_nColorTableMultiplier == 0) m_nColorTableMultiplier = DEFAULT_COLOR_TABLE_MULTIPLIER_257; - CreateTIFFColorTable(m_poColorTable, nOvBitsPerSample, + CreateTIFFColorTable(m_poColorTable.get(), nOvBitsPerSample, m_nColorTableMultiplier, anTRed, anTGreen, anTBlue, panRed, panGreen, panBlue); } @@ -3124,7 +3124,7 @@ CPLErr GTiffDataset::IBuildOverviews(const char *pszResampling, int nOverviews, if (m_nColorTableMultiplier == 0) m_nColorTableMultiplier = DEFAULT_COLOR_TABLE_MULTIPLIER_257; - CreateTIFFColorTable(m_poColorTable, nOvBitsPerSample, + CreateTIFFColorTable(m_poColorTable.get(), nOvBitsPerSample, m_nColorTableMultiplier, anTRed, anTGreen, anTBlue, panRed, panGreen, panBlue); } @@ -6361,7 +6361,7 @@ GDALDataset *GTiffDataset::Create(const char *pszFilename, int nXSize, TIFFGetField(l_hTIFF, TIFFTAG_COLORMAP, &panRed, &panGreen, &panBlue)) { - poDS->m_poColorTable = new GDALColorTable(); + poDS->m_poColorTable = std::make_unique(); const int nColorCount = 1 << poDS->m_nBitsPerSample; diff --git a/frmts/gtiff/gtiffrasterband_read.cpp b/frmts/gtiff/gtiffrasterband_read.cpp index b16d57b4436a..6dcc6e7c6604 100644 --- a/frmts/gtiff/gtiffrasterband_read.cpp +++ b/frmts/gtiff/gtiffrasterband_read.cpp @@ -1633,7 +1633,7 @@ GDALColorTable *GTiffRasterBand::GetColorTable() m_poGDS->LoadGeoreferencingAndPamIfNeeded(); if (nBand == 1) - return m_poGDS->m_poColorTable; + return m_poGDS->m_poColorTable.get(); return nullptr; } diff --git a/frmts/gtiff/gtiffrasterband_write.cpp b/frmts/gtiff/gtiffrasterband_write.cpp index 2cdbd126e4dd..5798e5bbec81 100644 --- a/frmts/gtiff/gtiffrasterband_write.cpp +++ b/frmts/gtiff/gtiffrasterband_write.cpp @@ -624,11 +624,7 @@ CPLErr GTiffRasterBand::SetColorTable(GDALColorTable *poCT) TIFFUnsetField(m_poGDS->m_hTIFF, TIFFTAG_COLORMAP); } - if (m_poGDS->m_poColorTable) - { - delete m_poGDS->m_poColorTable; - m_poGDS->m_poColorTable = nullptr; - } + m_poGDS->m_poColorTable.reset(); return CE_None; } @@ -696,10 +692,7 @@ CPLErr GTiffRasterBand::SetColorTable(GDALColorTable *poCT) eErr = GDALPamRasterBand::SetColorTable(poCT); } - if (m_poGDS->m_poColorTable) - delete m_poGDS->m_poColorTable; - - m_poGDS->m_poColorTable = poCT->Clone(); + m_poGDS->m_poColorTable.reset(poCT->Clone()); m_eBandInterp = GCI_PaletteIndex; return eErr; From 992e862c3b4ffcd918662227c0b136ab01179282 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 18:17:25 +0200 Subject: [PATCH 0265/1119] CMake: move setting C/C++ standards from CMakeLists.txt to cmake/helpers/GdalCAndCXXStandards.cmake --- CMakeLists.txt | 12 +----------- cmake/helpers/GdalCAndCXXStandards.cmake | 10 ++++++++++ 2 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 cmake/helpers/GdalCAndCXXStandards.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index f6f81c1c929d..bc27733f8199 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,18 +35,8 @@ define_property( PROPERTY PLUGIN_OUTPUT_DIR BRIEF_DOCS "Plugin modules build directories" FULL_DOCS "Plugin modules build directories") -# -# check compiler and set preferences. -if (NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 17) - set(CMAKE_CXX_STANDARD_REQUIRED ON) -endif() - -if (NOT CMAKE_C_STANDARD) - set(CMAKE_C_STANDARD 99) - set(CMAKE_C_STANDARD_REQUIRED ON) -endif() +include(GdalCAndCXXStandards) # if (MSVC) add_definitions(-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) diff --git a/cmake/helpers/GdalCAndCXXStandards.cmake b/cmake/helpers/GdalCAndCXXStandards.cmake new file mode 100644 index 000000000000..6e2480cab02c --- /dev/null +++ b/cmake/helpers/GdalCAndCXXStandards.cmake @@ -0,0 +1,10 @@ + +if (NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if (NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) + set(CMAKE_C_STANDARD_REQUIRED ON) +endif() From 59c0d1b979d0cb718b4786c35b1ef4f729308445 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 18:20:56 +0200 Subject: [PATCH 0266/1119] CMake: move things related to setting compilation flags to cmake/helpers/GdalCompilationFlags.cmake --- CMakeLists.txt | 6 - cmake/helpers/GdalCompilationFlags.cmake | 219 +++++++++++++++++++++++ gdal.cmake | 211 +--------------------- 3 files changed, 220 insertions(+), 216 deletions(-) create mode 100644 cmake/helpers/GdalCompilationFlags.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index bc27733f8199..32b0011b45d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,12 +37,6 @@ define_property( FULL_DOCS "Plugin modules build directories") include(GdalCAndCXXStandards) -# -if (MSVC) - add_definitions(-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) - add_definitions(-DNOMINMAX) -endif () -# include(CheckCompilerMachineOption) include(CheckCompilerSIMDFeature) include(Ccache) diff --git a/cmake/helpers/GdalCompilationFlags.cmake b/cmake/helpers/GdalCompilationFlags.cmake new file mode 100644 index 000000000000..3501de613802 --- /dev/null +++ b/cmake/helpers/GdalCompilationFlags.cmake @@ -0,0 +1,219 @@ + +# ###################################################################################################################### +# Detect available warning flags + +include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) + +# Do that check now, since we need the result of HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT for cpl_config.h + +set(GDAL_C_WARNING_FLAGS) +set(GDAL_CXX_WARNING_FLAGS) + +if (MSVC) + # 1. conditional expression is constant + # 2. 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' + # 3. non DLL-interface classkey 'identifier' used as base for DLL-interface classkey 'identifier' + # 4. ?????????? + # 5. 'identifier' : unreferenced formal parameter + # 6. 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch + # 7. nonstandard extension used : translation unit is empty (only applies to C source code) + # 8. new behavior: elements of array 'array' will be default initialized (needed for + # https://trac.osgeo.org/gdal/changeset/35593) + # 9. interaction between '_setjmp' and C++ object destruction is non-portable + # + set(GDAL_C_WARNING_FLAGS + /W4 + /wd4127 + /wd4251 + /wd4275 + /wd4786 + /wd4100 + /wd4245 + /wd4206 + /wd4351 + /wd4611) + set(GDAL_CXX_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS}) + add_compile_options(/EHsc) + + # The following are extra disables that can be applied to external source not under our control that we wish to use + # less stringent warnings with. + set(GDAL_SOFTWARNFLAGS + /wd4244 + /wd4702 + /wd4701 + /wd4013 + /wd4706 + /wd4057 + /wd4210 + /wd4305) + +else () + + set(GDAL_SOFTWARNFLAGS "") + + macro (detect_and_set_c_warning_flag flag_name) + string(TOUPPER ${flag_name} flag_name_upper) + string(REPLACE "-" "_" flag_name_upper "${flag_name_upper}") + string(REPLACE "=" "_" flag_name_upper "${flag_name_upper}") + check_c_compiler_flag(-W${flag_name} "HAVE_WFLAG_${flag_name_upper}") + if (HAVE_WFLAG_${flag_name_upper}) + set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -W${flag_name}) + endif () + endmacro () + + macro (detect_and_set_cxx_warning_flag flag_name) + string(TOUPPER ${flag_name} flag_name_upper) + string(REPLACE "-" "_" flag_name_upper "${flag_name_upper}") + string(REPLACE "=" "_" flag_name_upper "${flag_name_upper}") + check_cxx_compiler_flag(-W${flag_name} "HAVE_WFLAG_${flag_name_upper}") + if (HAVE_WFLAG_${flag_name_upper}) + set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -W${flag_name}) + endif () + endmacro () + + macro (detect_and_set_c_and_cxx_warning_flag flag_name) + string(TOUPPER ${flag_name} flag_name_upper) + string(REPLACE "-" "_" flag_name_upper "${flag_name_upper}") + string(REPLACE "=" "_" flag_name_upper "${flag_name_upper}") + check_c_compiler_flag(-W${flag_name} "HAVE_WFLAG_${flag_name_upper}") + if (HAVE_WFLAG_${flag_name_upper}) + set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -W${flag_name}) + set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -W${flag_name}) + endif () + endmacro () + + detect_and_set_c_and_cxx_warning_flag(all) + detect_and_set_c_and_cxx_warning_flag(extra) + detect_and_set_c_and_cxx_warning_flag(init-self) + detect_and_set_c_and_cxx_warning_flag(unused-parameter) + detect_and_set_c_warning_flag(missing-prototypes) + detect_and_set_c_and_cxx_warning_flag(missing-declarations) + detect_and_set_c_and_cxx_warning_flag(shorten-64-to-32) + detect_and_set_c_and_cxx_warning_flag(logical-op) + detect_and_set_c_and_cxx_warning_flag(shadow) + detect_and_set_cxx_warning_flag(shadow-field) # CLang only for now + detect_and_set_c_and_cxx_warning_flag(missing-include-dirs) + check_c_compiler_flag("-Wformat -Werror=format-security -Wno-format-nonliteral" HAVE_WFLAG_FORMAT_SECURITY) + if (HAVE_WFLAG_FORMAT_SECURITY) + set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -Wformat -Werror=format-security -Wno-format-nonliteral) + set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -Wformat -Werror=format-security -Wno-format-nonliteral) + else () + detect_and_set_c_and_cxx_warning_flag(format) + endif () + detect_and_set_c_and_cxx_warning_flag(error=vla) + detect_and_set_c_and_cxx_warning_flag(no-clobbered) + detect_and_set_c_and_cxx_warning_flag(date-time) + detect_and_set_c_and_cxx_warning_flag(null-dereference) + detect_and_set_c_and_cxx_warning_flag(duplicate-cond) + detect_and_set_cxx_warning_flag(extra-semi) + detect_and_set_c_and_cxx_warning_flag(comma) + detect_and_set_c_and_cxx_warning_flag(float-conversion) + check_c_compiler_flag("-Wdocumentation -Wno-documentation-deprecated-sync" HAVE_WFLAG_DOCUMENTATION_AND_NO_DEPRECATED) + if (HAVE_WFLAG_DOCUMENTATION_AND_NO_DEPRECATED) + set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -Wdocumentation -Wno-documentation-deprecated-sync) + set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -Wdocumentation -Wno-documentation-deprecated-sync) + endif () + detect_and_set_cxx_warning_flag(unused-private-field) + detect_and_set_cxx_warning_flag(non-virtual-dtor) + detect_and_set_cxx_warning_flag(overloaded-virtual) + detect_and_set_cxx_warning_flag(suggest-override) + + check_cxx_compiler_flag(-fno-operator-names HAVE_FLAG_NO_OPERATOR_NAMES) + if (HAVE_FLAG_NO_OPERATOR_NAMES) + set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -fno-operator-names) + endif () + + check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT) + if (HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT) + set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -Wzero-as-null-pointer-constant) + endif () + + # Detect -Wold-style-cast but do not add it by default, as not all targets support it + check_cxx_compiler_flag(-Wold-style-cast HAVE_WFLAG_OLD_STYLE_CAST) + if (HAVE_WFLAG_OLD_STYLE_CAST) + set(WFLAG_OLD_STYLE_CAST -Wold-style-cast) + endif () + + # Detect Weffc++ but do not add it by default, as not all targets support it + check_cxx_compiler_flag(-Weffc++ HAVE_WFLAG_EFFCXX) + if (HAVE_WFLAG_EFFCXX) + set(WFLAG_EFFCXX -Weffc++) + endif () + + if (CMAKE_BUILD_TYPE MATCHES Debug) + check_c_compiler_flag(-ftrapv HAVE_FTRAPV) + if (HAVE_FTRAPV) + set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -ftrapv) + set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -ftrapv) + endif () + endif () + +endif () + +add_compile_definitions($<$:DEBUG>) + +# message(STATUS "GDAL_C_WARNING_FLAGS: ${GDAL_C_WARNING_FLAGS}") message(STATUS "GDAL_CXX_WARNING_FLAGS: ${GDAL_CXX_WARNING_FLAGS}") + +if (CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + check_cxx_compiler_flag(-fno-finite-math-only HAVE_FLAG_NO_FINITE_MATH_ONLY) + if (HAVE_FLAG_NO_FINITE_MATH_ONLY) + # Intel CXX compiler based on clang defaults to -ffinite-math-only, which breaks std::isinf(), std::isnan(), etc. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-finite-math-only") + endif () + + set(TEST_LINK_STDCPP_SOURCE_CODE + "#include + int main(){ + std::string s; + s += \"x\"; + return 0; + }") + check_cxx_source_compiles("${TEST_LINK_STDCPP_SOURCE_CODE}" _TEST_LINK_STDCPP) + if( NOT _TEST_LINK_STDCPP ) + message(WARNING "Cannot link code using standard C++ library. Automatically adding -lstdc++ to CMAKE_EXE_LINKER_FLAGS, CMAKE_SHARED_LINKER_FLAGS and CMAKE_MODULE_LINKER_FLAGS") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lstdc++") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -lstdc++") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -lstdc++") + + check_cxx_source_compiles("${TEST_LINK_STDCPP_SOURCE_CODE}" _TEST_LINK_STDCPP_AGAIN) + if( NOT _TEST_LINK_STDCPP_AGAIN ) + message(FATAL_ERROR "Cannot link C++ program") + endif() + endif() + + check_c_compiler_flag(-wd188 HAVE_WD188) # enumerated type mixed with another type + if( HAVE_WD188 ) + set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -wd188) + endif() + check_c_compiler_flag(-wd2259 HAVE_WD2259) # non-pointer conversion from ... may lose significant bits + if( HAVE_WD2259 ) + set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -wd2259) + endif() + check_c_compiler_flag(-wd2312 HAVE_WD2312) # pointer cast involving 64-bit pointed-to type + if( HAVE_WD2259 ) + set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -wd2312) + endif() +endif () + +# Default definitions during build +add_definitions(-DGDAL_COMPILATION) + +if (MSVC) + add_definitions(-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) + add_definitions(-DNOMINMAX) +endif () + +if (MINGW) + if (TARGET_CPU MATCHES "x86_64") + add_definitions(-m64) + endif () + # Workaround for export too large error - force problematic large file to be optimized to prevent string table + # overflow error Used -Os instead of -O2 as previous issues had mentioned, since -Os is roughly speaking -O2, + # excluding any optimizations that take up extra space. Given that the issue is a string table overflowing, -Os seemed + # appropriate. Solves issue of https://github.com/OSGeo/gdal/issues/4706 with for example x86_64-w64-mingw32-gcc-posix + # (GCC) 9.3-posix 20200320 + if (CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE STREQUAL "") + add_compile_options(-Os) + endif () +endif () diff --git a/gdal.cmake b/gdal.cmake index e390f8e41041..f806dad4a74d 100644 --- a/gdal.cmake +++ b/gdal.cmake @@ -44,199 +44,7 @@ option(CSHARP_MONO "Whether to force the C# compiler to be Mono" OFF) # this file is populated only be scripts/install_bash_completions.cmake.in install(CODE "file(REMOVE \"${PROJECT_BINARY_DIR}/install_manifest_extra.txt\")") -# ###################################################################################################################### -# Detect available warning flags - -# Do that check now, since we need the result of HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT for cpl_config.h - -set(GDAL_C_WARNING_FLAGS) -set(GDAL_CXX_WARNING_FLAGS) - -if (MSVC) - # 1. conditional expression is constant - # 2. 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2' - # 3. non DLL-interface classkey 'identifier' used as base for DLL-interface classkey 'identifier' - # 4. ?????????? - # 5. 'identifier' : unreferenced formal parameter - # 6. 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch - # 7. nonstandard extension used : translation unit is empty (only applies to C source code) - # 8. new behavior: elements of array 'array' will be default initialized (needed for - # https://trac.osgeo.org/gdal/changeset/35593) - # 9. interaction between '_setjmp' and C++ object destruction is non-portable - # - set(GDAL_C_WARNING_FLAGS - /W4 - /wd4127 - /wd4251 - /wd4275 - /wd4786 - /wd4100 - /wd4245 - /wd4206 - /wd4351 - /wd4611) - set(GDAL_CXX_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS}) - add_compile_options(/EHsc) - - # The following are extra disables that can be applied to external source not under our control that we wish to use - # less stringent warnings with. - set(GDAL_SOFTWARNFLAGS - /wd4244 - /wd4702 - /wd4701 - /wd4013 - /wd4706 - /wd4057 - /wd4210 - /wd4305) - -else () - - set(GDAL_SOFTWARNFLAGS "") - - macro (detect_and_set_c_warning_flag flag_name) - string(TOUPPER ${flag_name} flag_name_upper) - string(REPLACE "-" "_" flag_name_upper "${flag_name_upper}") - string(REPLACE "=" "_" flag_name_upper "${flag_name_upper}") - check_c_compiler_flag(-W${flag_name} "HAVE_WFLAG_${flag_name_upper}") - if (HAVE_WFLAG_${flag_name_upper}) - set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -W${flag_name}) - endif () - endmacro () - - macro (detect_and_set_cxx_warning_flag flag_name) - string(TOUPPER ${flag_name} flag_name_upper) - string(REPLACE "-" "_" flag_name_upper "${flag_name_upper}") - string(REPLACE "=" "_" flag_name_upper "${flag_name_upper}") - check_cxx_compiler_flag(-W${flag_name} "HAVE_WFLAG_${flag_name_upper}") - if (HAVE_WFLAG_${flag_name_upper}) - set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -W${flag_name}) - endif () - endmacro () - - macro (detect_and_set_c_and_cxx_warning_flag flag_name) - string(TOUPPER ${flag_name} flag_name_upper) - string(REPLACE "-" "_" flag_name_upper "${flag_name_upper}") - string(REPLACE "=" "_" flag_name_upper "${flag_name_upper}") - check_c_compiler_flag(-W${flag_name} "HAVE_WFLAG_${flag_name_upper}") - if (HAVE_WFLAG_${flag_name_upper}) - set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -W${flag_name}) - set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -W${flag_name}) - endif () - endmacro () - - detect_and_set_c_and_cxx_warning_flag(all) - detect_and_set_c_and_cxx_warning_flag(extra) - detect_and_set_c_and_cxx_warning_flag(init-self) - detect_and_set_c_and_cxx_warning_flag(unused-parameter) - detect_and_set_c_warning_flag(missing-prototypes) - detect_and_set_c_and_cxx_warning_flag(missing-declarations) - detect_and_set_c_and_cxx_warning_flag(shorten-64-to-32) - detect_and_set_c_and_cxx_warning_flag(logical-op) - detect_and_set_c_and_cxx_warning_flag(shadow) - detect_and_set_cxx_warning_flag(shadow-field) # CLang only for now - detect_and_set_c_and_cxx_warning_flag(missing-include-dirs) - check_c_compiler_flag("-Wformat -Werror=format-security -Wno-format-nonliteral" HAVE_WFLAG_FORMAT_SECURITY) - if (HAVE_WFLAG_FORMAT_SECURITY) - set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -Wformat -Werror=format-security -Wno-format-nonliteral) - set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -Wformat -Werror=format-security -Wno-format-nonliteral) - else () - detect_and_set_c_and_cxx_warning_flag(format) - endif () - detect_and_set_c_and_cxx_warning_flag(error=vla) - detect_and_set_c_and_cxx_warning_flag(no-clobbered) - detect_and_set_c_and_cxx_warning_flag(date-time) - detect_and_set_c_and_cxx_warning_flag(null-dereference) - detect_and_set_c_and_cxx_warning_flag(duplicate-cond) - detect_and_set_cxx_warning_flag(extra-semi) - detect_and_set_c_and_cxx_warning_flag(comma) - detect_and_set_c_and_cxx_warning_flag(float-conversion) - check_c_compiler_flag("-Wdocumentation -Wno-documentation-deprecated-sync" HAVE_WFLAG_DOCUMENTATION_AND_NO_DEPRECATED) - if (HAVE_WFLAG_DOCUMENTATION_AND_NO_DEPRECATED) - set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -Wdocumentation -Wno-documentation-deprecated-sync) - set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -Wdocumentation -Wno-documentation-deprecated-sync) - endif () - detect_and_set_cxx_warning_flag(unused-private-field) - detect_and_set_cxx_warning_flag(non-virtual-dtor) - detect_and_set_cxx_warning_flag(overloaded-virtual) - detect_and_set_cxx_warning_flag(suggest-override) - - check_cxx_compiler_flag(-fno-operator-names HAVE_FLAG_NO_OPERATOR_NAMES) - if (HAVE_FLAG_NO_OPERATOR_NAMES) - set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -fno-operator-names) - endif () - - check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT) - if (HAVE_GCC_WARNING_ZERO_AS_NULL_POINTER_CONSTANT) - set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -Wzero-as-null-pointer-constant) - endif () - - # Detect -Wold-style-cast but do not add it by default, as not all targets support it - check_cxx_compiler_flag(-Wold-style-cast HAVE_WFLAG_OLD_STYLE_CAST) - if (HAVE_WFLAG_OLD_STYLE_CAST) - set(WFLAG_OLD_STYLE_CAST -Wold-style-cast) - endif () - - # Detect Weffc++ but do not add it by default, as not all targets support it - check_cxx_compiler_flag(-Weffc++ HAVE_WFLAG_EFFCXX) - if (HAVE_WFLAG_EFFCXX) - set(WFLAG_EFFCXX -Weffc++) - endif () - - if (CMAKE_BUILD_TYPE MATCHES Debug) - check_c_compiler_flag(-ftrapv HAVE_FTRAPV) - if (HAVE_FTRAPV) - set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -ftrapv) - set(GDAL_CXX_WARNING_FLAGS ${GDAL_CXX_WARNING_FLAGS} -ftrapv) - endif () - endif () - -endif () - -add_compile_definitions($<$:DEBUG>) - -# message(STATUS "GDAL_C_WARNING_FLAGS: ${GDAL_C_WARNING_FLAGS}") message(STATUS "GDAL_CXX_WARNING_FLAGS: ${GDAL_CXX_WARNING_FLAGS}") - -if (CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - check_cxx_compiler_flag(-fno-finite-math-only HAVE_FLAG_NO_FINITE_MATH_ONLY) - if (HAVE_FLAG_NO_FINITE_MATH_ONLY) - # Intel CXX compiler based on clang defaults to -ffinite-math-only, which breaks std::isinf(), std::isnan(), etc. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-finite-math-only") - endif () - - set(TEST_LINK_STDCPP_SOURCE_CODE - "#include - int main(){ - std::string s; - s += \"x\"; - return 0; - }") - check_cxx_source_compiles("${TEST_LINK_STDCPP_SOURCE_CODE}" _TEST_LINK_STDCPP) - if( NOT _TEST_LINK_STDCPP ) - message(WARNING "Cannot link code using standard C++ library. Automatically adding -lstdc++ to CMAKE_EXE_LINKER_FLAGS, CMAKE_SHARED_LINKER_FLAGS and CMAKE_MODULE_LINKER_FLAGS") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lstdc++") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -lstdc++") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -lstdc++") - - check_cxx_source_compiles("${TEST_LINK_STDCPP_SOURCE_CODE}" _TEST_LINK_STDCPP_AGAIN) - if( NOT _TEST_LINK_STDCPP_AGAIN ) - message(FATAL_ERROR "Cannot link C++ program") - endif() - endif() - - check_c_compiler_flag(-wd188 HAVE_WD188) # enumerated type mixed with another type - if( HAVE_WD188 ) - set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -wd188) - endif() - check_c_compiler_flag(-wd2259 HAVE_WD2259) # non-pointer conversion from ... may lose significant bits - if( HAVE_WD2259 ) - set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -wd2259) - endif() - check_c_compiler_flag(-wd2312 HAVE_WD2312) # pointer cast involving 64-bit pointed-to type - if( HAVE_WD2259 ) - set(GDAL_C_WARNING_FLAGS ${GDAL_C_WARNING_FLAGS} -wd2312) - endif() -endif () +include(GdalCompilationFlags) # ###################################################################################################################### # generate ${CMAKE_CURRENT_BINARY_DIR}/port/cpl_config.h @@ -325,9 +133,6 @@ if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" ST CACHE INTERNAL "Previous value of USE_ALTERNATE_LINKER") endif() -# Default definitions during build -add_definitions(-DGDAL_COMPILATION) - if (ENABLE_IPO) include(CheckIPOSupported) check_ipo_supported(RESULT result) @@ -384,20 +189,6 @@ if (MINGW AND BUILD_SHARED_LIBS) set_target_properties(${GDAL_LIB_TARGET_NAME} PROPERTIES SUFFIX "-${GDAL_SOVERSION}${CMAKE_SHARED_LIBRARY_SUFFIX}") endif () -if (MINGW) - if (TARGET_CPU MATCHES "x86_64") - add_definitions(-m64) - endif () - # Workaround for export too large error - force problematic large file to be optimized to prevent string table - # overflow error Used -Os instead of -O2 as previous issues had mentioned, since -Os is roughly speaking -O2, - # excluding any optimizations that take up extra space. Given that the issue is a string table overflowing, -Os seemed - # appropriate. Solves issue of https://github.com/OSGeo/gdal/issues/4706 with for example x86_64-w64-mingw32-gcc-posix - # (GCC) 9.3-posix 20200320 - if (CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE STREQUAL "") - add_compile_options(-Os) - endif () -endif () - # Install properties if (GDAL_ENABLE_MACOSX_FRAMEWORK) set(FRAMEWORK_VERSION ${GDAL_VERSION_MAJOR}.${GDAL_VERSION_MINOR}) From 9e3eba5637b8250baaf14810a53d57f0904a680d Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 18:21:46 +0200 Subject: [PATCH 0267/1119] GdalStandardIncludes.cmake: make it work in STANDALONE mode --- cmake/helpers/GdalStandardIncludes.cmake | 26 ++++++++++++++---------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/cmake/helpers/GdalStandardIncludes.cmake b/cmake/helpers/GdalStandardIncludes.cmake index e138d2175838..b4f98e6562f2 100644 --- a/cmake/helpers/GdalStandardIncludes.cmake +++ b/cmake/helpers/GdalStandardIncludes.cmake @@ -8,15 +8,19 @@ GdalStandardIncludes #]=======================================================================] function(gdal_standard_includes _TARGET) - target_include_directories(${_TARGET} PRIVATE - $ - $ - $ - $ - $ # port - $ - $ - $ # ogr/ogrsf_frmts - $ # frmts - ) + if (STANDALONE) + target_include_directories(${_TARGET} PRIVATE $) + else() + target_include_directories(${_TARGET} PRIVATE + $ + $ + $ + $ + $ # port + $ + $ + $ # ogr/ogrsf_frmts + $ # frmts + ) + endif() endfunction() From e9f80e745759c65c6ad492a5c2c8cf418ec92a7a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 18:21:57 +0200 Subject: [PATCH 0268/1119] GdalVersion.cmake: make it work in STANDALONE mode --- cmake/helpers/GdalVersion.cmake | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/cmake/helpers/GdalVersion.cmake b/cmake/helpers/GdalVersion.cmake index b466522da06d..7961085ba325 100644 --- a/cmake/helpers/GdalVersion.cmake +++ b/cmake/helpers/GdalVersion.cmake @@ -16,8 +16,10 @@ GdalVersion #]=======================================================================] +set(GDAL_ROOT_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..") + # parse the version number from gdal_version.h and include in GDAL_MAJOR_VERSION and GDAL_MINOR_VERSION -file(READ ${PROJECT_SOURCE_DIR}/gcore/gdal_version.h.in GDAL_VERSION_H_CONTENTS) +file(READ ${GDAL_ROOT_SOURCE_DIR}/gcore/gdal_version.h.in GDAL_VERSION_H_CONTENTS) string(REGEX MATCH "GDAL_VERSION_MAJOR[ \t]+([0-9]+)" GDAL_VERSION_MAJOR ${GDAL_VERSION_H_CONTENTS}) string(REGEX MATCH "([0-9]+)" @@ -35,12 +37,16 @@ string(REGEX MATCH "GDAL_VERSION_BUILD[ \t]+([0-9]+)" string(REGEX MATCH "([0-9]+)" GDAL_VERSION_BUILD ${GDAL_VERSION_BUILD}) -if ((EXISTS "${PROJECT_SOURCE_DIR}/gcore/gdal_version.h") AND NOT ("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}")) +if (STANDALONE) + return() +endif() + +if ((EXISTS "${GDAL_ROOT_SOURCE_DIR}/gcore/gdal_version.h") AND NOT ("${GDAL_ROOT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}")) # Try to detect issues when building with cmake out of source tree, but against a previous build done in source tree - message(FATAL_ERROR "${PROJECT_SOURCE_DIR}/gcore/gdal_version.h was found, and likely conflicts with ${PROJECT_BINARY_DIR}/gcore/gdal_version.h") + message(FATAL_ERROR "${GDAL_ROOT_SOURCE_DIR}/gcore/gdal_version.h was found, and likely conflicts with ${PROJECT_BINARY_DIR}/gcore/gdal_version.h") endif () -if (EXISTS ${PROJECT_SOURCE_DIR}/.git) +if (EXISTS ${GDAL_ROOT_SOURCE_DIR}/.git) set(GDAL_DEV_SUFFIX "dev") else() set(GDAL_DEV_SUFFIX "") @@ -52,11 +58,11 @@ set(GDAL_RELEASE_DATE "$ENV{GDAL_RELEASE_DATE}") add_custom_target(generate_gdal_version_h COMMAND ${CMAKE_COMMAND} - "-DSOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DSOURCE_DIR=${GDAL_ROOT_SOURCE_DIR}" "-DBINARY_DIR=${PROJECT_BINARY_DIR}" "-DGDAL_SHA1SUM=${GDAL_SHA1SUM}" "-DGDAL_RELEASE_DATE=${GDAL_RELEASE_DATE}" - -P "${PROJECT_SOURCE_DIR}/cmake/helpers/generate_gdal_version_h.cmake" + -P "${GDAL_ROOT_SOURCE_DIR}/cmake/helpers/generate_gdal_version_h.cmake" VERBATIM) if (WIN32 AND NOT MINGW) From a3db07e69f0155bed5e3e57ba1b3c568f69299f9 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 18:22:32 +0200 Subject: [PATCH 0269/1119] SystemSummary.cmake: report C++17 related flags rather than C++11 ones --- cmake/modules/thirdparty/SystemSummary.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/thirdparty/SystemSummary.cmake b/cmake/modules/thirdparty/SystemSummary.cmake index 5251c4840a26..54164304fd09 100644 --- a/cmake/modules/thirdparty/SystemSummary.cmake +++ b/cmake/modules/thirdparty/SystemSummary.cmake @@ -23,8 +23,8 @@ macro(gather_flags with_linker result) # add the main flags without a config list(APPEND ${result} CMAKE_C_FLAGS) list(APPEND ${result} CMAKE_CXX_FLAGS) - list(APPEND ${result} CMAKE_CXX11_STANDARD_COMPILE_OPTION) - list(APPEND ${result} CMAKE_CXX11_EXTENSION_COMPILE_OPTION) + list(APPEND ${result} CMAKE_CXX17_STANDARD_COMPILE_OPTION) + list(APPEND ${result} CMAKE_CXX17_EXTENSION_COMPILE_OPTION) if(${with_linker}) list(APPEND ${result} CMAKE_EXE_LINKER_FLAGS) From 76ec763fc832eff0b8e03371707a78d9452e4adc Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 18:24:18 +0200 Subject: [PATCH 0270/1119] CMake: move code from CheckDependentLibraries.cmake to CheckDependentLibrariesCommon.cmake --- cmake/helpers/CheckDependentLibraries.cmake | 271 +---------------- .../CheckDependentLibrariesCommon.cmake | 281 ++++++++++++++++++ 2 files changed, 283 insertions(+), 269 deletions(-) create mode 100644 cmake/helpers/CheckDependentLibrariesCommon.cmake diff --git a/cmake/helpers/CheckDependentLibraries.cmake b/cmake/helpers/CheckDependentLibraries.cmake index 7335af0e9751..0dddba9da97c 100644 --- a/cmake/helpers/CheckDependentLibraries.cmake +++ b/cmake/helpers/CheckDependentLibraries.cmake @@ -8,275 +8,7 @@ Detect GDAL dependencies and set variable HAVE_* #]=======================================================================] -include(CheckFunctionExists) -include(CMakeDependentOption) -include(FeatureSummary) -include(DefineFindPackage2) -include(CheckSymbolExists) - -option( - GDAL_USE_EXTERNAL_LIBS - "Whether detected external libraries should be used by default. This should be set before CMakeCache.txt is created." - ON) - -set(GDAL_USE_INTERNAL_LIBS_ALLOWED_VALUES ON OFF WHEN_NO_EXTERNAL) -set( - GDAL_USE_INTERNAL_LIBS WHEN_NO_EXTERNAL - CACHE STRING "Control how internal libraries should be used by default. This should be set before CMakeCache.txt is created.") -set_property(CACHE GDAL_USE_INTERNAL_LIBS PROPERTY STRINGS ${GDAL_USE_INTERNAL_LIBS_ALLOWED_VALUES}) -if(NOT GDAL_USE_INTERNAL_LIBS IN_LIST GDAL_USE_INTERNAL_LIBS_ALLOWED_VALUES) - message(FATAL_ERROR "GDAL_USE_INTERNAL_LIBS must be one of ${GDAL_USE_INTERNAL_LIBS_ALLOWED_VALUES}") -endif() - -set(GDAL_IMPORT_DEPENDENCIES [[ -include(CMakeFindDependencyMacro) -include("${CMAKE_CURRENT_LIST_DIR}/DefineFindPackage2.cmake") -include("${CMAKE_CURRENT_LIST_DIR}/GdalFindModulePath.cmake") -]]) -if(TARGET Threads::Threads) - string(APPEND GDAL_IMPORT_DEPENDENCIES "find_dependency(Threads)\n") -endif() - -# Check that the configuration has a valid value for INTERFACE_INCLUDE_DIRECTORIES. This aimed at avoiding issues like -# https://github.com/OSGeo/gdal/issues/5324 -function (gdal_check_target_is_valid target res_var) - get_target_property(_interface_include_directories ${target} "INTERFACE_INCLUDE_DIRECTORIES") - if(_interface_include_directories) - foreach(_dir IN LISTS _interface_include_directories) - if(NOT EXISTS "${_dir}") - message(WARNING "Target ${target} references ${_dir} as a INTERFACE_INCLUDE_DIRECTORIES, but it does not exist. Ignoring that target.") - set(${res_var} FALSE PARENT_SCOPE) - return() - endif() - endforeach() - elseif("${target}" STREQUAL "geotiff_library" AND DEFINED GeoTIFF_INCLUDE_DIRS) - # geotiff-config.cmake of GeoTIFF 1.7.0 doesn't define a INTERFACE_INCLUDE_DIRECTORIES - # property, but a GeoTIFF_INCLUDE_DIRS variable. - set_target_properties(${target} PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${GeoTIFF_INCLUDE_DIRS}") - else() - message(WARNING "Target ${target} has no INTERFACE_INCLUDE_DIRECTORIES property. Ignoring that target.") - set(${res_var} FALSE PARENT_SCOPE) - return() - endif() - set(${res_var} TRUE PARENT_SCOPE) -endfunction() - -# Package acceptance based on a candidate target list. -# If a matching target is found, sets ${name}_FOUND to TRUE, -# ${name}_INCLUDE_DIRS to "" and ${name}_LIBRARIES to the target name. -# If `REQUIRED` is used, ${name}_FOUND is set to FALSE if no target matches. -function(gdal_check_package_target name) - if("REQUIRED" IN_LIST ARGN) - list(REMOVE_ITEM ARGN "REQUIRED") - set(${name}_FOUND FALSE PARENT_SCOPE) - endif() - foreach(target IN LISTS ARGN) - if(TARGET ${target}) - gdal_check_target_is_valid(${target} _is_valid) - if (_is_valid) - set(${name}_TARGET "${target}" PARENT_SCOPE) - set(${name}_FOUND TRUE PARENT_SCOPE) - return() - endif() - endif() - endforeach() -endfunction() - -# Macro to declare a dependency on an external package. -# If not marked with the ALWAYS_ON_WHEN_FOUND option, dependencies can be -# marked for user control with either the CAN_DISABLE or DISABLED_BY_DEFAULT -# option. User control is done via a cache variable GDAL_USE_{name in upper case} -# with the default value ON for CAN_DISABLE or OFF for DISABLED_BY_DEFAULT. -# The RECOMMENDED option is used for the feature summary. -# The VERSION, CONFIG, MODULE, COMPONENTS and NAMES parameters are passed to find_package(). -# Using NAMES with find_package() implies config mode. However, gdal_check_package() -# attempts another find_package() without NAMES if the config mode attempt was not -# successful, allowing a fallback to Find modules. -# The TARGETS parameter can define a list of candidate targets. If given, a -# package will only be accepted if it defines one of the given targets. The matching -# target name will be saved in ${name}_TARGET. -# The NAMES and TARGETS map to GDAL_CHECK_PACKAGE_${name}_NAMES and -# GDAL_CHECK_PACKAGE_${name}_TARGETS cache variables which can be used to -# overwrite the default config and targets names. -# The required find_dependency() commands for exported config are appended to -# the GDAL_IMPORT_DEPENDENCIES string (when BUILD_SHARED_LIBS=OFF). -macro (gdal_check_package name purpose) - set(_options CONFIG MODULE CAN_DISABLE RECOMMENDED DISABLED_BY_DEFAULT ALWAYS_ON_WHEN_FOUND) - set(_oneValueArgs VERSION NAMES) - set(_multiValueArgs COMPONENTS TARGETS PATHS) - cmake_parse_arguments(_GCP "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - string(TOUPPER ${name} key) - set(_find_dependency "") - set(_find_dependency_args "") - if(FIND_PACKAGE2_${name}_ENABLED) - find_package2(${name} QUIET OUT_DEPENDENCY _find_dependency) - else() - set(_find_package_args) - # For some reason passing the HDF5 version requirement cause a linking error of the libkea driver on Conda Windows builds... - if (_GCP_VERSION AND NOT ("${name}" STREQUAL "TileDB") AND NOT ("${name}" STREQUAL "HDF5")) - list(APPEND _find_package_args ${_GCP_VERSION}) - endif () - if (_GCP_CONFIG) - list(APPEND _find_package_args CONFIG) - endif () - if (_GCP_MODULE) - list(APPEND _find_package_args MODULE) - endif () - if (_GCP_COMPONENTS) - list(APPEND _find_package_args COMPONENTS ${_GCP_COMPONENTS}) - endif () - if (_GCP_PATHS) - list(APPEND _find_package_args PATHS ${_GCP_PATHS}) - endif () - if (_GCP_NAMES) - set(GDAL_CHECK_PACKAGE_${name}_NAMES "${_GCP_NAMES}" CACHE STRING "Config file name for ${name}") - mark_as_advanced(GDAL_CHECK_PACKAGE_${name}_NAMES) - endif () - if (_GCP_TARGETS) - set(GDAL_CHECK_PACKAGE_${name}_TARGETS "${_GCP_TARGETS}" CACHE STRING "Target name candidates for ${name}") - mark_as_advanced(GDAL_CHECK_PACKAGE_${name}_TARGETS) - endif () - if (GDAL_CHECK_PACKAGE_${name}_NAMES) - find_package(${name} NAMES ${GDAL_CHECK_PACKAGE_${name}_NAMES} ${_find_package_args}) - gdal_check_package_target(${name} ${GDAL_CHECK_PACKAGE_${name}_TARGETS} REQUIRED) - if (${name}_FOUND) - get_filename_component(_find_dependency_args "${${name}_CONFIG}" NAME) - string(REPLACE ";" " " _find_dependency_args "${name} ${_find_package_args} NAMES ${GDAL_CHECK_PACKAGE_${name}_NAMES} CONFIGS ${_find_dependency_args}") - endif () - endif () - if (NOT ${name}_FOUND) - find_package(${name} ${_find_package_args}) - if (${name}_FOUND) - gdal_check_package_target(${name} ${GDAL_CHECK_PACKAGE_${name}_TARGETS}) - elseif (${key}_FOUND) # Some find modules do not set _FOUND - gdal_check_package_target(${key} ${GDAL_CHECK_PACKAGE_${name}_TARGETS}) - set(${name}_FOUND "${key}_FOUND") - endif () - if (${name}_FOUND) - string(REPLACE ";" " " _find_dependency_args "${name} ${_find_package_args}") - endif() - endif () - endif () - if (${key}_FOUND OR ${name}_FOUND) - if(_GCP_VERSION) - - if( "${name}" STREQUAL "TileDB" AND NOT DEFINED TileDB_VERSION) - get_property(_dirs TARGET TileDB::tiledb_shared PROPERTY INTERFACE_INCLUDE_DIRECTORIES) - foreach(_dir IN LISTS _dirs) - set(TILEDB_VERSION_FILENAME "${_dir}/tiledb/tiledb_version.h") - if(EXISTS ${TILEDB_VERSION_FILENAME}) - file(READ ${TILEDB_VERSION_FILENAME} _tiledb_version_contents) - string(REGEX REPLACE "^.*TILEDB_VERSION_MAJOR +([0-9]+).*$" "\\1" TILEDB_VERSION_MAJOR "${_tiledb_version_contents}") - string(REGEX REPLACE "^.*TILEDB_VERSION_MINOR +([0-9]+).*$" "\\1" TILEDB_VERSION_MINOR "${_tiledb_version_contents}") - set(TileDB_VERSION "${TILEDB_VERSION_MAJOR}.${TILEDB_VERSION_MINOR}") - endif() - endforeach() - endif() - - if (DEFINED ${name}_VERSION_STRING AND NOT DEFINED ${name}_VERSION) - set(${name}_VERSION "${${name}_VERSION_STRING}") - endif() - - if( "${${name}_VERSION}" STREQUAL "") - message(WARNING "${name} has unknown version. Assuming it is at least matching the minimum version required of ${_GCP_VERSION}") - set(HAVE_${key} ON) - elseif( ${name}_VERSION VERSION_LESS ${_GCP_VERSION}) - message(WARNING "Ignoring ${name} because it is at version ${${name}_VERSION}, whereas the minimum version required is ${_GCP_VERSION}") - set(HAVE_${key} OFF) - else() - set(HAVE_${key} ON) - endif() - else() - set(HAVE_${key} ON) - endif() - else () - set(HAVE_${key} OFF) - endif () - if (purpose STREQUAL "") - - else () - if (_GCP_RECOMMENDED) - set_package_properties( - ${name} PROPERTIES - PURPOSE ${purpose} - TYPE RECOMMENDED) - else () - set_package_properties(${name} PROPERTIES PURPOSE ${purpose}) - endif () - endif () - - if (_GCP_CAN_DISABLE OR _GCP_DISABLED_BY_DEFAULT) - set(_gcpp_status ON) - if (GDAL_USE_${key}) - if (NOT HAVE_${key}) - message(FATAL_ERROR "Configured to use ${key}, but not found") - endif () - elseif (NOT GDAL_USE_EXTERNAL_LIBS) - set(_gcpp_status OFF) - if (HAVE_${key} AND NOT GDAL_USE_${key}) - message(STATUS - "${key} has been found, but is disabled due to GDAL_USE_EXTERNAL_LIBS=OFF. Enable it by setting GDAL_USE_${key}=ON" - ) - set(_find_dependency_args "") - endif () - endif () - if (_gcpp_status AND _GCP_DISABLED_BY_DEFAULT) - set(_gcpp_status OFF) - if (HAVE_${key} AND NOT GDAL_USE_${key}) - message(STATUS "${key} has been found, but is disabled by default. Enable it by setting GDAL_USE_${key}=ON") - set(_find_dependency_args "") - endif () - endif () - cmake_dependent_option(GDAL_USE_${key} "Set ON to use ${key}" ${_gcpp_status} "HAVE_${key}" OFF) - elseif (NOT _GCP_ALWAYS_ON_WHEN_FOUND) - message(FATAL_ERROR "Programming error: missing CAN_DISABLE or DISABLED_BY_DEFAULT option for component ${name}") - endif () - - if(_find_dependency_args) - string(REPLACE "\"" "\\\"" _find_dependency_args "${_find_dependency_args}") - set(_find_dependency "find_dependency(${_find_dependency_args})\n") - endif() - if(NOT BUILD_SHARED_LIBS AND GDAL_USE_${key} AND _find_dependency) - string(APPEND GDAL_IMPORT_DEPENDENCIES "${_find_dependency}") - endif() - unset(_find_dependency_args) - unset(_find_dependency) -endmacro () - -function (split_libpath _lib) - if (_lib) - # split lib_line into -L and -l linker options - get_filename_component(_path ${${_lib}} PATH) - get_filename_component(_name ${${_lib}} NAME_WE) - string(REGEX REPLACE "^lib" "" _name ${_name}) - set(${_lib} -L${_path} -l${_name}) - endif () -endfunction () - -function (gdal_internal_library libname) - set(_options REQUIRED) - set(_oneValueArgs) - set(_multiValueArgs) - cmake_parse_arguments(_GIL "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) - if ("${GDAL_USE_INTERNAL_LIBS}" STREQUAL "ON") - set(_default_value ON) - elseif ("${GDAL_USE_INTERNAL_LIBS}" STREQUAL "OFF") - set(_default_value OFF) - elseif( GDAL_USE_${libname} ) - set(_default_value OFF) - else() - set(_default_value ON) - endif() - set(GDAL_USE_${libname}_INTERNAL - ${_default_value} - CACHE BOOL "Use internal ${libname} copy (if set to ON, has precedence over GDAL_USE_${libname})") - if (_GIL_REQUIRED - AND (NOT GDAL_USE_${libname}) - AND (NOT GDAL_USE_${libname}_INTERNAL)) - message(FATAL_ERROR "GDAL_USE_${libname} or GDAL_USE_${libname}_INTERNAL must be set to ON") - endif () -endfunction () +include(CheckDependentLibrariesCommon) # Custom find_package definitions @@ -313,6 +45,7 @@ if (Iconv_FOUND) size_t ret = iconv(conv, &in, &ilen, &out, &olen); return (size_t)ret; }") + include(CheckCXXSourceCompiles) check_cxx_source_compiles("${ICONV_CONST_TEST_CODE}" _ICONV_SECOND_ARGUMENT_IS_NOT_CONST) if (_ICONV_SECOND_ARGUMENT_IS_NOT_CONST) set(ICONV_CPP_CONST "") diff --git a/cmake/helpers/CheckDependentLibrariesCommon.cmake b/cmake/helpers/CheckDependentLibrariesCommon.cmake new file mode 100644 index 000000000000..ebb353829fcb --- /dev/null +++ b/cmake/helpers/CheckDependentLibrariesCommon.cmake @@ -0,0 +1,281 @@ +# Distributed under the GDAL/OGR MIT style License. See accompanying file LICENSE.TXT. + +#[=======================================================================[.rst: +CheckDependentLibraries.cmake +----------------------------- + +Detect GDAL dependencies and set variable HAVE_* + +#]=======================================================================] + +include(CheckFunctionExists) +include(CMakeDependentOption) +include(FeatureSummary) +include(DefineFindPackage2) +include(CheckSymbolExists) + +option( + GDAL_USE_EXTERNAL_LIBS + "Whether detected external libraries should be used by default. This should be set before CMakeCache.txt is created." + ON) + +set(GDAL_USE_INTERNAL_LIBS_ALLOWED_VALUES ON OFF WHEN_NO_EXTERNAL) +set( + GDAL_USE_INTERNAL_LIBS WHEN_NO_EXTERNAL + CACHE STRING "Control how internal libraries should be used by default. This should be set before CMakeCache.txt is created.") +set_property(CACHE GDAL_USE_INTERNAL_LIBS PROPERTY STRINGS ${GDAL_USE_INTERNAL_LIBS_ALLOWED_VALUES}) +if(NOT GDAL_USE_INTERNAL_LIBS IN_LIST GDAL_USE_INTERNAL_LIBS_ALLOWED_VALUES) + message(FATAL_ERROR "GDAL_USE_INTERNAL_LIBS must be one of ${GDAL_USE_INTERNAL_LIBS_ALLOWED_VALUES}") +endif() + +set(GDAL_IMPORT_DEPENDENCIES [[ +include(CMakeFindDependencyMacro) +include("${CMAKE_CURRENT_LIST_DIR}/DefineFindPackage2.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/GdalFindModulePath.cmake") +]]) +if(TARGET Threads::Threads) + string(APPEND GDAL_IMPORT_DEPENDENCIES "find_dependency(Threads)\n") +endif() + +# Check that the configuration has a valid value for INTERFACE_INCLUDE_DIRECTORIES. This aimed at avoiding issues like +# https://github.com/OSGeo/gdal/issues/5324 +function (gdal_check_target_is_valid target res_var) + get_target_property(_interface_include_directories ${target} "INTERFACE_INCLUDE_DIRECTORIES") + if(_interface_include_directories) + foreach(_dir IN LISTS _interface_include_directories) + if(NOT EXISTS "${_dir}") + message(WARNING "Target ${target} references ${_dir} as a INTERFACE_INCLUDE_DIRECTORIES, but it does not exist. Ignoring that target.") + set(${res_var} FALSE PARENT_SCOPE) + return() + endif() + endforeach() + elseif("${target}" STREQUAL "geotiff_library" AND DEFINED GeoTIFF_INCLUDE_DIRS) + # geotiff-config.cmake of GeoTIFF 1.7.0 doesn't define a INTERFACE_INCLUDE_DIRECTORIES + # property, but a GeoTIFF_INCLUDE_DIRS variable. + set_target_properties(${target} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GeoTIFF_INCLUDE_DIRS}") + else() + message(WARNING "Target ${target} has no INTERFACE_INCLUDE_DIRECTORIES property. Ignoring that target.") + set(${res_var} FALSE PARENT_SCOPE) + return() + endif() + set(${res_var} TRUE PARENT_SCOPE) +endfunction() + +# Package acceptance based on a candidate target list. +# If a matching target is found, sets ${name}_FOUND to TRUE, +# ${name}_INCLUDE_DIRS to "" and ${name}_LIBRARIES to the target name. +# If `REQUIRED` is used, ${name}_FOUND is set to FALSE if no target matches. +function(gdal_check_package_target name) + if("REQUIRED" IN_LIST ARGN) + list(REMOVE_ITEM ARGN "REQUIRED") + set(${name}_FOUND FALSE PARENT_SCOPE) + endif() + foreach(target IN LISTS ARGN) + if(TARGET ${target}) + gdal_check_target_is_valid(${target} _is_valid) + if (_is_valid) + set(${name}_TARGET "${target}" PARENT_SCOPE) + set(${name}_FOUND TRUE PARENT_SCOPE) + return() + endif() + endif() + endforeach() +endfunction() + +# Macro to declare a dependency on an external package. +# If not marked with the ALWAYS_ON_WHEN_FOUND option, dependencies can be +# marked for user control with either the CAN_DISABLE or DISABLED_BY_DEFAULT +# option. User control is done via a cache variable GDAL_USE_{name in upper case} +# with the default value ON for CAN_DISABLE or OFF for DISABLED_BY_DEFAULT. +# The RECOMMENDED option is used for the feature summary. +# The VERSION, CONFIG, MODULE, COMPONENTS and NAMES parameters are passed to find_package(). +# Using NAMES with find_package() implies config mode. However, gdal_check_package() +# attempts another find_package() without NAMES if the config mode attempt was not +# successful, allowing a fallback to Find modules. +# The TARGETS parameter can define a list of candidate targets. If given, a +# package will only be accepted if it defines one of the given targets. The matching +# target name will be saved in ${name}_TARGET. +# The NAMES and TARGETS map to GDAL_CHECK_PACKAGE_${name}_NAMES and +# GDAL_CHECK_PACKAGE_${name}_TARGETS cache variables which can be used to +# overwrite the default config and targets names. +# The required find_dependency() commands for exported config are appended to +# the GDAL_IMPORT_DEPENDENCIES string (when BUILD_SHARED_LIBS=OFF). +macro (gdal_check_package name purpose) + set(_options CONFIG MODULE CAN_DISABLE RECOMMENDED DISABLED_BY_DEFAULT ALWAYS_ON_WHEN_FOUND) + set(_oneValueArgs VERSION NAMES) + set(_multiValueArgs COMPONENTS TARGETS PATHS) + cmake_parse_arguments(_GCP "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + string(TOUPPER ${name} key) + set(_find_dependency "") + set(_find_dependency_args "") + if(FIND_PACKAGE2_${name}_ENABLED) + find_package2(${name} QUIET OUT_DEPENDENCY _find_dependency) + else() + set(_find_package_args) + # For some reason passing the HDF5 version requirement cause a linking error of the libkea driver on Conda Windows builds... + if (_GCP_VERSION AND NOT ("${name}" STREQUAL "TileDB") AND NOT ("${name}" STREQUAL "HDF5")) + list(APPEND _find_package_args ${_GCP_VERSION}) + endif () + if (_GCP_CONFIG) + list(APPEND _find_package_args CONFIG) + endif () + if (_GCP_MODULE) + list(APPEND _find_package_args MODULE) + endif () + if (_GCP_COMPONENTS) + list(APPEND _find_package_args COMPONENTS ${_GCP_COMPONENTS}) + endif () + if (_GCP_PATHS) + list(APPEND _find_package_args PATHS ${_GCP_PATHS}) + endif () + if (_GCP_NAMES) + set(GDAL_CHECK_PACKAGE_${name}_NAMES "${_GCP_NAMES}" CACHE STRING "Config file name for ${name}") + mark_as_advanced(GDAL_CHECK_PACKAGE_${name}_NAMES) + endif () + if (_GCP_TARGETS) + set(GDAL_CHECK_PACKAGE_${name}_TARGETS "${_GCP_TARGETS}" CACHE STRING "Target name candidates for ${name}") + mark_as_advanced(GDAL_CHECK_PACKAGE_${name}_TARGETS) + endif () + if (GDAL_CHECK_PACKAGE_${name}_NAMES) + find_package(${name} NAMES ${GDAL_CHECK_PACKAGE_${name}_NAMES} ${_find_package_args}) + gdal_check_package_target(${name} ${GDAL_CHECK_PACKAGE_${name}_TARGETS} REQUIRED) + if (${name}_FOUND) + get_filename_component(_find_dependency_args "${${name}_CONFIG}" NAME) + string(REPLACE ";" " " _find_dependency_args "${name} ${_find_package_args} NAMES ${GDAL_CHECK_PACKAGE_${name}_NAMES} CONFIGS ${_find_dependency_args}") + endif () + endif () + if (NOT ${name}_FOUND) + find_package(${name} ${_find_package_args}) + if (${name}_FOUND) + gdal_check_package_target(${name} ${GDAL_CHECK_PACKAGE_${name}_TARGETS}) + elseif (${key}_FOUND) # Some find modules do not set _FOUND + gdal_check_package_target(${key} ${GDAL_CHECK_PACKAGE_${name}_TARGETS}) + set(${name}_FOUND "${key}_FOUND") + endif () + if (${name}_FOUND) + string(REPLACE ";" " " _find_dependency_args "${name} ${_find_package_args}") + endif() + endif () + endif () + if (${key}_FOUND OR ${name}_FOUND) + if(_GCP_VERSION) + + if( "${name}" STREQUAL "TileDB" AND NOT DEFINED TileDB_VERSION) + get_property(_dirs TARGET TileDB::tiledb_shared PROPERTY INTERFACE_INCLUDE_DIRECTORIES) + foreach(_dir IN LISTS _dirs) + set(TILEDB_VERSION_FILENAME "${_dir}/tiledb/tiledb_version.h") + if(EXISTS ${TILEDB_VERSION_FILENAME}) + file(READ ${TILEDB_VERSION_FILENAME} _tiledb_version_contents) + string(REGEX REPLACE "^.*TILEDB_VERSION_MAJOR +([0-9]+).*$" "\\1" TILEDB_VERSION_MAJOR "${_tiledb_version_contents}") + string(REGEX REPLACE "^.*TILEDB_VERSION_MINOR +([0-9]+).*$" "\\1" TILEDB_VERSION_MINOR "${_tiledb_version_contents}") + set(TileDB_VERSION "${TILEDB_VERSION_MAJOR}.${TILEDB_VERSION_MINOR}") + endif() + endforeach() + endif() + + if (DEFINED ${name}_VERSION_STRING AND NOT DEFINED ${name}_VERSION) + set(${name}_VERSION "${${name}_VERSION_STRING}") + endif() + + if( "${${name}_VERSION}" STREQUAL "") + message(WARNING "${name} has unknown version. Assuming it is at least matching the minimum version required of ${_GCP_VERSION}") + set(HAVE_${key} ON) + elseif( ${name}_VERSION VERSION_LESS ${_GCP_VERSION}) + message(WARNING "Ignoring ${name} because it is at version ${${name}_VERSION}, whereas the minimum version required is ${_GCP_VERSION}") + set(HAVE_${key} OFF) + else() + set(HAVE_${key} ON) + endif() + else() + set(HAVE_${key} ON) + endif() + else () + set(HAVE_${key} OFF) + endif () + if (purpose STREQUAL "") + + else () + if (_GCP_RECOMMENDED) + set_package_properties( + ${name} PROPERTIES + PURPOSE ${purpose} + TYPE RECOMMENDED) + else () + set_package_properties(${name} PROPERTIES PURPOSE ${purpose}) + endif () + endif () + + if (_GCP_CAN_DISABLE OR _GCP_DISABLED_BY_DEFAULT) + set(_gcpp_status ON) + if (GDAL_USE_${key}) + if (NOT HAVE_${key}) + message(FATAL_ERROR "Configured to use ${key}, but not found") + endif () + elseif (NOT GDAL_USE_EXTERNAL_LIBS) + set(_gcpp_status OFF) + if (HAVE_${key} AND NOT GDAL_USE_${key}) + message(STATUS + "${key} has been found, but is disabled due to GDAL_USE_EXTERNAL_LIBS=OFF. Enable it by setting GDAL_USE_${key}=ON" + ) + set(_find_dependency_args "") + endif () + endif () + if (_gcpp_status AND _GCP_DISABLED_BY_DEFAULT) + set(_gcpp_status OFF) + if (HAVE_${key} AND NOT GDAL_USE_${key}) + message(STATUS "${key} has been found, but is disabled by default. Enable it by setting GDAL_USE_${key}=ON") + set(_find_dependency_args "") + endif () + endif () + cmake_dependent_option(GDAL_USE_${key} "Set ON to use ${key}" ${_gcpp_status} "HAVE_${key}" OFF) + elseif (NOT _GCP_ALWAYS_ON_WHEN_FOUND) + message(FATAL_ERROR "Programming error: missing CAN_DISABLE or DISABLED_BY_DEFAULT option for component ${name}") + endif () + + if(_find_dependency_args) + string(REPLACE "\"" "\\\"" _find_dependency_args "${_find_dependency_args}") + set(_find_dependency "find_dependency(${_find_dependency_args})\n") + endif() + if(NOT BUILD_SHARED_LIBS AND GDAL_USE_${key} AND _find_dependency) + string(APPEND GDAL_IMPORT_DEPENDENCIES "${_find_dependency}") + endif() + unset(_find_dependency_args) + unset(_find_dependency) +endmacro () + +function (split_libpath _lib) + if (_lib) + # split lib_line into -L and -l linker options + get_filename_component(_path ${${_lib}} PATH) + get_filename_component(_name ${${_lib}} NAME_WE) + string(REGEX REPLACE "^lib" "" _name ${_name}) + set(${_lib} -L${_path} -l${_name}) + endif () +endfunction () + +function (gdal_internal_library libname) + set(_options REQUIRED) + set(_oneValueArgs) + set(_multiValueArgs) + cmake_parse_arguments(_GIL "${_options}" "${_oneValueArgs}" "${_multiValueArgs}" ${ARGN}) + if ("${GDAL_USE_INTERNAL_LIBS}" STREQUAL "ON") + set(_default_value ON) + elseif ("${GDAL_USE_INTERNAL_LIBS}" STREQUAL "OFF") + set(_default_value OFF) + elseif( GDAL_USE_${libname} ) + set(_default_value OFF) + else() + set(_default_value ON) + endif() + set(GDAL_USE_${libname}_INTERNAL + ${_default_value} + CACHE BOOL "Use internal ${libname} copy (if set to ON, has precedence over GDAL_USE_${libname})") + if (_GIL_REQUIRED + AND (NOT GDAL_USE_${libname}) + AND (NOT GDAL_USE_${libname}_INTERNAL)) + message(FATAL_ERROR "GDAL_USE_${libname} or GDAL_USE_${libname}_INTERNAL must be set to ON") + endif () +endfunction () + +# vim: ts=4 sw=4 sts=4 et From e755508bea4708667588fde365a5e90c3461ad37 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 18:25:06 +0200 Subject: [PATCH 0271/1119] GdalDriverHelper.cmake: add infrastructure for STANDALONE mode --- cmake/helpers/GdalDriverHelper.cmake | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/cmake/helpers/GdalDriverHelper.cmake b/cmake/helpers/GdalDriverHelper.cmake index b352ae1ca08b..ad07cb3d407c 100644 --- a/cmake/helpers/GdalDriverHelper.cmake +++ b/cmake/helpers/GdalDriverHelper.cmake @@ -155,7 +155,11 @@ function(add_gdal_driver) set(_COND ${_DRIVER_PLUGIN_CAPABLE_IF}) endif() - get_target_property(PLUGIN_OUTPUT_DIR ${GDAL_LIB_TARGET_NAME} PLUGIN_OUTPUT_DIR) + if(STANDALONE) + set(PLUGIN_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") + else() + get_target_property(PLUGIN_OUTPUT_DIR ${GDAL_LIB_TARGET_NAME} PLUGIN_OUTPUT_DIR) + endif() if (_DRIVER_PLUGIN_CAPABLE OR _DRIVER_PLUGIN_CAPABLE_IF) set(_INITIAL_VALUE OFF) @@ -293,7 +297,9 @@ function(add_gdal_driver) else() message(FATAL_ERROR "Driver ${_DRIVER_TARGET} should declare DRIVER_NO_SHARED_SYMBOL_WITH_CORE") endif() - _set_driver_core_sources(${_KEY} ${_DRIVER_TARGET} ${_DRIVER_CORE_SOURCES}) + if(NOT STANDALONE) + _set_driver_core_sources(${_KEY} ${_DRIVER_TARGET} ${_DRIVER_CORE_SOURCES}) + endif() endif () else () @@ -325,7 +331,9 @@ function(add_gdal_driver) target_compile_options(${_DRIVER_TARGET} PRIVATE $<$:${GDAL_CXX_WARNING_FLAGS}>) endif() target_compile_options(${_DRIVER_TARGET} PRIVATE $<$:${GDAL_C_WARNING_FLAGS}>) - add_dependencies(${_DRIVER_TARGET} generate_gdal_version_h) + if (NOT STANDALONE) + add_dependencies(${_DRIVER_TARGET} generate_gdal_version_h) + endif() endfunction() # Detect whether driver is built as PLUGIN or not. @@ -483,7 +491,7 @@ macro(gdal_dependent_format format desc depends) cmake_dependent_option(GDAL_ENABLE_DRIVER_${key} "Set ON to build ${desc} format" ${GDAL_BUILD_OPTIONAL_DRIVERS} "${depends}" OFF) add_feature_info(gdal_${key} GDAL_ENABLE_DRIVER_${key} "${desc}") - if ((GDAL_ENABLE_DRIVER_${key} AND NOT _GDF_SKIP_ADD_SUBDIRECTORY) OR GDAL_REGISTER_DRIVER_${key}_FOR_LATER_PLUGIN) + if (NOT STANDALONE AND (GDAL_ENABLE_DRIVER_${key} AND NOT _GDF_SKIP_ADD_SUBDIRECTORY) OR GDAL_REGISTER_DRIVER_${key}_FOR_LATER_PLUGIN) add_subdirectory(${format}) endif () endmacro() @@ -525,7 +533,7 @@ macro(ogr_dependent_driver name desc depend) "${depend}" OFF) endif() add_feature_info(ogr_${key} OGR_ENABLE_DRIVER_${key} "${desc}") - if (OGR_ENABLE_DRIVER_${key} OR OGR_REGISTER_DRIVER_${key}_FOR_LATER_PLUGIN) + if (NOT STANDALONE AND OGR_ENABLE_DRIVER_${key} OR OGR_REGISTER_DRIVER_${key}_FOR_LATER_PLUGIN) add_subdirectory(${name}) endif () endmacro() From 8f46cf0fceca26b4a02243742ef1f2213984e307 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 18:25:35 +0200 Subject: [PATCH 0272/1119] CMake: add SetupStandalonePlugin.cmake --- cmake/helpers/SetupStandalonePlugin.cmake | 57 +++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 cmake/helpers/SetupStandalonePlugin.cmake diff --git a/cmake/helpers/SetupStandalonePlugin.cmake b/cmake/helpers/SetupStandalonePlugin.cmake new file mode 100644 index 000000000000..53fee7fdcd72 --- /dev/null +++ b/cmake/helpers/SetupStandalonePlugin.cmake @@ -0,0 +1,57 @@ +# Distributed under the GDAL/OGR MIT License. See accompanying file LICENSE.TXT. +# This file is included by drivers that want to be built as plugin against an +# installed GDAL library (and thus not requiring to build libgdal itself) + +include("${CMAKE_CURRENT_LIST_DIR}/../../cmake/modules/init.cmake") + +# Hint used to alter the behavior of a number of .cmake files +set(STANDALONE ON) + +# Detect installed GDAL +find_package(GDAL REQUIRED) +set(GDAL_VERSION_IMPORTED ${GDAL_VERSION}) +set(GDAL_LIB_TARGET_NAME GDAL::GDAL) + +# Check that we build the plugin against a GDAL version that matches the one +# of the sources +include(GdalVersion) +set(GDAL_VERSION_MAJOR_SOURCE ${GDAL_VERSION_MAJOR}) +set(GDAL_VERSION_MINOR_SOURCE ${GDAL_VERSION_MINOR}) +set(GDAL_VERSION_REV_SOURCE ${GDAL_VERSION_REV}) +if(NOT "${GDAL_VERSION_IMPORTED}" MATCHES "${GDAL_VERSION_MAJOR_SOURCE}.${GDAL_VERSION_MINOR_SOURCE}.${GDAL_VERSION_REV_SOURCE}") + if (NOT IGNORE_GDAL_VERSION_MISMATCH) + message(FATAL_ERROR "Building plugin against GDAL sources ${GDAL_VERSION_MAJOR_SOURCE}.${GDAL_VERSION_MINOR_SOURCE}.${GDAL_VERSION_REV_SOURCE} whereas linked GDAL library is at version ${GDAL_VERSION_IMPORTED}. This is not a nominally supported configuration. You can bypass this check by setting the IGNORE_GDAL_VERSION_MISMATCH variable.") + endif() +endif() + +include(GdalCAndCXXStandards) +include(GdalStandardIncludes) + +include(CheckDependentLibrariesCommon) + +include(GdalCompilationFlags) + +set(GDAL_ENABLE_PLUGINS ON) +set(GDAL_BUILD_OPTIONAL_DRIVERS ON) +set(OGR_ENABLE_PLUGINS ON) +set(OGR_BUILD_OPTIONAL_DRIVERS ON) +include(GdalDriverHelper) + +include(GNUInstallDirs) +# Used by GdalDriverHelper's add_gdal_driver() +set(INSTALL_PLUGIN_DIR + "${CMAKE_INSTALL_LIBDIR}/gdalplugins" + CACHE PATH "Installation sub-directory for plugins") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") + +macro(standalone_driver_finalize VAR) + include(SystemSummary) + include(driver_declaration.cmake) + if (NOT ${VAR}) + message(FATAL_ERROR "${VAR} is not set, due to missing build requirements") + endif() + system_summary(DESCRIPTION "${PROJECT_NAME} is now configured on") + feature_summary(DESCRIPTION "Enabled drivers and features and found dependency packages" WHAT ALL) +endmacro() From 9e85ace225b241aaa6cc2493557600bb770c61fe Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 18:27:06 +0200 Subject: [PATCH 0273/1119] CMake: make MrSID buildable in standalone mode --- cmake/helpers/CheckDependentLibraries.cmake | 8 ++---- .../CheckDependentLibrariesGeoTIFF.cmake | 5 ++++ .../CheckDependentLibrariesMrSID.cmake | 1 + doc/source/drivers/raster/mrsid.rst | 25 +++++++++++++++++++ frmts/CMakeLists.txt | 2 +- frmts/mrsid/CMakeLists.txt | 11 ++++++++ frmts/mrsid/driver_declaration.cmake | 1 + 7 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 cmake/helpers/CheckDependentLibrariesGeoTIFF.cmake create mode 100644 cmake/helpers/CheckDependentLibrariesMrSID.cmake create mode 100644 frmts/mrsid/driver_declaration.cmake diff --git a/cmake/helpers/CheckDependentLibraries.cmake b/cmake/helpers/CheckDependentLibraries.cmake index 0dddba9da97c..448cebba887b 100644 --- a/cmake/helpers/CheckDependentLibraries.cmake +++ b/cmake/helpers/CheckDependentLibraries.cmake @@ -163,11 +163,7 @@ gdal_check_package(ZSTD "ZSTD compression library" CAN_DISABLE ${ZSTD_NAMES_AND_ gdal_check_package(SFCGAL "gdal core supports ISO 19107:2013 and OGC Simple Features Access 1.2 for 3D operations" CAN_DISABLE) -gdal_check_package(GeoTIFF "libgeotiff library (external)" CAN_DISABLE RECOMMENDED - NAMES GeoTIFF - TARGETS geotiff_library GEOTIFF::GEOTIFF -) -gdal_internal_library(GEOTIFF REQUIRED) +include(CheckDependentLibrariesGeoTIFF) gdal_check_package(PNG "PNG compression library (external)" CAN_DISABLE RECOMMENDED VERSION "1.6") gdal_internal_library(PNG) @@ -348,7 +344,7 @@ gdal_check_package(FreeXL "Enable XLS driver" CAN_DISABLE) define_find_package2(GTA gta/gta.h gta PKGCONFIG_NAME gta) gdal_check_package(GTA "Enable GTA driver" CAN_DISABLE) -gdal_check_package(MRSID "MrSID raster SDK" CAN_DISABLE) +include(CheckDependentLibrariesMrSID) set(GDAL_USE_ARMADILLO_OLD ${GDAL_USE_ARMADILLO}) gdal_check_package(Armadillo "C++ library for linear algebra (used for TPS transformation)" CAN_DISABLE) diff --git a/cmake/helpers/CheckDependentLibrariesGeoTIFF.cmake b/cmake/helpers/CheckDependentLibrariesGeoTIFF.cmake new file mode 100644 index 000000000000..82ea7cbc70fe --- /dev/null +++ b/cmake/helpers/CheckDependentLibrariesGeoTIFF.cmake @@ -0,0 +1,5 @@ +gdal_check_package(GeoTIFF "libgeotiff library (external)" CAN_DISABLE RECOMMENDED + NAMES GeoTIFF + TARGETS geotiff_library GEOTIFF::GEOTIFF +) +gdal_internal_library(GEOTIFF REQUIRED) diff --git a/cmake/helpers/CheckDependentLibrariesMrSID.cmake b/cmake/helpers/CheckDependentLibrariesMrSID.cmake new file mode 100644 index 000000000000..e2f37b39feae --- /dev/null +++ b/cmake/helpers/CheckDependentLibrariesMrSID.cmake @@ -0,0 +1 @@ +gdal_check_package(MRSID "MrSID raster SDK" CAN_DISABLE) diff --git a/doc/source/drivers/raster/mrsid.rst b/doc/source/drivers/raster/mrsid.rst index fbe96a84c8a6..ec77d3e4ceaa 100644 --- a/doc/source/drivers/raster/mrsid.rst +++ b/doc/source/drivers/raster/mrsid.rst @@ -63,6 +63,31 @@ GeoKeys, stored in MrSID files. This bug was fixed in MrSID software version 1.5, but if you have older encoders or files, created with older encoders, you cannot use georeference information from them. +Standalone plugin compilation +----------------------------- + +.. versionadded:: 3.10 + +While this driver may be built as part of a whole GDAL build, either in libgdal +itself, or as a plugin, it is also possible to only build this driver as a plugin, +against an already built libgdal. + +The version of the GDAL sources used to build the driver must match the version +of the libgdal it is built against. + +For example, from a "build_mrsid" directory under the root of the GDAL source tree: + +:: + + cmake -S ../frmts/mrsid -DCMAKE_PREFIX_PATH=/path/to/GDAL_installation_prefix -DMRSID_ROOT=/path/to/mrsid_sdk_root + cmake --build . + + +Note that such a plugin, when used against a libgdal not aware of it, will be +systematically loaded at GDAL driver initialization time, and will not benefit from +`deferred plugin loading capabilities `. For that, libgdal itself must be built with the +CMake variable GDAL_REGISTER_DRIVER_MRSID_FOR_LATER_PLUGIN=ON set. + See Also: --------- diff --git a/frmts/CMakeLists.txt b/frmts/CMakeLists.txt index fc9787f9580c..fe27c646f5f5 100644 --- a/frmts/CMakeLists.txt +++ b/frmts/CMakeLists.txt @@ -165,7 +165,7 @@ gdal_dependent_format(jp2lura "JPEG-2000 (based on Luratech)" "GDAL_USE_LURATECH # ESRI ArcSDE C API SDK gdal_dependent_format(sde "ESRI ArcSDE Raster" "HAVE_SDE") # LizardTech's decoding software development kit (DSDK) -gdal_dependent_format(mrsid "Multi-resolution Seamless Image Database" "GDAL_USE_MRSID") +include(mrsid/driver_declaration.cmake) gdal_dependent_format(georaster "Oracle Spatial GeoRaster" "GDAL_USE_ORACLE" DRIVER_NAME_OPTION GEOR) gdal_dependent_format(ecw "ERDAS JPEG2000 (.jp2)" "GDAL_USE_ECW") diff --git a/frmts/mrsid/CMakeLists.txt b/frmts/mrsid/CMakeLists.txt index c91ab9d269ed..bc5ea5fe6ab8 100644 --- a/frmts/mrsid/CMakeLists.txt +++ b/frmts/mrsid/CMakeLists.txt @@ -1,3 +1,14 @@ +cmake_minimum_required(VERSION 3.16...3.28) + +if(NOT DEFINED PROJECT_SOURCE_DIR) + # Standalone plugin building + project(gdal_MrSID) + include("${PROJECT_SOURCE_DIR}/../../cmake/helpers/SetupStandalonePlugin.cmake" ) + include(CheckDependentLibrariesGeoTIFF) + include(CheckDependentLibrariesMrSID) + standalone_driver_finalize(GDAL_ENABLE_DRIVER_MRSID) +endif() + option(GDAL_ENABLE_DRIVER_JP2MRSID "Whether to enable JPEG2000 support with MrSID SDK" OFF) add_gdal_driver(TARGET gdal_MrSID diff --git a/frmts/mrsid/driver_declaration.cmake b/frmts/mrsid/driver_declaration.cmake new file mode 100644 index 000000000000..007b65737707 --- /dev/null +++ b/frmts/mrsid/driver_declaration.cmake @@ -0,0 +1 @@ +gdal_dependent_format(mrsid "Multi-resolution Seamless Image Database" "GDAL_USE_MRSID") From 36055b6de63d95b7c6e831be80d9b3dcfbf5ee54 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 21:13:11 +0200 Subject: [PATCH 0274/1119] tiff_srs.py: skip 2 tests if libgeotiff < 1.6.0 --- autotest/gcore/tiff_srs.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/autotest/gcore/tiff_srs.py b/autotest/gcore/tiff_srs.py index 71a922ad89f6..a4d9d6c9c9a1 100755 --- a/autotest/gcore/tiff_srs.py +++ b/autotest/gcore/tiff_srs.py @@ -621,7 +621,7 @@ def test_tiff_srs_proj4(proj4): def _create_geotiff1_1_from_copy_and_compare(srcfilename, options=[]): if int(gdal.GetDriverByName("GTiff").GetMetadataItem("LIBGEOTIFF")) < 1600: - pytest.skip() + pytest.skip("libgeotiff >= 1.6.0 required") src_ds = gdal.Open(srcfilename) tmpfile = "/vsimem/tmp.tif" @@ -1250,9 +1250,11 @@ def test_tiff_srs_projected_GTCitationGeoKey_with_underscore_and_GeogTOWGS84GeoK def test_tiff_srs_write_compound_with_non_epsg_vert_crs(): - """Test bugfix for https://github.com/OSGeo/gdal/issues/7833""" + if int(gdal.GetDriverByName("GTiff").GetMetadataItem("LIBGEOTIFF")) < 1600: + pytest.skip("libgeotiff >= 1.6.0 required") + filename = "/vsimem/test_tiff_srs_write_compound_with_non_epsg_vert_crs.tif" srs = osr.SpatialReference() srs.SetFromUserInput( @@ -1349,6 +1351,9 @@ def test_tiff_srs_read_compound_without_EPSG_code(): """Test case where identification of code for CompoundCRS (added for bugfix of https://github.com/OSGeo/gdal/issues/7982) doesn't trigger""" + if int(gdal.GetDriverByName("GTiff").GetMetadataItem("LIBGEOTIFF")) < 1600: + pytest.skip("libgeotiff >= 1.6.0 required") + filename = "/vsimem/test_tiff_srs_read_compound_without_EPSG_code.tif" srs = osr.SpatialReference() # WGS 84 + NAP height, unlikely to have a EPSG code ever From db2e14d0d97c1b131253ea01971b72475d273a5f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 19:03:44 +0200 Subject: [PATCH 0275/1119] CI: test building MrSID driver in standalone mode --- .github/workflows/ubuntu_20.04/Dockerfile.ci | 1 + .github/workflows/ubuntu_20.04/build.sh | 20 ++++++++++++++------ frmts/mrsid/CMakeLists.txt | 3 +++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ubuntu_20.04/Dockerfile.ci b/.github/workflows/ubuntu_20.04/Dockerfile.ci index 50aa2c17d8a5..531c52ba75b1 100644 --- a/.github/workflows/ubuntu_20.04/Dockerfile.ci +++ b/.github/workflows/ubuntu_20.04/Dockerfile.ci @@ -37,6 +37,7 @@ RUN apt-get update -y \ libfreexl-dev \ libfyba-dev \ libgeos-dev \ + libgeotiff-dev \ libgif-dev \ libhdf4-alt-dev \ libhdf5-serial-dev \ diff --git a/.github/workflows/ubuntu_20.04/build.sh b/.github/workflows/ubuntu_20.04/build.sh index 510a463b79cf..1b7b17ff3512 100755 --- a/.github/workflows/ubuntu_20.04/build.sh +++ b/.github/workflows/ubuntu_20.04/build.sh @@ -5,12 +5,12 @@ set -eu export CXXFLAGS="-march=native -O2 -Wodr -flto-odr-type-merging -Werror" export CFLAGS="-O2 -march=native -Werror" -cmake ${GDAL_SOURCE_DIR:=..} \ +cmake "${GDAL_SOURCE_DIR:=..}" \ -DUSE_CCACHE=ON \ -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \ - -DCMAKE_INSTALL_PREFIX=/usr \ - -DGDAL_USE_TIFF_INTERNAL=ON \ - -DGDAL_USE_GEOTIFF_INTERNAL=ON \ + -DCMAKE_INSTALL_PREFIX=/tmp/install-gdal \ + -DGDAL_USE_TIFF_INTERNAL=OFF \ + -DGDAL_USE_GEOTIFF_INTERNAL=OFF \ -DECW_ROOT=/opt/libecwj2-3.3 \ -DMRSID_ROOT=/usr/local \ -DFileGDB_ROOT=/usr/local/FileGDB_API \ @@ -20,5 +20,13 @@ cmake ${GDAL_SOURCE_DIR:=..} \ unset CXXFLAGS unset CFLAGS -make -j$(nproc) -make -j$(nproc) install DESTDIR=/tmp/install-gdal +make "-j$(nproc)" +make "-j$(nproc)" install + +# Test building MrSID driver in standalone mode +mkdir build_mrsid +cd build_mrsid +cmake -S ${GDAL_SOURCE_DIR:=..}/frmts/mrsid -DMRSID_ROOT=/usr/local -DCMAKE_PREFIX_PATH=/tmp/install-gdal +cmake --build . "-j$(nproc)" +test -f gdal_MrSID.so +cd .. diff --git a/frmts/mrsid/CMakeLists.txt b/frmts/mrsid/CMakeLists.txt index bc5ea5fe6ab8..5a7021927847 100644 --- a/frmts/mrsid/CMakeLists.txt +++ b/frmts/mrsid/CMakeLists.txt @@ -6,6 +6,9 @@ if(NOT DEFINED PROJECT_SOURCE_DIR) include("${PROJECT_SOURCE_DIR}/../../cmake/helpers/SetupStandalonePlugin.cmake" ) include(CheckDependentLibrariesGeoTIFF) include(CheckDependentLibrariesMrSID) + if (GDAL_USE_GEOTIFF_INTERNAL) + message(FATAL_ERROR "Internal libgeotiff not supported for MrSID standalone plugin build") + endif() standalone_driver_finalize(GDAL_ENABLE_DRIVER_MRSID) endif() From 2cea7b80e3b076a2074035d670e61f5dd833dd1a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 18:28:02 +0200 Subject: [PATCH 0276/1119] CMake: make Kakadu driver buildable in standalone mode --- cmake/helpers/CheckDependentLibraries.cmake | 2 +- .../CheckDependentLibrariesKakadu.cmake | 1 + doc/source/drivers/raster/jp2kak.rst | 25 +++++++++++++++++++ frmts/CMakeLists.txt | 2 +- frmts/jp2kak/CMakeLists.txt | 10 ++++++++ frmts/jp2kak/driver_declaration.cmake | 1 + 6 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 cmake/helpers/CheckDependentLibrariesKakadu.cmake create mode 100644 frmts/jp2kak/driver_declaration.cmake diff --git a/cmake/helpers/CheckDependentLibraries.cmake b/cmake/helpers/CheckDependentLibraries.cmake index 448cebba887b..97ede1ee7d16 100644 --- a/cmake/helpers/CheckDependentLibraries.cmake +++ b/cmake/helpers/CheckDependentLibraries.cmake @@ -519,7 +519,7 @@ option(GDAL_USE_PUBLICDECOMPWT "Set ON to build MSG driver and download external https://gitlab.eumetsat.int/open-source/PublicDecompWT" OFF) # proprietary libraries KAKADU -gdal_check_package(KDU "Enable KAKADU" CAN_DISABLE) +include(CheckDependentLibrariesKakadu) gdal_check_package(LURATECH "Enable JP2Lura driver" CAN_DISABLE) gdal_check_package(Arrow "Apache Arrow C++ library" CONFIG CAN_DISABLE) diff --git a/cmake/helpers/CheckDependentLibrariesKakadu.cmake b/cmake/helpers/CheckDependentLibrariesKakadu.cmake new file mode 100644 index 000000000000..5ef1b0f4383d --- /dev/null +++ b/cmake/helpers/CheckDependentLibrariesKakadu.cmake @@ -0,0 +1 @@ +gdal_check_package(KDU "Enable KAKADU" CAN_DISABLE) diff --git a/doc/source/drivers/raster/jp2kak.rst b/doc/source/drivers/raster/jp2kak.rst index da787ca31420..cf0a2ecbc741 100644 --- a/doc/source/drivers/raster/jp2kak.rst +++ b/doc/source/drivers/raster/jp2kak.rst @@ -284,6 +284,31 @@ unistd.h in kdu_arch.cpp. This means that \_SC_NPROCESSORS_ONLN and always return 0. Therefore the jp2kak driver might not default to creating worker threads. +Standalone plugin compilation +----------------------------- + +.. versionadded:: 3.10 + +While this driver may be built as part of a whole GDAL build, either in libgdal +itself, or as a plugin, it is also possible to only build this driver as a plugin, +against an already built libgdal. + +The version of the GDAL sources used to build the driver must match the version +of the libgdal it is built against. + +For example, from a "build_jp2kak" directory under the root of the GDAL source tree: + +:: + + cmake -S ../frmts/jp2kak -DCMAKE_PREFIX_PATH=/path/to/GDAL_installation_prefix -DKDU_ROOT=/path/to/kakadu_root + cmake --build . + + +Note that such a plugin, when used against a libgdal not aware of it, will be +systematically loaded at GDAL driver initialization time, and will not benefit from +`deferred plugin loading capabilities `. For that, libgdal itself must be built with the +CMake variable GDAL_REGISTER_DRIVER_JP2KAK_FOR_LATER_PLUGIN=ON set. + See Also -------- diff --git a/frmts/CMakeLists.txt b/frmts/CMakeLists.txt index fe27c646f5f5..2ee5e9e0a921 100644 --- a/frmts/CMakeLists.txt +++ b/frmts/CMakeLists.txt @@ -158,7 +158,7 @@ gdal_dependent_format(basisu_ktx2 "Basis Universal and KTX2 texture formats" "GD # ###################################################################################################################### # driver with proprietary libraries Kakadu software SDK -gdal_dependent_format(jp2kak "JPEG-2000 (based on Kakadu)" "GDAL_USE_KDU") +include(jp2kak/driver_declaration.cmake) gdal_dependent_format(jpipkak "JPIP Streaming" "GDAL_USE_KDU") # Luratech SDK gdal_dependent_format(jp2lura "JPEG-2000 (based on Luratech)" "GDAL_USE_LURATECH") diff --git a/frmts/jp2kak/CMakeLists.txt b/frmts/jp2kak/CMakeLists.txt index fa95a832ab21..194e5c01cf56 100644 --- a/frmts/jp2kak/CMakeLists.txt +++ b/frmts/jp2kak/CMakeLists.txt @@ -1,3 +1,13 @@ +cmake_minimum_required(VERSION 3.16...3.28) + +if(NOT DEFINED PROJECT_SOURCE_DIR) + # Standalone plugin building + project(gdal_JP2KAK) + include("${PROJECT_SOURCE_DIR}/../../cmake/helpers/SetupStandalonePlugin.cmake" ) + include(CheckDependentLibrariesKakadu) + standalone_driver_finalize(GDAL_ENABLE_DRIVER_JP2KAK) +endif() + add_gdal_driver(TARGET gdal_JP2KAK SOURCES jp2kakdataset.cpp jp2kak_headers.h diff --git a/frmts/jp2kak/driver_declaration.cmake b/frmts/jp2kak/driver_declaration.cmake new file mode 100644 index 000000000000..118d43fd80b5 --- /dev/null +++ b/frmts/jp2kak/driver_declaration.cmake @@ -0,0 +1 @@ +gdal_dependent_format(jp2kak "JPEG-2000 (based on Kakadu)" "GDAL_USE_KDU") From 38fbe8255f7602aa4029c17850a0c74cc1487d90 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 20:07:45 +0200 Subject: [PATCH 0277/1119] CMake: make OCI driver buildable in standalone mode --- .../helpers/CheckDependentLibrariesOCI.cmake | 2 ++ doc/source/drivers/vector/oci.rst | 30 +++++++++++++++++-- ogr/ogrsf_frmts/CMakeLists.txt | 2 +- ogr/ogrsf_frmts/oci/CMakeLists.txt | 10 +++++++ ogr/ogrsf_frmts/oci/driver_declaration.cmake | 1 + 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 cmake/helpers/CheckDependentLibrariesOCI.cmake create mode 100644 ogr/ogrsf_frmts/oci/driver_declaration.cmake diff --git a/cmake/helpers/CheckDependentLibrariesOCI.cmake b/cmake/helpers/CheckDependentLibrariesOCI.cmake new file mode 100644 index 000000000000..76d2644b1965 --- /dev/null +++ b/cmake/helpers/CheckDependentLibrariesOCI.cmake @@ -0,0 +1,2 @@ +set(Oracle_CAN_USE_CLNTSH_AS_MAIN_LIBRARY ON) +gdal_check_package(Oracle "Enable Oracle OCI driver" CAN_DISABLE) diff --git a/doc/source/drivers/vector/oci.rst b/doc/source/drivers/vector/oci.rst index 9f8b1fa18381..764767c2c9d5 100644 --- a/doc/source/drivers/vector/oci.rst +++ b/doc/source/drivers/vector/oci.rst @@ -353,8 +353,34 @@ coordinate reference system, and converting timestamps to UTC. ogr2ogr -f GPKG output.gpkg -nln new_layer_name -nlt POLYGON -s_srs EPSG:25832 -t_srs EPSG:25832 -dsco DATETIME_FORMAT=UTC OCI:username/password@host_name:port_number/service_name:MY_SCHEMA.MY_VIEW -sql "SELECT COLUMN_A, COLUMN_B, GEOMETRY FROM MY_SCHEMA.MY_VIEW" +Standalone plugin compilation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 3.10 + +While this driver may be built as part of a whole GDAL build, either in libgdal +itself, or as a plugin, it is also possible to only build this driver as a plugin, +against an already built libgdal. + +The version of the GDAL sources used to build the driver must match the version +of the libgdal it is built against. + +For example, from a "build_oci" directory under the root of the GDAL source tree: + +:: + + cmake -S ../ogr/ogrsf_frmts/oci -DCMAKE_PREFIX_PATH=/path/to/GDAL_installation_prefix -DOracle_ROOT=/path/to/instantclient_sdk_root + cmake --build . + + +Note that such a plugin, when used against a libgdal not aware of it, will be +systematically loaded at GDAL driver initialization time, and will not benefit from +`deferred plugin loading capabilities `. For that, libgdal itself must be built with the +CMake variable OGR_REGISTER_DRIVER_OCI_FOR_LATER_PLUGIN=ON set. + Credits ~~~~~~~ -I would like to thank `SRC, LLC `__ for -its financial support of the development of this driver. +`SRC, LLC `__ for its financial support of +the initial development of this driver. + diff --git a/ogr/ogrsf_frmts/CMakeLists.txt b/ogr/ogrsf_frmts/CMakeLists.txt index 690e4a0ef339..96bd9c670383 100644 --- a/ogr/ogrsf_frmts/CMakeLists.txt +++ b/ogr/ogrsf_frmts/CMakeLists.txt @@ -107,7 +107,7 @@ ogr_dependent_driver(gtfs "GTFS" "OGR_ENABLE_DRIVER_CSV") # ###################################################################################################################### # proprietary libraries -ogr_dependent_driver(oci "Oracle OCI" "GDAL_USE_ORACLE") +include(oci/driver_declaration.cmake) ogr_dependent_driver(idb "IDB" "GDAL_USE_IDB") ogr_dependent_driver(ods ODS "GDAL_USE_EXPAT") ogr_dependent_driver(ogdi "OGDI" "GDAL_USE_OGDI") diff --git a/ogr/ogrsf_frmts/oci/CMakeLists.txt b/ogr/ogrsf_frmts/oci/CMakeLists.txt index 577e1b1c2809..e3cc52932dc4 100644 --- a/ogr/ogrsf_frmts/oci/CMakeLists.txt +++ b/ogr/ogrsf_frmts/oci/CMakeLists.txt @@ -1,3 +1,13 @@ +cmake_minimum_required(VERSION 3.16...3.28) + +if(NOT DEFINED PROJECT_SOURCE_DIR) + # Standalone plugin building + project(ogr_OCI) + include("${PROJECT_SOURCE_DIR}/../../../cmake/helpers/SetupStandalonePlugin.cmake" ) + include(CheckDependentLibrariesOCI) + standalone_driver_finalize(OGR_ENABLE_DRIVER_OCI) +endif() + set(SOURCE oci_utils.cpp ocitest.cpp diff --git a/ogr/ogrsf_frmts/oci/driver_declaration.cmake b/ogr/ogrsf_frmts/oci/driver_declaration.cmake new file mode 100644 index 000000000000..cb0e78641e79 --- /dev/null +++ b/ogr/ogrsf_frmts/oci/driver_declaration.cmake @@ -0,0 +1 @@ +ogr_dependent_driver(oci "Oracle OCI" "GDAL_USE_ORACLE") From d147a5109e116af7172ad15149fe1cf71cf1e561 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 20:08:09 +0200 Subject: [PATCH 0278/1119] CI: test building OCI driver in standalone mode --- .github/workflows/ubuntu_20.04/build.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ubuntu_20.04/build.sh b/.github/workflows/ubuntu_20.04/build.sh index 1b7b17ff3512..e2a8cdde3517 100755 --- a/.github/workflows/ubuntu_20.04/build.sh +++ b/.github/workflows/ubuntu_20.04/build.sh @@ -30,3 +30,15 @@ cmake -S ${GDAL_SOURCE_DIR:=..}/frmts/mrsid -DMRSID_ROOT=/usr/local -DCMAKE_PREF cmake --build . "-j$(nproc)" test -f gdal_MrSID.so cd .. + +# Test building OCI driver in standalone mode +mkdir build_oci +cd build_oci +wget https://download.oracle.com/otn_software/linux/instantclient/1923000/instantclient-basic-linux.x64-19.23.0.0.0dbru.zip +wget https://download.oracle.com/otn_software/linux/instantclient/1923000/instantclient-sdk-linux.x64-19.23.0.0.0dbru.zip +unzip -o instantclient-basic-linux.x64-19.23.0.0.0dbru.zip +unzip -o instantclient-sdk-linux.x64-19.23.0.0.0dbru.zip +cmake -S "${GDAL_SOURCE_DIR:=..}/ogr/ogrsf_frmts/oci" "-DOracle_ROOT=$PWD/instantclient_19_23" -DCMAKE_PREFIX_PATH=/tmp/install-gdal +cmake --build . "-j$(nproc)" +test -f ogr_OCI.so +cd .. From 367c0bb3b188ae9da1fa83d78cd497033d040dcd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 22:45:11 +0200 Subject: [PATCH 0279/1119] GML writer: fix missing SRS in Featurecollection's boundedBy element (3.9.1 regression) Fixes #10332 Was caused by the fix 3674ec7570f324ea43eef08b6f5ce3a29de2741d for #10071 The regression mostly occurs when writing a GML file using ogr2ogr when the source layer has a named geometry column, e.g if the source is a GeoPackage or if using -sql The problem was acutally latent and also could have occured before 3.9.1 if using CreateLayer() without a SRS + CreateGeomField() --- autotest/ogr/ogr_gml.py | 92 ++++++++++++++++++++++++ ogr/ogrsf_frmts/gml/ogr_gml.h | 18 ++++- ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp | 88 +++++++++++++---------- ogr/ogrsf_frmts/gml/ogrgmllayer.cpp | 34 ++------- 4 files changed, 161 insertions(+), 71 deletions(-) diff --git a/autotest/ogr/ogr_gml.py b/autotest/ogr/ogr_gml.py index eadda1826340..fc70d74f1766 100755 --- a/autotest/ogr/ogr_gml.py +++ b/autotest/ogr/ogr_gml.py @@ -4356,3 +4356,95 @@ def test_ogr_gml_geom_link_to_immediate_child(): "data/gml/link_to_immediate_child.gml", open_options=["WRITE_GFS=NO"] ) assert ds + + +############################################################################### +# Test scenario of https://github.com/OSGeo/gdal/issues/10332 + + +@pytest.mark.parametrize("use_create_geom_field", [False, True]) +@pytest.mark.parametrize("has_srs", [False, True]) +def test_ogr_gml_ogr2ogr_from_layer_with_name_geom_field( + tmp_vsimem, use_create_geom_field, has_srs +): + + ds = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + if has_srs: + srs = osr.SpatialReference() + srs.ImportFromEPSG(4326) + srs.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER) + else: + srs = None + if use_create_geom_field: + lyr = ds.CreateLayer("test", geom_type=ogr.wkbNone) + my_geom_field = ogr.GeomFieldDefn("my_geom", ogr.wkbUnknown) + my_geom_field.SetSpatialRef(srs) + lyr.CreateGeomField(my_geom_field) + else: + lyr = ds.CreateLayer("test", geom_type=ogr.wkbUnknown, srs=srs) + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometryDirectly(ogr.CreateGeometryFromWkt("POINT(2 49)")) + lyr.CreateFeature(f) + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometryDirectly(ogr.CreateGeometryFromWkt("POINT(3 50)")) + lyr.CreateFeature(f) + + out_filename = str(tmp_vsimem / "out.gml") + gdal.VectorTranslate(out_filename, ds, format="GML") + + f = gdal.VSIFOpenL(out_filename, "rb") + assert f + try: + data = gdal.VSIFReadL(1, 10000, f) + finally: + gdal.VSIFCloseL(f) + + if has_srs: + assert ( + b'49 250 3' + in data + ) + else: + assert ( + b"2 493 50" + in data + ) + + +############################################################################### + + +@pytest.mark.parametrize("first_layer_has_srs", [False, True]) +def test_ogr_gml_ogr2ogr_from_layers_with_inconsistent_srs( + tmp_vsimem, first_layer_has_srs +): + + ds = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + srs = osr.SpatialReference() + srs.ImportFromEPSG(4326) + srs.SetAxisMappingStrategy(osr.OAMS_TRADITIONAL_GIS_ORDER) + lyr = ds.CreateLayer( + "test", geom_type=ogr.wkbUnknown, srs=(srs if first_layer_has_srs else None) + ) + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometryDirectly(ogr.CreateGeometryFromWkt("POINT(2 49)")) + lyr.CreateFeature(f) + + lyr = ds.CreateLayer( + "test2", geom_type=ogr.wkbUnknown, srs=(None if first_layer_has_srs else srs) + ) + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometryDirectly(ogr.CreateGeometryFromWkt("POINT(3 50)")) + lyr.CreateFeature(f) + + out_filename = str(tmp_vsimem / "out.gml") + gdal.VectorTranslate(out_filename, ds, format="GML") + + f = gdal.VSIFOpenL(out_filename, "rb") + assert f + try: + data = gdal.VSIFReadL(1, 10000, f) + finally: + gdal.VSIFCloseL(f) + + assert b"" in data diff --git a/ogr/ogrsf_frmts/gml/ogr_gml.h b/ogr/ogrsf_frmts/gml/ogr_gml.h index b5e0cda9b9e3..4ccedcbbfb61 100644 --- a/ogr/ogrsf_frmts/gml/ogr_gml.h +++ b/ogr/ogrsf_frmts/gml/ogr_gml.h @@ -61,7 +61,6 @@ class OGRGMLLayer final : public OGRLayer char *pszFIDPrefix; bool bWriter; - bool bSameSRS; OGRGMLDataSource *poDS; @@ -137,8 +136,14 @@ class OGRGMLDataSource final : public OGRDataSource OGRGMLSRSNameFormat eSRSNameFormat; bool bWriteSpaceIndentation; - OGRSpatialReference *poWriteGlobalSRS; - bool bWriteGlobalSRS; + //! Whether all geometry fields of all layers have the same SRS (or no SRS at all) + bool m_bWriteGlobalSRS = true; + + //! The global SRS (may be null), that is valid only if m_bWriteGlobalSRS == true + std::unique_ptr m_poWriteGlobalSRS{}; + + //! Whether at least one geometry field has been created + bool m_bWriteGlobalSRSInit = false; // input related parameters. CPLString osFilename; @@ -300,6 +305,13 @@ class OGRGMLDataSource final : public OGRDataSource const char *GetSRSDimensionLoc() const; bool GMLFeatureCollection() const; + void DeclareNewWriteSRS(const OGRSpatialReference *poSRS); + + bool HasWriteGlobalSRS() const + { + return m_bWriteGlobalSRS; + } + virtual OGRLayer *ExecuteSQL(const char *pszSQLCommand, OGRGeometry *poSpatialFilter, const char *pszDialect) override; diff --git a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp index 4ef9d4f6b47a..d9463991ef84 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp @@ -86,9 +86,8 @@ OGRGMLDataSource::OGRGMLDataSource() nBoundedByLocation(-1), nSchemaInsertLocation(-1), bIsOutputGML3(false), bIsOutputGML3Deegree(false), bIsOutputGML32(false), eSRSNameFormat(SRSNAME_SHORT), bWriteSpaceIndentation(true), - poWriteGlobalSRS(nullptr), bWriteGlobalSRS(false), poReader(nullptr), - bOutIsTempFile(false), bExposeGMLId(false), bExposeFid(false), - bIsWFS(false), bUseGlobalSRSName(false), + poReader(nullptr), bOutIsTempFile(false), bExposeGMLId(false), + bExposeFid(false), bIsWFS(false), bUseGlobalSRSName(false), m_bInvertAxisOrderIfLatLong(false), m_bConsiderEPSGAsURN(false), m_eSwapCoordinates(GML_SWAP_AUTO), m_bGetSecondaryGeometryOption(false), eReadMode(STANDARD), poStoredGMLFeature(nullptr), @@ -126,13 +125,13 @@ OGRGMLDataSource::~OGRGMLDataSource() if (!bFpOutputIsNonSeekable && nBoundedByLocation != -1 && VSIFSeekL(fpOutput, nBoundedByLocation, SEEK_SET) == 0) { - if (bWriteGlobalSRS && sBoundingRect.IsInit() && IsGML3Output()) + if (m_bWriteGlobalSRS && sBoundingRect.IsInit() && IsGML3Output()) { bool bCoordSwap = false; char *pszSRSName = - poWriteGlobalSRS - ? GML_GetSRSName(poWriteGlobalSRS, eSRSNameFormat, - &bCoordSwap) + m_poWriteGlobalSRS + ? GML_GetSRSName(m_poWriteGlobalSRS.get(), + eSRSNameFormat, &bCoordSwap) : CPLStrdup(""); char szLowerCorner[75] = {}; char szUpperCorner[75] = {}; @@ -201,7 +200,7 @@ OGRGMLDataSource::~OGRGMLDataSource() szLowerCorner, szUpperCorner); CPLFree(pszSRSName); } - else if (bWriteGlobalSRS && sBoundingRect.IsInit()) + else if (m_bWriteGlobalSRS && sBoundingRect.IsInit()) { if (bWriteSpaceIndentation) VSIFPrintfL(fpOutput, " "); @@ -268,8 +267,6 @@ OGRGMLDataSource::~OGRGMLDataSource() delete poReader; } - delete poWriteGlobalSRS; - delete poStoredGMLFeature; if (osXSDFilename.compare(CPLSPrintf("/vsimem/tmp_gml_xsd_%p.xsd", this)) == @@ -2001,6 +1998,48 @@ void OGRGMLDataSource::WriteTopElements() } } +/************************************************************************/ +/* DeclareNewWriteSRS() */ +/************************************************************************/ + +// Check that all SRS passed to ICreateLayer() and CreateGeomField() +// are the same (or all null) + +void OGRGMLDataSource::DeclareNewWriteSRS(const OGRSpatialReference *poSRS) +{ + if (m_bWriteGlobalSRS) + { + if (!m_bWriteGlobalSRSInit) + { + m_bWriteGlobalSRSInit = true; + if (poSRS) + { + m_poWriteGlobalSRS.reset(poSRS->Clone()); + m_poWriteGlobalSRS->SetAxisMappingStrategy( + OAMS_TRADITIONAL_GIS_ORDER); + } + } + else + { + if (m_poWriteGlobalSRS) + { + const char *const apszOptions[] = { + "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr}; + if (!poSRS || + !poSRS->IsSame(m_poWriteGlobalSRS.get(), apszOptions)) + { + m_bWriteGlobalSRS = false; + } + } + else + { + if (poSRS) + m_bWriteGlobalSRS = false; + } + } + } +} + /************************************************************************/ /* ICreateLayer() */ /************************************************************************/ @@ -2037,37 +2076,9 @@ OGRGMLDataSource::ICreateLayer(const char *pszLayerName, pszLayerName, pszCleanLayerName); } - // Set or check validity of global SRS. if (nLayers == 0) { WriteTopElements(); - if (poSRS) - { - poWriteGlobalSRS = poSRS->Clone(); - poWriteGlobalSRS->SetAxisMappingStrategy( - OAMS_TRADITIONAL_GIS_ORDER); - } - bWriteGlobalSRS = true; - } - else if (bWriteGlobalSRS) - { - if (poWriteGlobalSRS != nullptr) - { - const char *const apszOptions[] = { - "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr}; - if (poSRS == nullptr || - !poSRS->IsSame(poWriteGlobalSRS, apszOptions)) - { - delete poWriteGlobalSRS; - poWriteGlobalSRS = nullptr; - bWriteGlobalSRS = false; - } - } - else - { - if (poSRS != nullptr) - bWriteGlobalSRS = false; - } } // Create the layer object. @@ -2081,6 +2092,7 @@ OGRGMLDataSource::ICreateLayer(const char *pszLayerName, pszGeomFieldName = "geometryProperty"; poGeomFieldDefn->SetName(pszGeomFieldName); poGeomFieldDefn->SetNullable(poSrcGeomFieldDefn->IsNullable()); + DeclareNewWriteSRS(poSRS); if (poSRS != nullptr) { auto poSRSClone = poSRS->Clone(); diff --git a/ogr/ogrsf_frmts/gml/ogrgmllayer.cpp b/ogr/ogrsf_frmts/gml/ogrgmllayer.cpp index d5a9a3009d33..bb47fa9e9ab7 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmllayer.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmllayer.cpp @@ -44,7 +44,7 @@ OGRGMLLayer::OGRGMLLayer(const char *pszName, bool bWriterIn, : poFeatureDefn(new OGRFeatureDefn( pszName + (STARTS_WITH_CI(pszName, "ogr:") ? 4 : 0))), iNextGMLId(0), bInvalidFIDFound(false), pszFIDPrefix(nullptr), - bWriter(bWriterIn), bSameSRS(false), poDS(poDSIn), + bWriter(bWriterIn), poDS(poDSIn), poFClass(!bWriter ? poDS->GetReader()->GetClass(pszName) : nullptr), // Reader's should get the corresponding GMLFeatureClass and cache it. hCacheSRS(GML_BuildOGRGeometryFromList_CreateCache()), @@ -708,33 +708,6 @@ OGRErr OGRGMLLayer::ICreateFeature(OGRFeature *poFeature) poDS->PrintLine(fp, ""); } - if (iNextGMLId == 0) - { - bSameSRS = true; - for (int iGeomField = 1; - iGeomField < poFeatureDefn->GetGeomFieldCount(); iGeomField++) - { - OGRGeomFieldDefn *poFieldDefn0 = poFeatureDefn->GetGeomFieldDefn(0); - OGRGeomFieldDefn *poFieldDefn = - poFeatureDefn->GetGeomFieldDefn(iGeomField); - const OGRSpatialReference *poSRS0 = poFieldDefn0->GetSpatialRef(); - const OGRSpatialReference *poSRS = poFieldDefn->GetSpatialRef(); - if (poSRS0 != nullptr && poSRS == nullptr) - { - bSameSRS = false; - } - else if (poSRS0 == nullptr && poSRS != nullptr) - { - bSameSRS = false; - } - else if (poSRS0 != nullptr && poSRS != nullptr && poSRS0 != poSRS && - !poSRS0->IsSame(poSRS)) - { - bSameSRS = false; - } - } - } - if (poFeature->GetFID() == OGRNullFID) poFeature->SetFID(iNextGMLId++); @@ -794,7 +767,7 @@ OGRErr OGRGMLLayer::ICreateFeature(OGRFeature *poFeature) const int nCoordDimension = poGeom->getCoordinateDimension(); poGeom->getEnvelope(&sGeomBounds); - if (bSameSRS) + if (poDS->HasWriteGlobalSRS()) poDS->GrowExtents(&sGeomBounds, nCoordDimension); if (poGeom->getSpatialReference() == nullptr && @@ -1246,7 +1219,8 @@ OGRErr OGRGMLLayer::CreateGeomField(const OGRGeomFieldDefn *poField, /* Enforce XML naming semantics on element name. */ /* -------------------------------------------------------------------- */ OGRGeomFieldDefn oCleanCopy(poField); - auto poSRSOri = poField->GetSpatialRef(); + const auto poSRSOri = poField->GetSpatialRef(); + poDS->DeclareNewWriteSRS(poSRSOri); if (poSRSOri) { auto poSRS = poSRSOri->Clone(); From f900bb48f8d035755e5c7d70acf20d97138173dd Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 23:01:50 +0200 Subject: [PATCH 0280/1119] docker/README.md: advertize 3.9.1 docker images [ci skip] --- docker/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/README.md b/docker/README.md index 2eb423b940de..ae0c0976a9f1 100644 --- a/docker/README.md +++ b/docker/README.md @@ -100,11 +100,11 @@ If you are getting a ``: arena 0 background thread creation failed (1) # Images of releases -Tagged images of recent past releases are available. The last ones (at time of writing) are for GDAL 3.9.0 and PROJ 9.4.0, for linux/amd64 and linux/arm64: -* ghcr.io/osgeo/gdal:alpine-small-3.9.0 -* ghcr.io/osgeo/gdal:alpine-normal-3.9.0 -* ghcr.io/osgeo/gdal:ubuntu-small-3.9.0 -* ghcr.io/osgeo/gdal:ubuntu-full-3.9.0 +Tagged images of recent past releases are available. The last ones (at time of writing) are for GDAL 3.9.1 and PROJ 9.4.1, for linux/amd64 and linux/arm64: +* ghcr.io/osgeo/gdal:alpine-small-3.9.1 +* ghcr.io/osgeo/gdal:alpine-normal-3.9.1 +* ghcr.io/osgeo/gdal:ubuntu-small-3.9.1 +* ghcr.io/osgeo/gdal:ubuntu-full-3.9.1 ## Multi-arch Images From c456677528ad1d1e9ef2b04b0c177919b5ffd7b9 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 27 Jun 2024 23:17:39 +0200 Subject: [PATCH 0281/1119] test_ogrsf: fix when layer name is a reserved SQL keyword --- apps/test_ogrsf.cpp | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/apps/test_ogrsf.cpp b/apps/test_ogrsf.cpp index 6567012256b0..ddb13d4dc22b 100644 --- a/apps/test_ogrsf.cpp +++ b/apps/test_ogrsf.cpp @@ -1499,20 +1499,30 @@ static int TestLayerErrorConditions(OGRLayer *poLyr) static const char *GetLayerNameForSQL(GDALDataset *poDS, const char *pszLayerName) { - char ch; - for (int i = 0; (ch = pszLayerName[i]) != 0; i++) - { - if (ch >= '0' && ch <= '9') - { - if (i == 0) + /* Only quote if needed. Quoting conventions depend on the driver... */ + if (!EQUAL(pszLayerName, "SELECT") && !EQUAL(pszLayerName, "AS") && + !EQUAL(pszLayerName, "CAST") && !EQUAL(pszLayerName, "FROM") && + !EQUAL(pszLayerName, "JOIN") && !EQUAL(pszLayerName, "WHERE") && + !EQUAL(pszLayerName, "ON") && !EQUAL(pszLayerName, "USING") && + !EQUAL(pszLayerName, "ORDER") && !EQUAL(pszLayerName, "BY") && + !EQUAL(pszLayerName, "ASC") && !EQUAL(pszLayerName, "DESC") && + !EQUAL(pszLayerName, "GROUP") && !EQUAL(pszLayerName, "LIMIT") && + !EQUAL(pszLayerName, "OFFSET")) + { + char ch; + for (int i = 0; (ch = pszLayerName[i]) != 0; i++) + { + if (ch >= '0' && ch <= '9') + { + if (i == 0) + break; + } + else if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) break; } - else if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) - break; + if (ch == 0) + return pszLayerName; } - /* Only quote if needed. Quoting conventions depend on the driver... */ - if (ch == 0) - return pszLayerName; if (EQUAL(poDS->GetDriverName(), "MYSQL")) return CPLSPrintf("`%s`", pszLayerName); From 3643e2af50acaa9aab138edc7216b6ad8c55b1b2 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 28 Jun 2024 16:47:53 +0200 Subject: [PATCH 0282/1119] GTiff: emit clearer error when attempting to create external JPEG compressed overviews on dataset with color table --- autotest/gcore/tiff_ovr.py | 28 ++++++++++++++++++++++++++++ frmts/gtiff/gt_overview.cpp | 13 ++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/autotest/gcore/tiff_ovr.py b/autotest/gcore/tiff_ovr.py index 43577d218158..ba49b4b3fb45 100755 --- a/autotest/gcore/tiff_ovr.py +++ b/autotest/gcore/tiff_ovr.py @@ -1602,6 +1602,34 @@ def test_tiff_ovr_42(tmp_path, both_endian): ds = None +############################################################################### +# Test (failed) attempt at creating JPEG external overviews on dataset with color table + + +@pytest.mark.require_creation_option("GTiff", "JPEG") +@gdaltest.enable_exceptions() +def test_tiff_ovr_jpeg_on_color_table(tmp_path): + + tif_fname = str(tmp_path / "test_tiff_ovr_jpeg_on_color_table.tif") + + ct_data = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 255)] + + ct = gdal.ColorTable() + for i, data in enumerate(ct_data): + ct.SetColorEntry(i, data) + + ds = gdal.GetDriverByName("GTiff").Create(tif_fname, 1, 1) + ds.GetRasterBand(1).SetRasterColorTable(ct) + ds = None + + with gdal.Open(tif_fname) as ds: + with pytest.raises( + Exception, + match="Cannot create JPEG compressed overviews on a raster with a color table", + ): + ds.BuildOverviews("NEAREST", overviewlist=[2], options=["COMPRESS=JPEG"]) + + ############################################################################### # Make sure that 16bit overviews with JPEG compression are handled using 12-bit # jpeg-in-tiff (#3539) diff --git a/frmts/gtiff/gt_overview.cpp b/frmts/gtiff/gt_overview.cpp index 38ca89e434c2..980dd544076c 100644 --- a/frmts/gtiff/gt_overview.cpp +++ b/frmts/gtiff/gt_overview.cpp @@ -561,8 +561,19 @@ CPLErr GTIFFBuildOverviewsEx(const char *pszFilename, int nBands, papoBandList[0]->GetRasterDataType() == GDT_UInt16) && !STARTS_WITH_CI(pszResampling, "AVERAGE_BIT2")) { + // Would also apply to other lossy compression scheme, but for JPEG, + // this at least avoids a later cryptic error message from libtiff: + // "JPEGSetupEncode:PhotometricInterpretation 3 not allowed for JPEG" + if (nCompression == COMPRESSION_JPEG) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Cannot create JPEG compressed overviews on a raster " + "with a color table"); + return CE_Failure; + } + nPhotometric = PHOTOMETRIC_PALETTE; - // Should set the colormap up at this point too! + // Color map is set up after } else if (nBands >= 3 && papoBandList[0]->GetColorInterpretation() == GCI_RedBand && From c308cf3d9a59c9cdf2916585de273cdf54ab9bb4 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Fri, 28 Jun 2024 20:28:02 +0200 Subject: [PATCH 0283/1119] gdallocationinfo: avoid extra newline character in -valonly mode if coordinate is outside raster extent (3.9.0 regression) Fixes #10336 --- apps/gdallocationinfo.cpp | 7 +++- autotest/utilities/test_gdallocationinfo.py | 45 +++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/apps/gdallocationinfo.cpp b/apps/gdallocationinfo.cpp index d317e2599b5c..72599f0ef1d2 100644 --- a/apps/gdallocationinfo.cpp +++ b/apps/gdallocationinfo.cpp @@ -432,7 +432,12 @@ MAIN_START(argc, argv) osXML += "Location is off this file! No further details " "to report."; else if (bValOnly) - printf("\n"); + { + for (int i = 1; i < static_cast(anBandList.size()); i++) + { + printf("%s", osFieldSep.c_str()); + } + } else if (!bQuiet) printf("\nLocation is off this file! No further details to " "report.\n"); diff --git a/autotest/utilities/test_gdallocationinfo.py b/autotest/utilities/test_gdallocationinfo.py index 2b8c80cd7a2e..0b64f4c19333 100755 --- a/autotest/utilities/test_gdallocationinfo.py +++ b/autotest/utilities/test_gdallocationinfo.py @@ -254,3 +254,48 @@ def test_gdallocationinfo_echo(gdallocationinfo_path): strin="1 2", ) assert "1,2,132" in ret + + +############################################################################### +# Test out of raster coordinates + + +def test_gdallocationinfo_out_of_raster_coordinates_valonly(gdallocationinfo_path): + + ret = gdaltest.runexternal( + gdallocationinfo_path + " -valonly ../gcore/data/byte.tif", + strin="1 2\n-1 -1\n1 2", + ) + + ret = ret.replace("\r\n", "\n") + assert "132\n\n132\n" in ret + + ret = gdaltest.runexternal( + gdallocationinfo_path + ' -E -valonly -field_sep "," ../gcore/data/byte.tif', + strin="1 2\n-1 -1\n1 2", + ) + + ret = ret.replace("\r\n", "\n") + assert "1,2,132\n-1,-1,\n1,2,132\n" in ret + + +def test_gdallocationinfo_out_of_raster_coordinates_valonly_multiband( + gdallocationinfo_path, +): + + ret = gdaltest.runexternal( + gdallocationinfo_path + " -valonly ../gcore/data/rgbsmall.tif", + strin="1 2\n-1 -1\n1 2", + ) + + ret = ret.replace("\r\n", "\n") + assert "0\n0\n0\n\n\n\n0\n0\n0\n" in ret + + ret = gdaltest.runexternal( + gdallocationinfo_path + + ' -E -valonly -field_sep "," ../gcore/data/rgbsmall.tif', + strin="1 2\n-1 -1\n1 2", + ) + + ret = ret.replace("\r\n", "\n") + assert "1,2,0,0,0\n-1,-1,,,\n1,2,0,0,0\n" in ret From 294298d34a5aaeb5509a2a53a56e9e9a8bef7325 Mon Sep 17 00:00:00 2001 From: drons Date: Tue, 25 Jun 2024 09:39:57 +0300 Subject: [PATCH 0284/1119] MITAB: Add UTF-8 encoding --- ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp b/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp index cce88646f5c3..5a7800c73999 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp @@ -629,6 +629,7 @@ static const char *const apszCharsets[][2] = { {"CodePage869", "CP869"}, // DOS Code Page 869 = Modern Greek {"LICS", ""}, // Lotus worksheet release 1,2 character set {"LMBCS", ""}, // Lotus worksheet release 3,4 character set + {"UTF-8", "UTF-8"}, {nullptr, nullptr}}; const char *IMapInfoFile::CharsetToEncoding(const char *pszCharset) From 7cc2b29b79f49c23292e3bd7776836ec42c869b2 Mon Sep 17 00:00:00 2001 From: drons Date: Tue, 25 Jun 2024 10:03:06 +0300 Subject: [PATCH 0285/1119] MITAB: Disable table fields "laundering" for non-neutral charset --- ogr/ogrsf_frmts/mitab/mitab_miffile.cpp | 2 +- ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp | 3 ++- ogr/ogrsf_frmts/mitab/mitab_utils.cpp | 10 ++++++---- ogr/ogrsf_frmts/mitab/mitab_utils.h | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp b/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp index f548e33cc64c..422daadad205 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp @@ -998,7 +998,7 @@ int MIFFile::WriteMIFHeader() if (strlen(GetEncoding()) > 0) osFieldName.Recode(CPL_ENC_UTF8, GetEncoding()); - char *pszCleanName = TABCleanFieldName(osFieldName); + char *pszCleanName = TABCleanFieldName(osFieldName, GetEncoding()); osFieldName = pszCleanName; CPLFree(pszCleanName); diff --git a/ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp b/ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp index 3131960cfb0c..79d28e6b7560 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp @@ -1189,7 +1189,8 @@ int TABFile::WriteTABFile() if (strlen(GetEncoding()) > 0) osFieldName.Recode(CPL_ENC_UTF8, GetEncoding()); - char *pszCleanName = TABCleanFieldName(osFieldName); + char *pszCleanName = + TABCleanFieldName(osFieldName, GetEncoding()); osFieldName = pszCleanName; CPLFree(pszCleanName); diff --git a/ogr/ogrsf_frmts/mitab/mitab_utils.cpp b/ogr/ogrsf_frmts/mitab/mitab_utils.cpp index 9cd6b1f45bd1..57abdce5d57a 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_utils.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_utils.cpp @@ -455,7 +455,7 @@ char *TABEscapeString(char *pszString) * * The returned string should be freed by the caller. **********************************************************************/ -char *TABCleanFieldName(const char *pszSrcName) +char *TABCleanFieldName(const char *pszSrcName, const char *pszEncoding) { char *pszNewName = CPLStrdup(pszSrcName); if (strlen(pszNewName) > 31) @@ -481,6 +481,7 @@ char *TABCleanFieldName(const char *pszSrcName) // It was also verified that extended chars with accents are also // accepted. int numInvalidChars = 0; + bool bNeutralCharset = (pszEncoding == nullptr || strlen(pszEncoding) == 0); for (int i = 0; pszSrcName && pszSrcName[i] != '\0'; i++) { if (pszSrcName[i] == '#') @@ -493,9 +494,10 @@ char *TABCleanFieldName(const char *pszSrcName) } else if (!(pszSrcName[i] == '_' || (i != 0 && pszSrcName[i] >= '0' && pszSrcName[i] <= '9') || - (pszSrcName[i] >= 'a' && pszSrcName[i] <= 'z') || - (pszSrcName[i] >= 'A' && pszSrcName[i] <= 'Z') || - static_cast(pszSrcName[i]) >= 192)) + (!bNeutralCharset || + ((pszSrcName[i] >= 'a' && pszSrcName[i] <= 'z') || + (pszSrcName[i] >= 'A' && pszSrcName[i] <= 'Z') || + static_cast(pszSrcName[i]) >= 192)))) { pszNewName[i] = '_'; numInvalidChars++; diff --git a/ogr/ogrsf_frmts/mitab/mitab_utils.h b/ogr/ogrsf_frmts/mitab/mitab_utils.h index 72ae7b4c879b..a2f81756f8ac 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_utils.h +++ b/ogr/ogrsf_frmts/mitab/mitab_utils.h @@ -54,7 +54,7 @@ char **TAB_CSLLoad(const char *pszFname); char *TABEscapeString(char *pszString); char *TABUnEscapeString(char *pszString, GBool bSrcIsConst); -char *TABCleanFieldName(const char *pszSrcName); +char *TABCleanFieldName(const char *pszSrcName, const char *pszCharset); const char *TABUnitIdToString(int nId); int TABUnitIdFromString(const char *pszName); From b2082e9193a7692a00c9dbad9248f4f0e59516d8 Mon Sep 17 00:00:00 2001 From: drons Date: Wed, 26 Jun 2024 01:13:14 +0300 Subject: [PATCH 0286/1119] MITAB: Upgrade file version with UTF-8 charset --- ogr/ogrsf_frmts/mitab/mitab_miffile.cpp | 4 ++++ ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp b/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp index 422daadad205..71ace7ab3a93 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp @@ -1946,6 +1946,10 @@ int MIFFile::SetCharset(const char *pszCharset) { m_poMIFFile->SetEncoding(CharsetToEncoding(pszCharset)); } + if (EQUAL(pszCharset, "UTF-8")) + { + m_nVersion = std::max(m_nVersion, 1520); + } return 0; } diff --git a/ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp b/ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp index 79d28e6b7560..db60eaad21a6 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp @@ -1770,6 +1770,10 @@ int TABFile::SetCharset(const char *pszCharset) { m_poMAPFile->SetEncoding(CharsetToEncoding(pszCharset)); } + if (EQUAL(pszCharset, "UTF-8")) + { + m_nVersion = std::max(m_nVersion, 1520); + } return 0; } From fbca4fd9419518c9c14c58c9e5188a2fd5c9bcba Mon Sep 17 00:00:00 2001 From: drons Date: Wed, 26 Jun 2024 01:14:26 +0300 Subject: [PATCH 0287/1119] MITAB: Add test with UTF-8 encoded files --- autotest/ogr/data/mitab/utf8.mid | 3 +++ autotest/ogr/data/mitab/utf8.mif | 31 +++++++++++++++++++++++++++++++ autotest/ogr/ogr_mitab.py | 28 +++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 autotest/ogr/data/mitab/utf8.mid create mode 100644 autotest/ogr/data/mitab/utf8.mif diff --git a/autotest/ogr/data/mitab/utf8.mid b/autotest/ogr/data/mitab/utf8.mid new file mode 100644 index 000000000000..8a99fb046564 --- /dev/null +++ b/autotest/ogr/data/mitab/utf8.mid @@ -0,0 +1,3 @@ +"Значение Ð","Значение Б","Значение Ð’","Значение Г","Значение Д" +"Значение 1","Значение 2","Значение 3","Значение 4","Значение 5" +"Полигон","Синий","Заливка","Ра Б б","ЪЫÐЩ" diff --git a/autotest/ogr/data/mitab/utf8.mif b/autotest/ogr/data/mitab/utf8.mif new file mode 100644 index 000000000000..55f4f1eb9697 --- /dev/null +++ b/autotest/ogr/data/mitab/utf8.mif @@ -0,0 +1,31 @@ +Version 1520 +Charset "UTF-8" +Delimiter "," +CoordSys Earth Projection 8, 1001, "m", 39, 0, 1, 7500000, 0 Bounds (-749281.53901, -10002137.4978) (15749281.539, 10002137.4978) +Columns 5 + Поле_Ð Char(10) + Поле_Б Char(10) + Поле_Ð’ Char(10) + Поле_Г Char(10) + Поле_Д Char(10) +Data + +Point 7404648.72 6144520.22 + Symbol (35,16711680,12) +Pline 4 +7404638.32 6144512.27 +7404646.55 6144515.77 +7404653.33 6144520.94 +7404657.51 6144525.21 + Pen (2,2,65280) +Region 1 + 6 +7404649.37 6144522.85 +7404646.78 6144518.96 +7404642.44 6144519.88 +7404642.59 6144522.85 +7404645.79 6144523.76 +7404649.37 6144522.85 + Pen (1,2,0) + Brush (2,16777215,16777215) + Center 7404645.9 6144521.36 diff --git a/autotest/ogr/ogr_mitab.py b/autotest/ogr/ogr_mitab.py index cf00366e0dd9..b913706c0db4 100755 --- a/autotest/ogr/ogr_mitab.py +++ b/autotest/ogr/ogr_mitab.py @@ -1980,7 +1980,7 @@ def test_ogr_mitab_45(tmp_vsimem, frmt, lyrCount): # Test read MapInfo layers with encoding specified -@pytest.mark.parametrize("fname", ("tab-win1251.TAB", "win1251.mif")) +@pytest.mark.parametrize("fname", ("tab-win1251.TAB", "win1251.mif", "utf8.mif")) def test_ogr_mitab_46(fname): fldNames = ["Поле_Ð", "Поле_Б", "Поле_Ð’", "Поле_Г", "Поле_Д"] @@ -2268,6 +2268,32 @@ def test_ogr_mitab_tab_write_field_name_with_dot(tmp_vsimem): ds = None +############################################################################### + + +@pytest.mark.parametrize("ext", ["mif", "tab"]) +def test_ogr_mitab_write_utf8_field_name(tmp_vsimem, ext): + + tmpfile = tmp_vsimem / f"ogr_mitab_tab_write_utf8_field_name.{ext}" + ds = ogr.GetDriverByName("MapInfo File").CreateDataSource( + tmpfile, options=["ENCODING=UTF-8", f"FORMAT={ext}"] + ) + lyr = ds.CreateLayer("test") + lyr.CreateField(ogr.FieldDefn("地市", ogr.OFTInteger)) + f = ogr.Feature(lyr.GetLayerDefn()) + f["地市"] = 1 + f.SetGeometryDirectly(ogr.CreateGeometryFromWkt("POINT(2 3)")) + lyr.CreateFeature(f) + with gdal.quiet_errors(): + ds = None + + ds = ogr.Open(tmpfile) + lyr = ds.GetLayer(0) + f = lyr.GetNextFeature() + assert f["地市"] == 1 + ds = None + + ############################################################################### # Test read text labels with local encoding from mif/mid file From f60a21a5bb3dead69966f30eb0769c94de48a933 Mon Sep 17 00:00:00 2001 From: drons Date: Wed, 26 Jun 2024 23:34:50 +0300 Subject: [PATCH 0288/1119] MITAB: Add 'STRICT_FIELDS_NAME_LAUNDERING' creation option --- ogr/ogrsf_frmts/mitab/mitab.h | 5 +- ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp | 5 + ogr/ogrsf_frmts/mitab/mitab_miffile.cpp | 12 ++- .../mitab/mitab_ogr_datasource.cpp | 14 ++- ogr/ogrsf_frmts/mitab/mitab_ogr_driver.cpp | 6 ++ ogr/ogrsf_frmts/mitab/mitab_tabfile.cpp | 13 ++- ogr/ogrsf_frmts/mitab/mitab_utils.cpp | 92 +++++++++++-------- ogr/ogrsf_frmts/mitab/mitab_utils.h | 3 +- 8 files changed, 107 insertions(+), 43 deletions(-) diff --git a/ogr/ogrsf_frmts/mitab/mitab.h b/ogr/ogrsf_frmts/mitab/mitab.h index 83699f7b85f8..5a61a0173006 100644 --- a/ogr/ogrsf_frmts/mitab/mitab.h +++ b/ogr/ogrsf_frmts/mitab/mitab.h @@ -87,6 +87,7 @@ class IMapInfoFile CPL_NON_FINAL : public OGRLayer GBool m_bBoundsSet; char *m_pszCharset; + bool m_bStrictLaundering = true; std::set m_oSetFields{}; TABFeature *CreateTABFeature(OGRFeature *poFeature); @@ -200,6 +201,7 @@ class IMapInfoFile CPL_NON_FINAL : public OGRLayer void SetEncoding(const char *); const char *GetEncoding() const; + virtual void SetStrictLaundering(bool); int TestUtf8Capability() const; CPLString NormalizeFieldName(const char *pszName) const; /////////////// @@ -403,6 +405,7 @@ class TABFile final : public IMapInfoFile int WriteFeature(TABFeature *poFeature); virtual int SetCharset(const char *pszCharset) override; + virtual void SetStrictLaundering(bool bStrictLaundering) override; #ifdef DEBUG virtual void Dump(FILE *fpOut = nullptr) override; #endif @@ -906,7 +909,7 @@ class MIFFile final : public IMapInfoFile /* { return m_poMAPFile->GetHeaderBlock()->SetProjInfo( poPI ); }*/ virtual int SetMIFCoordSys(const char *pszMIFCoordSys) override; virtual int SetCharset(const char *pszCharset) override; - + virtual void SetStrictLaundering(bool bStrictLaundering) override; #ifdef DEBUG virtual void Dump(FILE * /*fpOut*/ = nullptr) override { diff --git a/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp b/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp index 5a7800c73999..ae6909b9f128 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_imapinfofile.cpp @@ -684,6 +684,11 @@ void IMapInfoFile::SetEncoding(const char *pszEncoding) SetCharset(EncodingToCharset(pszEncoding)); } +void IMapInfoFile::SetStrictLaundering(bool bStrictLaundering) +{ + m_bStrictLaundering = bStrictLaundering; +} + int IMapInfoFile::TestUtf8Capability() const { const char *pszEncoding(GetEncoding()); diff --git a/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp b/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp index 71ace7ab3a93..489c21717b07 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_miffile.cpp @@ -998,7 +998,8 @@ int MIFFile::WriteMIFHeader() if (strlen(GetEncoding()) > 0) osFieldName.Recode(CPL_ENC_UTF8, GetEncoding()); - char *pszCleanName = TABCleanFieldName(osFieldName, GetEncoding()); + char *pszCleanName = + TABCleanFieldName(osFieldName, GetEncoding(), m_bStrictLaundering); osFieldName = pszCleanName; CPLFree(pszCleanName); @@ -1953,6 +1954,15 @@ int MIFFile::SetCharset(const char *pszCharset) return 0; } +void MIFFile::SetStrictLaundering(bool bStrictLaundering) +{ + IMapInfoFile::SetStrictLaundering(bStrictLaundering); + if (!bStrictLaundering) + { + m_nVersion = std::max(m_nVersion, 1520); + } +} + /************************************************************************/ /* MIFFile::GetSpatialRef() */ /************************************************************************/ diff --git a/ogr/ogrsf_frmts/mitab/mitab_ogr_datasource.cpp b/ogr/ogrsf_frmts/mitab/mitab_ogr_datasource.cpp index a3c5b254af00..9d5a3f19921d 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_ogr_datasource.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_ogr_datasource.cpp @@ -137,7 +137,8 @@ int OGRTABDataSource::Create(const char *pszName, char **papszOptions) IMapInfoFile *poFile = nullptr; const char *pszEncoding(CSLFetchNameValue(papszOptions, "ENCODING")); const char *pszCharset(IMapInfoFile::EncodingToCharset(pszEncoding)); - + bool bStrictLaundering = CPLTestBool(CSLFetchNameValueDef( + papszOptions, "STRICT_FIELDS_NAME_LAUNDERING", "YES")); if (m_bCreateMIF) { poFile = new MIFFile(this); @@ -158,7 +159,7 @@ int OGRTABDataSource::Create(const char *pszName, char **papszOptions) } poFile = poTabFile; } - + poFile->SetStrictLaundering(bStrictLaundering); m_nLayerCount = 1; m_papoLayers = static_cast(CPLMalloc(sizeof(void *))); m_papoLayers[0] = poFile; @@ -312,6 +313,14 @@ OGRTABDataSource::ICreateLayer(const char *pszLayerName, const char *pszEncoding = CSLFetchNameValue(papszOptions, "ENCODING"); const char *pszCharset(IMapInfoFile::EncodingToCharset(pszEncoding)); const char *pszDescription(CSLFetchNameValue(papszOptions, "DESCRIPTION")); + const char *pszStrictLaundering = + CSLFetchNameValue(papszOptions, "STRICT_FIELDS_NAME_LAUNDERING"); + if (pszStrictLaundering == nullptr) + { + pszStrictLaundering = CSLFetchNameValueDef( + m_papszOptions, "STRICT_FIELDS_NAME_LAUNDERING", "YES"); + } + bool bStrictLaundering = CPLTestBool(pszStrictLaundering); if (m_bSingleFile) { @@ -376,6 +385,7 @@ OGRTABDataSource::ICreateLayer(const char *pszLayerName, } poFile->SetDescription(poFile->GetName()); + poFile->SetStrictLaundering(bStrictLaundering); // Assign the coordinate system (if provided) and set // reasonable bounds. diff --git a/ogr/ogrsf_frmts/mitab/mitab_ogr_driver.cpp b/ogr/ogrsf_frmts/mitab/mitab_ogr_driver.cpp index 5bdfa0ab687e..d3917f1ef328 100644 --- a/ogr/ogrsf_frmts/mitab/mitab_ogr_driver.cpp +++ b/ogr/ogrsf_frmts/mitab/mitab_ogr_driver.cpp @@ -228,6 +228,9 @@ void RegisterOGRTAB() "description='Friendly name of table. Only for tab " "format.'/>" // See // https://support.pitneybowes.com/SearchArticles/VFP05_KnowledgeWithSidebarHowTo?id=kA180000000CtuHCAS&popup=false&lang=en_US + "

    9rnv`dARKGAew`p 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%0APAv35&*1?=c?fzQZ9V*9!`Z`LL zZbZG`=)4vj6QU=~7CF2~lO4XIhY9Ceg;CbL&_;{$Yj2{YUqq5cuQni;sRR#Je`Op) zJ}&Y(QyP|NR9U=8{5!lAR~q|jSSb6UwiEo<6^*=zxUaK`5gDBj+SfNpLH@1;(T9@Q zrcU<7JW&&>amoz*CUyC|J85_{%^2kgv4s@f|K^|8yF>!T%pveiAF(|$%D||0##r+k z@t=wJ?SwjVwrUSz;{GH`kAxW4<+JXSk}@K6cjqN8Ny(0bf)a|^19r*SWg*kuiyEDp zV5;tybC!SUsUoSeF2D&=*$({qoU)1_z~z2CP=*z0)q!`=1A*(}ZI8K|Rj#9sx0IIK zSCa5KdO*J6<))L1l_%WKxh!mhYrakjIM!$%H#>5T@6-}|q~FE5!u`&csm65pxlm&V z#l1dLQCu`eQ?;E$ZWH<(+d?w#a7X8lJa{42657S6e zLc~t~e$Kd8fN@XGV*fWK4_zoaPB+GZl+#;Q#-H`fJ%@R2RuwogRG}$Y%6HYa@f=(o zjs!$jZwGm~LMwoWD-PAOV{$chGlMI3BP^sv=1UVEaj0?DQcV2Kl|9S-3rKTh0jh{` zRqy88X;2eyJX2(?z<7w|%z;C$s#UWy$w*#{uHwd78VuO=EyLeb|BYwEy$W*iC`aaJ z>L2YppRRe-DTLSNGypN4i9x#PSet*5KZaGy3Eo0YIM~yySPYzY%G4Z8IJ7+HA~MW6 zq8Vqh*x9!dShZb_Ic4)f$K19%=igA2c~&A|+y9ytoL=u^`BPRvf{du-Uq+q$hOEsO zFqVe0hD0x{tmx?=v}t~3(d3pCmg12aImN_9(6JB*{Y&|M8!2564LDKpH3p{AHkIh1VLLqXf;_8S7T`9`w5-mXb#Px;#@4I(&|33h@4VKJ+rQ>QCw14WT0s z7M^JDKyD4Iq#53#G#jY64puT$czLc*qZ|@cznN%fswbRV^%#&WaelbrULNn_mlxv0 z0$FD|zP~nY@zyx;PLyHH&Jct++Vk%ZS28<Tf>X;po(|n*&{D)tp>C!>qn-Wy`z*jWwy| zqJAzscid{d=O=nYT=sQZ>KXo39)l`D*AdwWC?hKD#Ho!ev)IqsrK0(Itn9mA9J(pw zH0-Ds3aGeO>?1-A$ymvfmr~x{HUw16}u~Sar$3#jwA*su8STf62 z@OZ=Dm9W)|AJwPnk|JjF(cMllj&jO2$x(kwUnXM_(t6sRDgcz>^BOt^u+URc&{Z1G zk*!t}g*K*zfh%V3SKGEDpe06J<-OE;*6g33@rjX3lFQBz`a{S*glCzbn6s#DjEaPQ znzBy?wA%C?!9cvKilzi&0ybg(XB3czZrTaQW&7o~tku8W%Pmhz^!{EF-%$7apYIt8 zGR#9QeoC8?8Yg6{n|xE=^|U|YR0~_Kj$%U$P=^YV`a$B-%M;K8-Am#k9bNP4SFdH= zfJT=uQ$u0!i4;Mw-j$rrk{)ifE;@6cJD|Y0%Gr-PpmPi#xgpZ3Qpad4dZ}Z7$8B4f z1g+XUU0huPu6#-M)2QU^fjI(Obh;zIl^A4hSH=f52V(FRb=9EV{F{nzMU-6_aj$~2 z#GmIn`I^l)RL^j#r<&z;f<*S}#|8q6$*hI9_rDe)n4&dvu;3xph+exmD3uEQTfeMU zR<{(Lq|1i*^QI&gkJ@e41;_rCmJi#7qvw)}Q7;kNxCBe4n%5G(+?J0=fmoZ=bXrpznw^L*rKRKp zP^XNvuy9-wC{)L7@^vZqzYaow!8;Kt4RvVm{ZRaG#DSlLHYmU@#@Q78_;c=uswHtU zN?N3&jr?={Fa2yLaKIKp;J>JIGzECkD&_Awp+nw+$@)CbGr_WH`# znP>L(AsQ!pV`Bik+irh5GPX-m7_TM=`;p~0oSkT=?)pRq(MP?WB=l!im$ zcI$uTP?t;~J1~#*;BvmwEtsD+u?}3K2MOjDvg8Qt>b$~}EI_;13qb-y_n!{#$Tc7J z2(LIaz!Yw*sW;(DTPmSmmFLZTpK!-wT>1vDD-V5?DPw53$iucJZKlyM)E*9a%jOEkfq%5JC7I5*8tmcT|@ zn79crFOzog(%&K&_s1=iqh$4d$b1KT34<<+tv(rr^p>~5MRZ)>97EFT5hdxJ1rg*Y zL3*r8brd!JlrRETJ1T)@a&5jfZZIX5J0H$t5^a~>`RIB|phL5>Fh20B+_c!KjXP^Q^rK#eIInk zt3>=`Hm+Sea%tFXnf|5BI~6FacafdbU=d$+AN}Il)1@J#xALByvo8LnY7IAmfIaLe zCjyjScPx{$n`O05T8orL5G|8|hGu8Xi?n3t84oykVU#^utsMU}aUeJQZb>J3{KQ66 zCxKJal7imHcjd6(G5p6rBQB)5NaHwI%W2%i{!{l;1*ru=Fr{K1Ue|Yj2mjY?*YIIK z(KV@GKlAj1q~IVKm{%{?bPeCR)sleGyyTXj;XQE;^ZhU8pV$U2n?-c{^b$0y2tv3S zURXXK{Ix2p9 zHf}Oh=9uS{$rv_=OX8{w(b`d3`PzHg8QgF-&4OLF5vf~^76Qk>0`Gn&jq{1hogM(t z0O03&V*J|SZ+rMePqO8e87_Du6V*HaeR#!ILNTan&;zOtJYfJ?qIW|A)Q*x84ehJD zB+pJdk**Sl?jin{e9LeztfE zYatQYLg7@XOHl$}k?o_3F$d0_Lj91NC^a*Y%B@p<8&m(wE{=w+DDzk-afw=kwm>0? zi;By68?_lW3MS4uf{O`v#${wa%sC1F$jj!FqMr8Q=-GMhYH-Df^zE5LZe-D1ON%Ql zldHXWqKU0K1W*QqQsU9Wmh?LM`zoCIY93 zdSqlzB7{JQ8;hjZJsQ@R8{6UC57ba(P^@&VlNROC5hZo7Q+bTuToCPgxKI{8JTKn8 zy`JB-Ba{94HN4TuK^TfGJNwkq%(M#Y`edhEfLPO@DMQqW4(*D-k1DfnV-(^SP2neJ zWtvw3cS6R6gC?20Rn{hsd?pJ#n8c96X%XkX>W&W%M%=iIErHn-+DxNSuV&CXu9MZY zPCh{>ESvLai6mgg^Pt!wIT;Giz88t@|jLVXHkm})t7SvzfC8wtIp}6#WySv_XnqMH6b|R&UYVFVp}>ly z4eX3lZNm;_$7yE@k@3)ocI9VW8XhHiKJp((ghKrhB2l05)w~JGTlS7=h|VR>jlG!C z0*}o8d|h=JX*Ua3JG2s!;}cGCRjv68ogzR<;hM-E!9sA}b}!a?KPbV6tQ6nl^wOCSZb4`B+iAM=S*9s6eP-n+_LU6x~0 zy)T~y8HP$$B#)KaNEHBFF9>d(o*XB_s|6d<^LxAfV)bG{*{@Tgt4}n-B-6X8%ODb=a3w~PSdO-?UW$EeL8Z2mXmgJPs ze%om!oWF)OlEE_Gv&J{k%B$T4Q~I&?mOGXBQ2;=K3D5KG-!EavDj3;JkAbUF&!`DX zfu=l3e6CR^{#W|=Wa!a!wegVedRB93xy)^2!j2gXnAg>lUNdc9D^QfBQk)sRywHOcOi>23B32nN;LB=X| z`Q$OP&FlQuHyxVfZ^K;4czSceN5`%1D?-!6ot;qO|7tE`f7 z^rTaR?%&x(!rz+kye#+eQHGi8sGx-5w!UOKg_k{$CqJ>JR$(c%eMRCKKnHGxJOVRq zSG0i?|5J2IV)-3WkQbS?;h&1#5otruuUWyd@n$sAY4dv+RlIxpdwQc48?w z@(abYwWKX0x8@-m2puXIxzjcHf+y1OwbdiXXlTNFAo8qwr`&qrrIV5Q_biG06Vnp5 zO+gydaXr;y@y{g0VfxistZawg94C)qUDsx26YZ;gMy+eWXpHrI?T-AkEbc5dQsA?y zQ{+wIVc*xyxuk`=C=YE!jUSGnNg@0kPE%l#P4Y^R*mPi{B!kqA1s>zM$}E0=lf77q zCi$jJ3QnEa7kmilw&>{U$Rq0Txk(#orUN(n1FZrLG!odiU4?5KQ9F@iV! z#AT1q%X;`%Q=`gO#;Lm2(BYr#L6~irgn%mh&rgDiyH_?`Cw;Va9Z5~g88Z+2+$)B% z$g9PtiVIgsDS^{Q8~~X9IRV{>-&F0o#c1ejM%?>ZQywhLdCII;q-q7uHZHd5Si~1B z^2%&4$X@)d=Zucswr{#j8_>Xwz}E`-m~+8;jXJ;`X3&->IPkrW2rws-OR;j{+9K+%IT5PnF}p<(Ees^Jgxrg8{L2f zO{Gp1&h|SHIdfn^;*dB|Hz9p2oPUNgNTFqRp8)#Z-ZNnm3;Gt}0wd z4dsiWTAjfeOgCFdz}W|}pl)@u6DnZud8UpZ!Nnuc@FR=36zq)FeOahyr+PJ1f%JW> zC-QhcEI%h~WrY)#phFV{v97lSkbwl1#bI(b(L>3{jTYxoX=MPJu7$Jn(vuspt_Eit zjM)>O)N~}QbIA}H*chS}bCjE7qTiIJM+2Ghf)9Th0v=$d%+dI!6QGO@!%mxq$ELX7V36N^b{ zx@Z^A&LK|LkL3pt6b{M1n22* z`TW@1;yLo?_sOx&8xL6M$Nf5ba{-#=fOhIp?L=Gzf00;dNxukC#VH2I?q<(NPsvYr z`?p$#8lYLjZ9)-jFuhRFxLBhA{Aqd?yZ-$8vhLga;ox|%;$50)pcaO*$OuV@d4%*v zK^#Yn+YvbB3mzT!Fa}0q3p%tMJ=TJx1vKHKs{rqjNxbJ0$7c~6qe_`2`6g5Ku~+D+ zjMmzXDZBzb5&fT~B+KZp_n+&D>aQ{O2moN1b0yAndA$w4Vg%l(8X+iFes*+F>yw=B zv?8p=kOp?$^K-4;PmgRq9e1kA<~UiWC`Lz&>af^KEUK~DD>+6(R6>B{x=w;8C$?$Y zpWHhP`sZKHPZfV`xYvEf_;lAI0Pq=6;+3f6lC(B7guZR!_mpwLxI%YC$;mUVlN(1! zVIMneESsrmVP!=Z>v_p0llrh9xrf8^L43U9{w~fJ>g_sBF&6&AuEX=Fr1Z$OO?N{L zS}C>Mm>7jfe3NzjciJx*g{qLgxKW-PSJiChF-#rVfB4ZXJ(|V+4Y>3bkc;&F6LR}} zqd$~)N-B~{ls8cI{JeKld&oA4D}5_5es^6gCH1(ycqFki;WCc1)-ybXH{VX^v$;h? zdk*c!SgDKqm&S=Bzs>KGaoF;2m}o^*%|T2~NT_uD%-rTskN-u}X{^$UFYNB|u{=OW zgty(H!3psXYsY*DEk&6)Y&$;rKHl^5u6~J~5Zi%{e}g=3&gO_hL!P9=fMC&%!spZZ z<@`uRWtBAl>lG^=(I*BGBzbYs@p_^HCc1NIESa1o>2e4o<@G9=58un}2&H*QxcP`3 z-j#_DED}j~q#~k8zBmw^Iyew!us+k_*r4o5l4a11x# zkQ)Bvmp_h_nJbUA@L#UN>>j5We>w?zJ6aRU{sZA)>Vl3CO656rGK-Z}9a9r~I@A0G z8MnBL=_7xv(|Bvf{meY&&&SV&tkPRV(dHh;M7Pv&kwzgM{mK=$B6o{AOjrZ^WH})q zbFjT0;M1_FS{?voO+aIrrhadt0V1pO8cB!f(%1aXN}ln&MLUM2#AlzxW_*MGWr(gXAoU)OVc80L=j)+R^4rVVgt*~~=dmIgfhhwRl5aLAL?1Y> zd}zPXXgZ^OZt2M6*X_q&)8m@<*kfO&dRlnK#+}6W$XMlC7QchWGxg@}@Qe8DleXT4 z0;LGDWI3@yb}TY$j+$oy(^4M|i{7t#rrqHihU5d{qU7`_Ya0m6yqKD5Wx27KnJx(V zgxv|MN^xZQv?~AB1DXSDMzS*4PmavMfAIuSxonTVCSlw|Vj^cL80}d`>)%i3vnl-? z^2iugfSECExysS!$|@t<5&2{Rru6)YFJMe$^D6&lk`kCzo0)7uo%kok&ty;%VK?&L z(R)PHG9Hg>juRu8u-y12W8o;y9rN=sr1C}a2{~Jg@89QVf^uVBeYM|`cpA$Z^W%_( zP`?wn+m@D`vBDp9uy$$qVKa*pbib<{S3)sqtXf@!v*UQwXTpk$K>;al_2%3L;amp@ zj91EF%d-3RyWawz|Myw|bREN4PEv3y=YHEg6qEB!h9+7AyB6-RMLbf0`ce|!(TDjY z4?j|LIU;_6`iQcb$+Y+yRb3mQ2Sv(JN%&#Hk%%a{rFA=~EEMLR%Mbc8mozR1^FcNM zmbKUKiy{HeZV1(L;(bjp96hC$$KlEj*)etak8r7%Nc!jyRaj-!>yYupygEHm^Wv{S z2GU})2kmMFpP41X$7sJ?^~4eOECsa3t{S{K;3v1DwAplB)U<<#x!QA4_Yt?Q+fqea%FK#dW7@QYh}jg zFG~LO%y->t%DBv5>D?uUDPq<%uwa<9DyOCE;UN4CA7s-zi%=%{4+_PVjRjnLlq%fI zooNsRY1f7Coa2Pp2{P05k_rjS|Ah}q9b3Z9U^?e{eRT*bpC$jIlPZ(@itniu8Q0ZA z7%E=xYTYw3W3`iG>#c7ZUol6TGj)g6g2Z?BpC%%uHRF~}iW_q6 z^D~aaA3HW?hCVf%;jcNm)ybGww;FB7&ofH zg%gnvo#-TH&F-qXi;qN}klhTDjFuKFLl7Q)(nJfD^x?QYT!Mu=^^E0*V7Xzxyz z)&spaD}*WreQDWc29@-?+UiM`>~4EPoMjYlbS(^;U1keD_-NCt&YV0X@N~V8g;I(8lYB2J8k!2RBsOvScV7#}^l4}&(n)&Elx%A-Hx z!MpVMFZ~d@?%}|PFSHzH@nMG`RaNvqjkYh%!heXgF3+kx!;N&W4}l+@9(ld zY#g^AE2ODU zh;z<;0xIE9K6>Qh)mYD-imALFJk?yv20wF_Qb9|SS*Oa>_SWn4oKrM7L{j7J^@Wbo z!dj!l#S7Xf(IaxTeH|(*9Qk8bLNGF$S%#kv_s>qu8io<|v3`7>Q+wW>Rh1!aj2F0b zRBgzC;*UL?|1{{4!OL=c+;I}P<#uv3&PcpoGsE@e7EgfVBh7^?w#lQ&=ahGf{v0<- z`RpfO+{1135k{$nD%l~`@R#f4?%&_{^(M$Itb`+>%kDzv6-1j%kp_~p6r5rHvZX1Y z!`Rb9yLrDGlsjlzTu;7s`5`!C?qD+?8Gg_RUf1&1>H3;da^U^Xxn7o~4T!c?yP2XPhk7D~Wz z$JlL1D=KgGqvz#tH>ww@ThW_mP5fW^FPRN*o`_q;cPj*Z_<~Fbtdh2ks@w$>ER1H< zPS?zcvh}(x9TRo0kt%_e(ZPL0Uf*5%5_kkkJw4gii!dCe&}tCLXgo)U^O93*byN^! z62VLn99C^#q#@~4xpaCH2S}PLW$3?=+9Q8HJ=8%xZKVGPix{ub+&&EQtQAle|9Lp? zWMOmdZWN?8;$N4M4aFsGkKTowec-<4d-~R1(%&W8W#Z^v8k2iE_Ih}kK6L*HmH%tv z*)IuyxX#HEH43#kZmA}y>J7hups{=#wrxJbt|~bIX`m41Zka><+oXV2s$=)rIXZM^ zs5E_P5b7H*9Z3JJZ1_}Rv-(e0Rejtym*`G7Was%pw3q655%#m@<3Z~*jqVzFS@!C0Yr6@^b1kMHJT@ZgWZI@vs48}=si;261yRL?Rh8LiHYR#G zG#gb%&Rtas1KI+lEjJQSj-2%8&|F95ZTy2AZ?Y{c+aZ5DFgLru&mZvEjUxgG zDlVg+~w4I0z+@8hr{+lpS4Ohq10zPRT_)0KFF*-qj2q>DSxK z{zzcF`!t1M|44319%};QWBGPZlE-z>FyZ#ulVvXdrvzQCM73WK72Z+U`e?g_w&*=6 zfwc(hkP~MD&(;iR*Miglxe=6;Ik^H{POGvHEsum~u~63ms+m!^0t$QE5FN&SMP}?X z`HDq*E$q`T6DF$bskX#$&%UC|@S2&_;xWzo%yrVG^>=Cjr1f)7&Kr@PM(0W4R$H*w z`OuV(bC+??ma^Q-VO^#PI+thm?D-4K6Kb`p@|jkms9rsbwVO6)Cih>t9#LrHgbneB z-NKrcaTnyz8W&T{C;UG(VOu{B(M@@yNlR&1r$h`lpf)G6pS$;*G)aDhldk=tJNlCO z7wNRy!}-4y#4_Sd*ropMG0rxX5y8yf)*S+qh8$BdbiydaA!nuIMmRm8<#K5*eI z@jil2A-&Y4Imp9=rES-Iwq`I22FpShlZGC8%Zlgt`em-n+WGTKV{ber23dGSz>aPt z$v*V*LrUQO1+Q9up{;jXy4Zwq_fvmw?oM#;UK2KM^`Sl%idtuDu$P-zW!+&^W_ZZr zxN3^wyr^{QkP#i)w0qg00*vx)ZHcsvvHA4UP}Y=j{xL=A={FhbIfEXr+$&3+yMWy5 z+Q4y54l0WTLPPsiq&<;I`)U^J!5NuEL{e+mg29?OarE=`wi5U<95M9s-?A=IY;XW$ za)|k_X8!n}lA4$h1>#JH%72AFlIQ{9e>+LagSWUW z{HrA8&45~gCHG`BE4d)80lm~F>RrLMZZ#042^bwODfT?Ub%n$Tl^2Wto|lD@s8or1 z6cs0@P_i}HBQ@%cHwrIR|7BMifwwdj5%yBDxf~Go&#T!doFr$k?m#X|T2oOw71}<( zMlY^rak>eL8iAK~{4T!%OfSp}f!*J>6UxL} zJhnpCEyXGu6}_4c*va~zTa={t;3FU=xcc$UJzGV6B^c)+Jnv>)j`1>pLF=FFj3K!Q zJR`S?;2MYj?%JvL;)gAmfCa8B%E;H#z7b>?yt;Ur~?gO2vxe&)OxhL<)2U4Or`#U%TE%ux5%xHVHKzk z3v@CqrN6=%%3XAojnIf{1&XP9ocOi!)h~%Cvx;DMO>lnw6yogQ6R{#}@ zijmnTH&-sq2DOP{|D-gtr2STMn8B^8U@2R?iyE=R0>t0lX0(stJTze1$uafqic5O16>((#435s~ zUI<1NphgfhhxjpC_oXb_!ezkD>p>QJXzzrZe$3v*K_L0Jt(nGrN7Hx(YV{9Q*L}~J zmPv%(5)wA{<18!dhD7z_a2EKIK1Kw=>rma%**1urTZ~9V?MlKA7W_fU7~fzGh@WJ# zELcO4cK?(fRzib6@@poyK!#Vo?F_d4uW#pi0r|{VpCWGg3k%@>L zpj+)J*y#@S-gHC2|6Gs%eQ#%f**@@344M4NzYjU)_)ot-i+zZpE+Rc3}wxk)Ar`z}4^W zy%3icxxpqnhFN~|6><3=epjt?8usl<(}2&Thz2KW!gAG!Ob_H9b&zX(&wR#*MMbEn z|NBAwen2NsH!a`)?Ay75-@?S&5N{gcArmVya8dt2oK_+j_Uatn@yF-rRC6QJQqtzn z?~kZFe;gc-o<0pZ9R`6Y@R__S|N=z zh-U7+jBY6q6PssdFULZXKf7g?-$HjEHamK2 z8{iIJ6gBUP!>W#aYto8$|7NA9Ch=1=>O+tn>p>2ijIn31>b}AO7}Xv?%m=j^q~vz~ z8QZxr?|OlYl%NVP+muI*=8FZq{oBAoHgMq8%6zP)?@$eBNjrM7eQ`-k?ek-C!YMH_ z`WHBPhGV-@Mld4}%euDsA!oA}sLyjI?)r(9ck_4-yjh#Jg$#V`vpkOuJ{b5QN7ilT z*OkxNqQ_(iSXDc!nx85zAhx`m3(PC_KZk9z3L%%fpNOa{#@cvu`W>HGh!Wdt1uz{B zDKx}Tr~93Q*EaT-J?aTw36n5E+xA+YqAGqQDb0HwAW)YK7fL9qxvMrXQRYIM^r-_h zJLSb)d>2r{`vhv0PEbzY}K&VHrNOGH{KA39a4FjUfb~LKANj=Ihl!gJ)&F+b7bg=(T`xmh%;? zY|xrQzE@<3_8sJ`ww_HvSJ+#ctNSn=c7;ZS53(sD?JD@S3D1KX4>v!e6c`B~FDU65 zMv{&GLb=uEhZ}6*1j!($qgSk|LvP7laasrM z;HRLP@*9ccN0#Mabzq9fTW4KQw~`btB{?7MK|u>E@lMbgFruxJNjvAq0QNgf(Kh;2@Ra`MUc9Sy->M*oP~cyRE^=C_++Mm_V8RPCG zfbRI7y!RRIGBdPd;Ct@7z6|qT9>uxqK}%^{#T6}?RK*}!?#7Q)Z{BbJPNpwSN+ZI9 z6)OlStcm`$-G^W}cD~Aar^lF%4EoWtS5k2WSQNitQ7tO(%_#@Dz)Q1}Cn0-hVqqid z9Gy(M5X(hSSR#mGhL(s)3EDO+qa%JLZBANvg5DGNmESMco6glZ#3>6|MiET@*HDvQ zxWk=i9zFYlV7|7D&~-2QX&U<2HO{P`*VK$d{RxAXQgbI3>)x&b03IVv_bj6^e)JAdP^>)~tALBBkz6&M=|4CQq|Voi%=7XL}wFt$3?%dXOr zL*Yw3`h-Tk-9+A(^-DPm%hLM#FDQ=zWU_=F$_242~$t1sw zYmH%BXX1<4e)r#ExkQppQaz{FvpG{%OBaakCon)FH8Fl?9L7UGdV{T@7(ci^#J2f6!r!B( z8;Z3{?NZxqb2U_&V-h3^4aOLmFQoO!)!iuAe@|VEzaq#cQrvq9pK;L6ZFVo-CXHW< z@Kjo&-(KFg|2^6&{RYcKbK(O9h|HMh0nYG3qA{#!WTM)v-Ttzpw_+zpTV}HCjDPn8 z-fX3(3;m+s=h_0%6Q>Y=aNs23Q0;5dmQ|o(vF>8=`!pzSe38-Zc3Bn-Ea)_yLQ^UT zUA6mO#SH&8U0X2|+9)8f&YX6w-w--t_k$7E3eqh-^B#*1nbs*!`*GOZ<-UgxdSRjrWxk6>@cr0&IOB-Ob?()8Eft zT!ghze`osw;IWfX#DCf@%J}OYh3K)R=u(BZD08lT?N(vV zqpAD{iw?-1YG_fbBY=O{O0tuVC0#$#GhS|E7KYzcI$VDa6OC@^_!1*kk2=R??X{--#y2NFb=dHyj2L=PxF=?B1Exn}^}er! zLe1PQ&~29_hHYt3Evb!9G~m^LqCbdasS(qK6btLAdR<%HB|Pef_g{GyQ*)L7H|#F= zjTh~XT2b&)Vqmh-eL8iqx+vmE>$IYY$u@23@A40W;pqJ>K4L+fEFeF<$E!y^UXfT5)1NE) zDq^|o)xd9;vlt@w^yj->k*w-@Wp6=0uC^TO^#4WJIR;1iMcuw*JDJ$##I|kQHYT=h z+qP{_G_gI=#P-|&y7gAwPxn@(D(R%teV%>xUhB6yr_zxitcOz9_?;{~u`i_Aj;>HX81geUdh8&*5MjF4VSn^N-Vbteu$2a)>%tCHa810r!uGu@3P8eONi_^`We;p`?ghPS;0Iw_rmWB7iNJD!4Bax3f(NG zNh*v(CZuRow<1QV)(WolGO)>Sx$LREXT=ZeJP`I{rIoz|mhFH-h{!qa^UFtEf9%a+J2GwqYH6;$4K!dT;x53KA4 zyNR>^1Qk)C;fXx2^i@Q3{;=CqV>1$z-2Sk^o7j=DK%np?%{Y(Gs4Ggek*&xE4q;!3$|40X#m%7n}4IHrQ*Y zRy5T{Dfz!hC5CoTUAWMm+_#3WS7%CoKB~o9ubI(u+X^d3$|9^N3q==N8Y>)^voYlaOen9ToJi%N&6$kS+a#gN-6cr2S44Ab)BrVr zMhm?V-spmf09N|f2HqZj9#z`1RHTC(*I|3vHGv(US~ipa@-ydiby@}-pan6u(vG!# zO}E?bWv=X+0i8N*nZQ;6wKBVGYHFh|ohR+m0Q>tc;{Irnkl)XAoGAdUe5z&^$K&5f zzTf=AWc|JJT$zKvf&<(sK-(2tWtP!Gm#T7+Ty2nNwRK!T(wNx6Bc108G=SQr!0GLZ zqwTBCw=VVLkG63grSa~+w)`*4@yPi5;-2T!iEDcE@hYMX;}h|O&R6wR;E8E|~v}r-E>DQOi$$@c!lTbIX+!%`R2%KSuyQ-zmbamObyzW(yb5V90R2{>CE> z7qfltNTj^}Pv61qxDY-W3yB;`rx?pEUW*(DS@p!1l;E_CYI614bg|S5Jtg;FbWLJbI@06DCFAmO>&4E1HV z`b@qtLsZHl8e3)5TG=MG?;tc{y5X}T4{cj5NO;tknxg$#_xvEh=z`HtUn~qRi=o?u z!3t9n(MVor9`<`IB7r*QUSDVais!vxL=2DOJ>uR}O?V;Vu;d}2s>8xM;2BNHj2w`$ zAx`M`y$dsr7Tr{q2?X*b!j4alONVA13WYfcAc6czYv?lyQcJ09x;G0+auH`=?>>r*IW<~6$Zy`r#-D%VN#FSR12#}Kta%SXwl{h|5$yh z|I*lLF+7K%l;=~l>d$P0>}BthFp+qv%ok6#2#n+-YP1wOkF13X>W= zQW?A0s~rh|?z*z(M%KS4G`ky%7?eo;;B>ea;wG|Hc@dtLaRDMTZ4)eTp_zsO$A$XK zho$G3>uDPQ`n+~9{E)zXi%m=k)qwzJ?T>5&bN+_{|I4AjwLgo`CXY+jNNIeZ_A=5~ z4)+u3pHANpQ9#mVlb8~NWoh*b91~BgR~M#AjB+-5-9rnxt{R2nlJIZ$FJ`yK{)RPp zfK3!gimSSiMQT=zG@%LF(YofH0*g=y(*U9ZW8A9f~wNeCZH{3{kY3mDA~Yuze-D1 zE6OTbIk}`ZP>v}eNYj8YM0E4sP4EIMxT)z+`)R;r{f;QXPPqwSg*CbGC<&ThK{Gk$ zH+O@@-Icoe+qiE%ReEi04F~||q@7;7{9MIt__IIaiI zjRnyxEbZKwB4`#60J zaiTt@cOyAJMti7erF=G?!^%7X6T3+hh|C)Mi*~WJs!3B|H&}$ZAp2-32NVZ>X%9Qc zrchH+3wciFda1)}McYY(GU534qVYyi2}^+~ZKpU8-m>2Hk71|KRHG19XwX7dD*lj9 z(bd{#RCIs)k8nKGwPPPTqyPZ%2XQMQY#D+{}|yU_yIeat(ux<&TM@Je`$m=H+Sqt zItRYAQ!7J|%V^ApB>U%9t)VjjBWJmEipiO_R91*Lk+o#6zOE3Q7jiBD&IJEVAd8(?F1BLG+E-C(3r${+z3N+ zOiz)UWtIYo9j^Yny!ER@HIYo-zs}wQNmu+$Kj}Nkr>kv&nE9Wv`N*&wn&ulc$u1W2!{!DKa-1V;nwt9 z65NL=(d3L4%|rX97M*Kvp^D`?X7N01M^jJ#30MBgQ{*Z`b}D6f3T~p} z*7x8xLo5#AtQldfFqX^wA^!0l}<+NF96J{!$343})QpaIl zMxQw%PO=&t6BxWZZ1@zA$9W5)WHg&@9be ztvd;Uf{8fCeC2_u<+OGL0_NU3?mi8$XLan|b$=Q}DD$nc`pf@a&{g4Qby9t~u!1r^ z`6O8#w+tN7A`nhA9eIvCA-%E$YTf)Mxok1$2=4ay?wn$Zww4_!wfg~eOYL#Zv1_$e zg>ydsKN&cHdG-}b^4z@B)~i=<#k7K&J#0bqsc3}J3IE7VNWd}4Xe{zX+#o$zMz?%z7=uELSX@}k$;-B0UqW@)V$95{s}&t1h)lDz-i`9)mVZ(KcM${wM;o!?*ITf zK*qoDWg2xXjKTLsc)n#_6(Z;hIGsP!gZ+f*6dbYilr9LkQViug14_S0p;;?#&ZjW& z3OoqXKQ3(WO*q6?$i4n;0`u50Nw1Ij_1JZW0eX3mI;tx$5UzDgMd(1@WP-UDKYvnSQz_e!;M^9pK`cG00HJNGNb&cR;|X8< zKfOi15j)E9VPHjI7|$PKIpxnaYuRRO57Zk^|E2Ffd*c&zeO>T;(j3bxkw{2l;teeo z&>Kx5AKr0V%5buLjz-}~TzIF;jGYG1I`CI+SWk7(il5)jjXoQO1wMBZCwT2f9CJV(*XOFpe$6RiF zhqX5%90rlmp)L^vt+|{FgN#MzoA6Nf)Dfr^oOTij8Fc1Q2q;-2fr$dErkwKLd7FY3 zD1=#|iCFnaW`hMdi^Ce*AKVSO4&v0eV*FFyBU#(CybJichFwka?82s^esbgG>8ujQ z;owcXi_<|BOlB`IUG5hF%EGSO==dZXR7s|KLmusHvB&@c{4Fe7qKrNenAd9M z-qfsXa?GOqDSq5jNvW8Q@XD z_8R4qYj#zyPo748w6RhMS7;4eey-rkgucka#M@sDi#{kl3M#mM71H2oU2tzI4qR0U1ANxI!B7}Lxb3XvO29fuVP2z3z$fW3e(r0d;H5g7 zANp%)kY*hS7%NAySbWLOimDR$*1i@*^Cy~392B@K`>iry(QZeKy+X30YOIHyiL9lp z<}M;~S-qt_Pg`3c%!i%L{_Gs!fVwdTfaH7c%Px)@t_e)LO@H;YH(;RQ@nz{nn4NW} z#HtX!QCUbLY&mPD`#&xRcCm>*TTm!*#AB$Qc{L8BM{Rg)FaDns^$Lxnt z=_Cg*M=FBrj2mywnbPTCZr<#P>x-(S38qcey~7seoVe+RY?3r3-LhIe9hAh3C$)w_ zwt1wcd(qt5cJEID%VM$+o*s<`aOBSJ%>9hc9BFRuEW}aUw6(AJ{=@&B8f>?5&2IHX z!1aA1Go7fc4+rRF+s%FKuvx`ul&+VEb^>ooIujDsov+-m>q>HQ&G`Wl8tRiR!DJ3?42l^U zB#le_RNyGg0z0}xZHsa~5m3#y_*x^Yg(ch2U9IZYu5QR3%f?-eiiy@2E`ve2( zWbK<{-p`-vKt;#g@bz3nd}`5T!=LW0VcSE4gJ)04$H3xksPS|dSoW(n@9WC%evyT5 z*|1)xBQ1^OdI-OUTEG1Ds`u^6Z}x25^LYWjiJo61-uYvmFhKyoWfKPVd8zd>0gez)E_S7V*vGz1U@y({}bvFSq}hGJW^@Yj3LA^+i3_qrt?6{CeCI{2l@r!XDd=>rq#pv!0T+8CJJZ zejk=qz&oHocfLVJj%Kr!Z@joMyi504hf1%u(TM*!K62^9)_!8!oI`voaAE;Ch=|@i zFXQdleI?pe9II=fE*8qo$>~d@6rbB&vJ}Xk-7E8ovf2yhabpC)+Q-(R1IV<#vk!MS z)t_S(#=?P}bsw#9?R|mJpmYk`6~W(C2%1j*&N5hz=t@Ac2?}f$)K`*&t^Nh37NO75 z-zge#-Svu%Z*58`NF>*>RV`et^a0UzGa2=4D?jtGEd#LRB}k7KC?J;}MZ~96img?! ziw&&a20Pb-0~<4@1d*P6*|>H1IlOOtJX$g;dx7)i@g`vj7F@<*uKew%^_@;b?(P0; z_!3+`z9@jB@F6N?@%-!aG4yn9#+)TLKST>SlH8Dee7$*&=NX4v_*Vzd;}iy`OqUY< z(7W*YI$(yWHKMeXMLZ;YkOriw?CvPUDr=qC^*HwEJm(L5tIs`g%VVy$5&RnjR@+@` z>}D|Y{kQ-R8Bd4dJXzmhQzDjs zbsU28Md$$l+~-W!`Q7_)U<&J6t5=oChW7@c08soM%K#%N%`~L8pb{PQd(XIC^1JZ; zSwQV4^ev)BSGCIxUtYLkOHiU;z5X}{YsSv8sxO;wWBgR~Iw;Kv0$2*16+u(?CqG9F zQfpdF3mDM_39xc0VAA@@q(SmVRc%_T69Y03vaHa?PJR`7uYAy$&L#yTKIV8I!L!55 zNGjh-WWpHwlL->wxBAo;rx!eyjQ0T9+bi_*iZW)CBB<;p;-2gr5hhXrSBSbER@>y}KX==35wS|S-p-NYZTEBf~A)hD|SXHfmE z;A=}@hGQoohagZx8d z&&l+N3+a+2!xV&_{U8`EAIqeZy@tTQ?5ECH5U*hU{Up!adCWWgrngnS?lsIU--7#j zZXew7BwsU|^_bD*i)NC4ITE%3vFivr;i;~W>OJTSa)6u*tiN?^sjAeWQJ<6`1-d1n zy8b~g-n#&R%eTz5#Zy250+byL?P*QLZGka&(W<1}&Z*`G^U@ol-x}Mh*we<1 z9OnUNX7SyzSt6H`EOW*gW!*3PzE<-ShZ`5B(VBP$;%$p;O7RdwNQ2*DT@90DW)Ww+ z{E_PNYwseZGV?2f=R-tXbygXyw)k~R;ITa}4|c_qF7BAgzfaZy9(z}4c2Tz6fuu(6 zbaq@Vj~txe6HYo|iByeyLz!#LW_gv?bwwZw29EBcgxg%Jsf8?>-N8rDvPOlMd@Oe< z`I$S{x~;de5%8JRKc%%m0Kd@b&0=xWA*J7Yi@Jx3JSl%4IZCKI2O@_?(0ZEJ_Z@b0 z*@s~P>|k))5X%`5Ri|wBoqESLB06rMdLj`Ds?z5kgFI5r)FNN*0`GDG4eob&P8X&X zPj`LINcu?BP?4t*DL)J^5v}kjoY-9TP|FDhzJ37`GVcynQj=zzvQ?1+yzN3Wa;vAr zr+hk*hI2n@Oj_Db8t~eq+Co$?)zvH;rNUP4#XWxRz1VV;g2Zt94%a0H3K}fCdKg$f z1Em4Q*L78wLjh9r-~hmQz&S~?UPb&~vFN_Qy?}XVTx5QtyE-0gLW*c$Fww1!yJl$g z1swP12by(gnG9wF`c#q)E6$&fcEzC)GInr|>(02ulEAK*?Ul}mm17pB*`XOV7l8+O z)D!)Csa3T|92M0W!g+sP8uhc+^CxHf#o#gcJYxai6rxp=xtqs*$h+($(oJesP3!iJ z-a&!QdJg?H44W=a5<}qj2F&2=Y^$HOiv}kI<7_Xnal|%RXAxfWjHbrhkwo^**wj>0 zC8XI<2ugT*#&vfuT|zxWs?b-@N0;##{6L_S4Q zc!lWQO4%!Sy<~+f3Tu=rH1n!PxuJJBl0gc15es1^Wba({*T>`*_Ok5cSjut79S8k3 z`C|H-6z6qWW5n|c(JMs4K4bnoJsM|8d=ik0{XqjJmlLN!-(rY*TX8hli4K8b{ zg^j%7Qrp-E-$w$%IZ1m&Fd~)wY+7BLB#SL}rr8%eY)HPe1nQ5Wt9vB#uZ~4AayJKSkRm6EN$3wIZw5m?%eN z3qb`aw}r_qEvx{tD>$5(Z-sZoTih_m#xv^CeM_p7^*dgX9h6t)7FPf>;DKmM=Ue>j zJ0;8`A?XLg8NNlrWsKFPp&;b$^zh4JiCAE%yUnYWAQh)e5))g(=LlXBxqZOE@K-HV z&qdF)4mkS07K9(Kvl^vhgcI`go~U_qDljXohuY2)yM(83R|NJ1m6OW@*1ZL>?SVoS zT5ikyQJPldqFzOq>IxFJak+V?$5QyPe8_pny4_{F(WvJK3Y(178@v>_{}@VbV%tB} z3+bv`q-srMFL<-_9Y(G6&h){LgVNHqOBv06ux6bo<`Xq~%M@v}TsWb%hSyM|S&Cr>C`{sd7{(FrBsfKjsIGlHNdW|s9} zg=rPeRQ5aLG12-fNMR{5Wgr_RE%vF%E1+|kh&oWJ`W}K>SddFZ{B95sxYdh{RuyxY zmwD1%g|*4Tc6ynhk;e%KDR;-JX^~3(GNKjw`lo@p{AWucMGR%T_rOhgir&N2oQ8a0 z?6RmC4S4-+^U(?JkVjj?J7Uod-rAcLzX1-VH z4NIr8k#WRHziG9w6%;sE!UYdOVtb^u{L9%nRKYH)fR2xwx7Y?8DS9!k$0q~Kz@*j! z#>pvuh=W3LBi-V-Me;}b2ni=qHRw+&-~J_i z{Hl`5p3qUq@+BKPEtCn7R^ypnzD)2a{#668gOKHItTePnG3gp!)Pm@ z9nm3b(T`X1sqeB|Jy3}B>3{ps2}*T7mGpnVjlJFT|Kn}!A%UNCoh!`$zi(r2<$-(R z$>4*QLEMN!21p_i3LpVtNRpC{5*;(9o9x!5EB2%-g`j)>0!cm*J@9U(J7Aj!yi zI{V(^Md?_;40ld^)80D?&&Qwnd-dB}(4_a6b@{*!NVGXS%;eaQ$} zs})bk{JQ7#Q2d&nnXho(>0?m;;1xPf81FdjGW<%fy3I;odj`8_@Gej#>T_wW9tX9!yy9vpN^&8N5g-EfDc z$(+3FiT5rZGCnt0tE2 zsHi3>`+(7{hR<7EgpYI5du`b~M1I9BI`INaRTHK5Vdks2=hT5rmn|$jBb;0CG9Ej4 z9hEiui%$MLp(-w#8XGn1J*=)if zIBYq1uk9|w^!GEkB<@b6Yf* z@Mi16o>SiyB4&pSn7KIA-W6Fae}6@=gVZmCAI?zD@3|MznU58*2Q+0o1pP3cV1}iyPSpF5Cf-`8`w=ou z`Fz^9zEZQg11$BlSKN2^@MavM0#6)Aeu7W00p`ezR^7h!_O6){oFce~i9YaS!DScE z57dDF?j%B^{@||qnwkRuEXLDJo3wU;0aU^$xZs~hO#UCta>Ff}#YCcO{9ul3j`NF< zYk?^D857mXo6BBy>#?1A3qtK>-88GxhLKq)#Roe4ar5_l&}IZT`;~}N4q6O!Z(o6g z`z^hM{WV=guOFgk=!go(h+21}0=@TGUoi27dA|-K@5XN`N4)`n;@?<3Og{;@Udg{& zqu>Kpsqk4otBS}hsxNqoC8!#4P!;7bc8=1*7XXK(EToy&4ptFQcP0D z46_4ObMo?frb7q8C`Hu((>-X!Xcn3wna{dhfi^JeqX)NfCQWnh9-C(Q$wTTrKaJRo zlyC(ntC)(NMfK5&4vb=3h3&@7QvAVk`1f`g(2?aI zUd~5)E6aR+q}?k{*h9|WK*K)V0G(J5!n5N8+Fnwnf5w%h?=4^}?)j`wjg@IL``q#R z_sksO{^eijdna;$Zorb1f5$Z!P+=!8g5LfyJZ!Kl05%w6(&Yo=aNAsCDu@BvL0F&( zcew6od3|zz?HI=EM5$n-ofCT?_Z{E7=3F3$OJ{br1FBz!~Ws0gxK8V^5Y^$5pecj#+&S0=f8r(|`+rU#un@hx`{hq#MQ4zFJ%q zb6s1`i^m9Fd*N=n+f!bsPR#eM%|wHKPLRAZW>;d`+EAzMj_0|EVPO1$R*cq@kVj)I z>!0?MV(Za0Ua1Ke%kB;;A$QNErzZpO8NVC91%$|{`8&7wtxg2of!2ig&}Py5e8h7?ct$Mw>7OIiZDG-ksepippo3dis!|u!GZgkI0aA6Z6{D+o(qKzAA z6H1`P`Wph3e7R~(Sl!iQ5%u>j zx#uBO((Y>aZh^ils2OEdw+3&99_Sad%t7D1n_su zJOnn;3!>ihEqxahCCdbstJ4%j^bsnOQ=O8Sd*FR~ro!IKu}%q1Fo1U;lW4NTba(R| zauKI6g7{-JRKV@YNniqq#78}!btO6K^{OUmsj-R=8Z0{JQXcXW7@t{Au1`8&fg4d? zJxArhf%+CtQiq?FBk z`u-F+KRrQF`nK>IABKbf77(8qo6~~*ABO-fX+ryz)4&s^J%;cHpF?C8Q@z0mTpLJ@ zE*HrwzjLj0tXlAHpnkTD>E3_10aRh2+!X$*q^Q4cTV46dY>PhNyI^DeH;bL^2rj9b zh$s@JlO}BSRHTCnlv^>?<(9^6(mz-5jyjKnSYcA4RWaucxAb}JRpn{DF~M!q}GJhJ`PF$GWCsQA{}LPK=J7+-%LK0zbKov zxzS=UC<2ApARXhX;hJ)RScg8{aD}m)%0jUz7To{{AVwzUlaPDNA7l!Os4Y=lP>Y32 zhg2}y^^r=GPdX>o01~3BF7yhr(5V|lAD0&xSE$`VThkx!WzT!<0uMTzK+TNxqkM8R zh<^-=IQ3H==J;m{K5^LjgxUe##3+T3B|{!JDm<`?MKsr$308~&5!0V&-LWdvlliL$2EIs%s;>AzHB00 zXM^yNh_e5!05#BMCt&ljdsh6fqD~8oNZPslwvvy{8&;p{$LJE=>g^2A7mUS^_gf1- zgRpR%!TfzCzyK*+d{N#nW8j)4eh`cTZyl&zDBSg>0W0a3zD1L)Fw3@!O&$Kz{P&x_ zumyz=&THJDm8%DbMS)aiA{H+71Pf6Qm5)K04h7&(^0`%=@KoO8dGoZMIuJ<*LzP&AZHjdGsv^R*FzQa+nWc}B zm@YCG6A!DEHZ*oak%Xye{x}&QRi*Z+J&5V$Ad@Dx9 ziW$z(z38Pf0#JXm=ZK+gz6l1eT9vW>`oFt6-1ZH2FW6j+5<>I`q@Q6bD!51u02|Ju zR{P(Ww~UWZ<-fu&s9R4TkY|LQSPQwieUI;GkBV29m(}Up&;>01Z~T5U!Njt~|3ycN9q^wo!QQMRxL9>WXm82ZJBFWIzQLoRVRI_NRl1o@*qlG>rf zdtNvNeIiZIYp6cZm8_N59PNi1)?mb{R7^Fu6jkIbg6+M-x69f-+hHKUFA~~(f%GBD=ne_=?^L04ekgRUkp7|G)Aj;0mdYwamcieS|_NzGS6C&*^#a5v(9i6fRDXMr!d>5`zgUpTy0*Atx|g>kgL(ACsn zX<8+P5Bkdm!44|+=9h7q(#-TWGui`JT~YRA z8n$K{_Ufnl}#q80|@IQrx_#=4$^Bf^kN_DDAXeL^a8Ks2OvI6&oE1i z@;yJ!MyOcAplJD{SK{~@;#K$K>pmhY%HSgS0{#=E_OuR8?%u0xBv+I=_G_nyd+=-| z<`BD4pq4aELcO>4QHAa~3;szBAiVU}N0~bWdfq+1gV$mLy2l{0&Ucz8lJb~ItTveR z5nqB&e7zBC4>8Ff)A4KtBI)t@qxuuQcm8fT;LQkWKj$~tv$ZxdBB7W#Cv}^U?u7Mu zUWy88pSQp70KoUt-)AL$^}u9I9v15^|BDw`{~Vym|7TtrwE}~=Z^a&_`%1ixt}z_w zuO!xcsG403P0rHa)|B$1@%uIu$8P3#1OUs2_iA4xBmOM5e{Svb{_NEFtm9y`U(ILzw*JPFLjZwVLx*VrYyvV(JJf4YgNQRO zYSiP544CN?yYu=6qo^Yufud9}c!$n{?Qs51VrU zgTGv*7#*ei&(^<3F*iaAxWE8DXp=L`6mSld9== zRFjvMvv?=}&*7KJ0i;{ef6*_r@6WfFjRK?BI^U3LVy!Rn>-C_l?lo>|pv1}X-hSh| zd(&b5qjW5h^$MRLe&tUJOalMJM3#d)x*tJ4R#UGJ^Nah#6_&q=U0YmBk+p9i|fw1(yCN^KHQkoTO5D{(4$`GMMtWpV5A zyY(L`x_Q@QGWWY<#;!l2ZJINh^@TdyVZCDs0~svEyZ^H1=<*|;oP+o@KH7LZ4#n@m z`hhO~!>X$My)9r39bt|#VN^9jD`Q1|w=SIPO?+pkF%#-DrVWyV$`&|J1SGKOEadO@ zv;`9RabQ+bZ0SZ5AtxuKSQ0zEX*m%a!lupzC3D7vpqiMZLQg8UoyIF>-_3*q%8PAO zCol?T5(My(Uyv;a2;xv*ZF+MsPVhb5g1HZj=8a4IhPp*y;fRkJc|PNLs$R+8yFE9l z$e*e?V;Bl87kDJ(oOR}~Bd>XZ?3)tsM+D;x2l={IismVP7Chxo8~xp(=n0cWUFWuP zWk2kp!a=ln@EhSxQg8YX^a0EC4X3WAE>0kXnPe6AQrs^=zVqSNQ!ZBct*D`wc2E03 z5`2i(r(G&UwSr%#kgS;aqXtj{flo!noCSSQ%iNR%G5>_53UFE@)j5@ul=9RFqYfZ} z*nETe_3q?7F0s(z!2`IF8l4GX&6L~+3b2`pn8PGw_s=>-SiCg?I=Sr;C)97w8=cC0XTMJr zLzd2_wHw{F))XRdj}` zibi;_3wA&pLNE$$!0OA%ZqS)5yMh>CXKErr)adz5GcNo-_K(n5s z#Vyce;mD3~H&;r_RHCiNsEghBsir4&N7~z#S zg9$wq&@}@$rfm6*srw!Ox@-Y!X`f+$xp$nih*EyITEanO4Sl35cqt~apzo^s@V&13 zlK8cya*PS$m?su((jH;&(|&c|q0^3%eaU?)Dm5R~E$g4>RF^Oru7hJg>?cF2pt%7> zDIfe^7on`EJVR1w-gvV8vF`jTJjKEb%*4I;pS-ta!;5(;TP*UUug*otXNwLcs-dNI zl%M>EJm=Cr9&L}KF}5CWe3>>_68w{M-_|~0w=NPa`rTS3(*d-BaSMO9xg`7Ed-wwO zo0L3a_;t6KsAx2Nd|s%4WR~_Ajo;XzbnQMtiP??~MAGOW5bWPmq{1wau+X1?lS}+q15)?wzMlUG6eYyvv(ope|fq32BWgLf`!Xjwp zhZS;CL3XJq9!+&n#0qez*N1$m1Vz*kF!*=u0D@v9CYn6=l|&D?*sq!KR$*JUjn_|r z%=o~(la$%Mj|VKd5bc}8Q5EA-7Q0A538VcQDw1HNljQeRD1wEyXuAD7%@V|gBDv+)t!Y}N z++>g&8C;S_;hEbN2nMZ|k)ROhLl;h&CUp|ck6peS)-7s$h;@@{%?jml=^tbT+8V)A z67ThMS3?nI@Pve2BDdZl4dGQ7Y*ePhw|~(-D*oyM$3auFu~C>Jzvyn=yfDNzMX?z= zO-|bR1DhLhx`+AD(!KrOD5!rZwt_gIEE?DTbj__?k+AzMMZ11s{JIQWx!WNp&f%h9 zO*fWzeXIy9Cz0fzc%_M(M`r`YD%(#R;RuM@B5*_1c}Q7auvj}l%Vc=#()G0X(RB3L zPKeX@bVO{s85uU*G-X5eE&dYHPEbfz_qc=JZw6uKw>A@OyYm!f_k#aOd&9GZE9~%2%p=FyyCP#u8%v86XsS@i^}q)UUc3 zneC0`oC`!|popmr8NVQW%K@I`=VM! zM`NyJ!`#MFWakJlTt5mP-=E^HNe<9drp5|ZSB&{FOix)DoAsindm4l*jFDk^DmHNx zEM*uQE5sl~!J&He9p2OQv#=m}RGK}rR=8s#d|LeWJ}kK*w5CT_odJlSNv7YW!R)Vw zThBBZ1sJ2Xl!ekxACpCK-a1VTj#aMSSDCJrZ?|Oxy7uS$rh_iz*U)WKi1%3K`&MZT zjet861RXF;xN@Wm_{=hvtt(x)mZ`;{$T0mB@LCe_!g{rqA9&!++n7v{ac}3!L(mPI_ zNynQC=dKRc>r7V)4KN4tI?Ye9@#8r11!JN_rmb(JO)SDRlu@XM+n)u3Pjk?$<*-We z=S(U!@(2`kUo6l4l*)O8N5AbNCVFM&YD&Ey3~+d)13C4APgnsxIc|KK45~pc2Ijue(&zOKj{4c z5nY=FjR=xBAhVZqA90HYU@e8F-_x4>-k7LHESAYgs+CBE&42QQ_#<#Yi{5|rR6-s4 z@u({opHX#o@arL3@fyBJzh2 z@l=g-#_q)V`NiQ2@egxHP?Go{8NI;VmKDR1Quhwb`0T(eqU%>)4)(;%(BuTWQATyOF(&0aZIu$tTKC`+$u;eD~N6sLofv^a?*Vg5_9!B>l2?iCJ zJ1Poak^5eG>b-Tx{H~0JTJ$l|a^~=9Vd0R2x#t0f@jMMW3!rLKQCZUGi$_+cN z&{uxgG|XBin6~)b#lvWO9wuCEun5tv{KW@e7)+Iikq&%7cO|Y!A%4LU{2O0UfuI5AmNN^;BRKYy5Dbni7_`g1K=@8T@YgqE`r@EEcQOIVW6^YZ++!z179aeNMH0g2_JQ>FCQNbq4 zunP@{D7!-$x+s@v`BwC{Vc&z6BW{R|oBocU%TLr=&SuLiG) zIxahC!WpUHGmVNz&67ywueg8rZhgT3F(B&ww>q6$Wb4R9#O*I}A9cEE!aQKtuu zYEIQxow8&Q%6& z)Mq>x$~MwXLgrUSpKaEqO-BAVb#!K-87oODe*FUF1r9JyBnpNjD~J3Hs#T45cDn$1g)y3F)Z4A&iw$tV zG)~Tm6EQtSMom z;~+W%j+Rl+J}WQH53%DzDjGs4Ty~53h}d_7AID7m9e&5`M7d?tu;}(ls7$R}2b2jq zC1egnGaP%YkWKg+xdqODJh8dsq6z&_cqF)Acn2S5?+dW#tNwVvh%u2GW+uz-jGl7x ziW3Tmjia)etTBg0^FXpxY&gTk%nuzuT(qI`o8xX|`q@1xwvxQk?Niy495j~c4@;h* z8-sB~>C5Q4E=u^tw2tC5$f)=6Qija#58Y)d$S^2l;E!+gkY4NM9${rbghzl##-O03 z{Dp|&cr+~V!P`GGCo>W6TNM-=FRXP5WY>D_5%oewl1nk2iUFnU=WqZTxx-QVel9(?aeG06Oz@xBj@T>>T~WI=2*!!`3FKuZqGfch43> zt|_Y`Zjjcrzmhxdl;7cZ5Y6$+7f{|?BBx0ig=iHRNlqfYbqZ=-gmluo#ODYATLCvitRbSHv6sZXE~azqk-e;qNKV|fH!_42Eb#C5E zU~UkTf^}jaspOp1K#V9EIUYhAQYQNZJ(I*lFj}Vjk@m~*>_eh?c?-9 zBnZN()oDCyl00Az;Fc@Wv_1knz%d&V3zB6xntw6zw}COBkaLT8$S@oi?4>`1$GdC4GNk=5xvm#4o)9 z423WS8%_diqVrR(B_RcHjSq8la{ErBe#HbbWDAq162I#cuS-}jR3U&=oMI6sAt!`( z`}|djNNo?K(HC`=1Ea7MGYaK7BB1@wGiuc zYt!U$?h!MnV^^T z>nWH(Ur_8YfuB^PuTUoZukg#Blq-L8K5i!%@-AO!xDnYUCc4@bqg$6KBo4f(EiZI$ z`d^X# z{{=^MZZ>eA9HE&jcYAZMDMCnZ7zigV5TR5BXcO*TyaIy_S#jVPJG5Oc+-NNbnXx$8 zA>=%T!K`9&gDou1reGK*5k%DzZ3FSFlFFpd{EN8KLWD1?j5}IB*^sP^)9(;tVKSG{&49kUVv$K zx^`2sCg>=F3&#h-Xcw+~6114XPJiO&QEHhc(uTi2r?m8fj~N6{B&eknDuj=*2i$mb z2<F#SB;<;RQAf2b;u~wehtr+}l3}psp3zQ+Z2l{R)@}blZOQ z5SPtUZHCsS*tb829Ehbl+)+VT{;}q(`%ypRVQcD+Nn%BoHo${au(v|#$uaMIksN_Y zIE2gb$z$&J)iX=G%FbYWcZ=g&0k2W8x`PzIkdD$nEi_q_N0lp_za7enp~3nCZ?7kx zElQ~1+K+*s=zQcyArUt#4;><2XlKz!N@UrtX)C5CnKboN9Cg`s?G3KbB;TlnFrVbNyrcYG!%XGt-G@_nr zxc#LgV(OY1o`Mx9MwdGUWb1!IW|gj;?o73L3L%`((2*6tD#Q8}{&c=>^Ss~8arnUX zyPYL(9v~!`px0yEuL^_n+D9g$3S@eNg7}0+Pe(`XEwqlu$-MlpV3pU#!)0+rD z4qT4ad+^*e4jTlh!u?v>4htH>Qgwx=O^+t7F`c(78# zspI9O=n(x1Yj~rF)rc0?w7noK0PV0e8S7{6@2N8iG*y1fm!&OlcR*iPhuLq{X0ajL1m=L=s?!O)pLn%ob?c#s2;Ls3k5>t)}B-&A}> z+-#NMM7;}tJ?t7@;2_gca`&?i_r_Ruv&2nefes9GToI69SdsBkZsq zXnn-4Ptf1IO7KN;{A;r6ODpRjJ#^fNX3&P~*^@d%vEt4^;3KfBGLpG2RZQf%1^7lgp1V|zEK;R3>1EL6E0SC#^Y%LYDBp;aTD zA`r?UEVS|Ob^ZpSLzmx24I`^>>D`k*C53jqkw%{P$iMG?v?u<*qeAu>VmGFL)(w)-p*n2j$ec_J4JpBX%phUTqkYGM+NNmycP3rZus^c@3VAdnXFLlw1S{Fm*D zYOd&$UEd=vl>QN^t-E1?&IL_3KVroDkpPBKSEDHRGDY_s=LBOfr|?g0>i3hwnalDa z%l24QP*uR9Wj^CL`5w(Nb=*+#tNWqo1$r|zZ$$nr;%RXH1>0TCA369eU$k(~)vyne zgYXFn{1s!eJ2LnbFpegH(J5O1X(EtoPTKe3+>Jbry>B4r1}f@BRc`U{AiiZ>_5LX% zf+ak2^hzPGqyadI8mwDVgy`eU6PgvOiGGh7pJb`;k64zJB9yuzoYxmw%(&51YHz z5QuG(oOz8(9>H-2(ZgczTQA=oOOLIvwhqLUZ!&u^!S;_m`%jm;aH=&sI6cn zHFt9x$jF1x!a>@fjX8-n?`EQ)B4y8zhl`27k>Jy1j&EV#DQp1OER8~-VJmV6o3=bBD5l)c04NQkDIfGm6CzRe3Z6a${9g4QDpa9~|>-%O$=TI9h*z zLjhqhBY0)5=7Xd^qg1iQ1paT4ut%_kqD7_f{2e&n zg?}tI(lY_jWj&BSK=ZUNMS=IOIOVL85r79v?IcE>3-rlDNn^o*u!bD1&F#>;_hR5ZLyoL!Wd1 zq^{Tgk1p`PbL+hMJ0Ga!hniIRdkBJFbXQItz=7Qt3CeQ_V1XqY6i<+8c4#JI@LNLu zFJIGZErq2RH`;<$!?iXUr-E|_D(P6jF{#t!(V-O|Xz>Nq#+9XkDm`HT%sq1Zz`}r| zuO94MS6_dwWmt*&Vo+Z-F?L43SFvy^KCTaByK*{Et|t@k6dSK{e0TeXcDYd)fzox{ z`^qK^e|H9u4pK6qdMdZP!wO9ZAf)$41WsxgB7VWF7&{bx3=>y;P6L=@>rm0FOUudX z%gf+%2|kE{16U;VjU2W7`0_eLpl>qYkw1+xGPbFx$sd?BMj#6IOP{F3Lj&_%|84UU z0VE`n0H77lVH!+t8k)Ae=W|?IgMO&5(UG*@8#C*@Y`%hJ9!65ekFt$eXM+Y-)AP^lyaXE&~+26sEQ{rCNsz69xitwm$` zfRg6)YhOt<-3J^)4oct2h24q8WFY9d zfAAkj=3hp}5wbQ0W1WM9B)_E>J&qGhTQamRN4}O`UYmn%?|bGg-(?KbYu5Hp#~*w9 zVe;(&C%@c?wHo5KgWCa$8DNG@6JgCXX}gbF81H?RA5(?DDj`7vF#(R=W}uqM81qYZ-VxoaEzWitVnlP1GhmqTNB(YPvN$w zAL(ncaih=QMZDXRE&BBx>9b%%QuCZV+fgoW4sARK6Lqwq{Ou>U5zJpjt&D*@WA+>D z!wGObu0AyIN4EIq|$PgikJfAP8^ahP4}!gX9=MRyg3;_<{}P8S{7|68i5>2z*oB z50RQMQRm`-e;9hZZf)Jj_x;iB<^+N&wB&uITgCl6BUNyDwONeGO2MQ^Zpo zfE*Ofe;=UI0(T~=bF9Lc?8RSGC>7aWj9t~ZZj@iB0m>wb)_Glc6E|U(A%Bb#I}7yZ za+0gzvc;G;Hbb>=%ljFA<0FU2@{1yXywHv@dKhwx?6Fuwo8CZB;3$|>RN}%!fxsrs zBkjaIhOzWWuxu>_yGXH}u8S|kY62SpXEUHe9l^1-Fij^v#WswX@2GLoH$v`8q&^f5 z3dk!OYkp{v6T=yV?8W(?)??n)QJh~OXe~uF9w~EB ztZpMHxDF!0J2bE|CBo>fG=X>P;$PQYg(3x52nKcPrhpWx7QiMgeje?YYEb*RS3`zm z5k)OsnvEir3Rb!g|EgtUgA+(A=^qj580yAEX$3g1tCzG0T0>wnuJ8xkL*+|M|GfT- zFs!J;@o^#$)zJ>Y9S%nfsJaE4-+*+oy1vlt^dTh?3HQ)I2ecq!0`c>LnQmCu&FISw z)V<4b9BXYJk$IeW| z#{+Evy~>OIJJQYgXMgy9lM;OPwxwh?Nv#>{4VG^YB@HV$8?9Of1MKv#*VG#Jn4faTo9NA+Q?@ zD~0OZ4kQfS!Kl_(Go#mCUa^PBs>2g-f=J89rnej=-O8-uoYA%T-0sFP#4t+zyPmc6 zSg&z)uY1uw`zJsR>sVS20gpdE5Qyf3K_tY)yUcoSe@IxHneFCxjnwx9lF-?HR3 zh+QK#tX#G>h8xH5A$vW%F%pi9l8|cs<6@uf?Iya*{$IA8n22_!2QYzAB4!;RLt}@} z8q)h|v6H{aLwCz(z-VBMF=yGN_#pv9w^-`1XK#2;_BVoxh(L=Q1M4Yak{k^z%5$5b z8kWd@oYLtG0U3$8Qoj&8x>lJY4GU8Sm7R3u(1O)f{93SbSOiyFrcc<$?5$V zWHZVf%SK|K>UNWT@))1)WjVN?PX=Ui{uLc->F2Ta=}8>N;X>MtAqz9wlGq;R;NjH%}$c8}u9E zq9n1$WYIW{ZUSPl#agX^;WRniKZIu-iCBEptXD3HNOreH6+#MN{pFy%Es43Vhzm;9 z`vpNp@;7Ehmn&gb(ANYBFdIN~h6-MS>y%NTrY<2E@qoH>o`VJig{W8sqgU{AI)Wt& zlu2wB6ER12DkEt8PrP7(A^lYoOGhT?30lB8}<7}S3K~fQGr{?R7#Vm-t1XrN zdD#7(9GAg!UkjKGR_p;HQ^1Azzq&IFauNx8`z1&lX3J6=YslzGGHdeFh9NTC#OE|r zP_wf&wPmCdY=k!lAK279b^H4?biLSVFCvz6>Q$*Cbq+jCIu zy#IjJuRt`;S`LKGC|V5xXEc~y&gATOd7*UW;#A=6s^IQgG#fTk>co5@JhQTsO}Xjf z)ZH=e?ZZ{O5nbN2luVz)jXm$?8_AB67)ov})C_t-#Zau}IZw(axzwBhP#0ScJ!@vT z8eiV>i#l4--t@LLWyoQYsmG{py@1`HC_Fz^x~bHQND6xZE9I6T+(0^{rnl0rOS z05x!V`b!NI0^=~l!4r*EBkqRn({*C>E9qqN6&CwpU5~`mYVREJQc^*bz=5AQGdqJ( zj=1cQc};g^J9aWm|5W7I5RMD6heJh_)vx&&@fPIJ7G3gW&zQk; zRX^hQD>HcYVEx63Ftj6_qAW(B0b44Akw@6j_F!ictwJ$iTqld8?%Nl$gDVP-7t+L# zIv+QjQqb=_(=5FKhD5>>p|e$rlGG=+qjaOSF@K-ZCj`zHb9ZE<1yBmD5@4>p@d z&!f#AvT654*!^(u)Ox-sM=IFdneoLehVlf7x0MW$ldQ8E{LM8;r}cUrojtue=`Fr> zuqdq75_DwkH1?b#IcWzs$v*b^GnoB#AdF^!N)c@e^6)DiC>;^y5F8If+^tZ{)bTg5 z3O;fx0IKcJK;8#Hgn7>RX>hwK&bzse*xeuSPD%cO6@*Y*g|3BgrA6?dEKhV!-HcWP z;nHxEc$6W9C1k;gqeGi3Q z2>>}!Y7VWQT)t8v}+AoEnsEATLl+Vuq{sd%F_e@)IwOrQhs1k z*xjsS2XC0QRNUI2-nfnM?sEOR_@7WL1SNsm-3g_mmi;bEn|`rL&}9=e=?}Ta^Uzjr zt>`X8Vghjp8aWR&Q)$|I79D|a%_BW7$rsoTrm?>iB~gz_y*Kt`UU)r!FIWi^W!Bl? zHhTpzq7qMPBFiULGgGX^jCfPaNsl+QdfvnqFKF-qRTo^iLONmu44W07wsy`R)l1M} z6mN*AyLxPr$+&(y9yv|Q*Iz(nIDNgx1^Ly<+a~O(;HvU{^=0DePg3f_Y-T_Z#2W7(K2;P9IFwe5W=f^ zovz*g*kF}3_tSpYLMO)H7W{BTPKtp{5$%sepK!P8e3as+i;iud%u}f~E|gc(2gL0b zu_zSXOdz2EF`^zp^UqrF5F0)(VtPq9mknV+zj4e@k!c)&`e@PWU*Yg}gxKgQENL9^ zVvnGC^+W>FChH$Cj!_-tm0mo7OqvMpDjcpx%_LQ|NiPO#n&`_=qVaO<4`wNEMPUGS zbc!G6$Y-yzW3Q-*c%X^*i0fyqLxIRZQ{bhQhm{f!28|NhT8t4Lp_9fk zUz{wL_HAl(@Dk4^+k>7Az?KrvetaiiRn;8BuF@3`BhqSMcdG5w<@6keSRw=UFLnw1!A1^?jeLpaI95~@6pC9rbrJzo z$80a(eH?sy-pBUJJ;0UF3*zsnwvr_-vzBM8m2*ByupoQBfs~S>QSfAl73_bfpq`$1 ziFm-l0l?np;H~WWZ%o#6nCr!%7mX3l!w@YVk+-_L01=yRlSqm3DEzCpcrA&_DKpEY zz-*d2n9mlh`|Dv-681pwE{m)nQc6R1&pa1DBR?H%2CH%b%GO7PL0F zHNuY4mX`F%6G&uXF#C0}TDh+UrKU8?adUy0i)xFBMyD(#Xm&qnhB*}fgrDI(tAaQ{ zO^$Q88^|Rez5*V5;Qw96L$;!6fGNLO=QFOTZi&#zK!p`3c{H0Ara<8KcGxI>u@1L; z-Aw>>j0K%52~L(1U7$|CvK=fw3=~&c}?F$qLMQ@p0{i9gn8!ndd`MOe1_VIXv}9f+qL3 zW0F$px>3c*`PO1Uidy+$-FbJi-mM7K3Nmc^q-t&ZCV6}fy_^2m1k1!E-LRx4UG^i9 zHtCQVO?f>uyE|g;oiSY6SUZ3$K{O$Njy~7&yj$SHmt$uM;Utx`sUAhy|MKcrB1*wO zVp?8tLIzbtsyuSa(CP(T2JbplLUEaL$HGf$EaBsdnc>o(Fxn{0uXj&?36eM*P{Xqh zTZX{Xhf+#IpzkgmnylsqS)6c%Nc(y`lg>pZ`I+Wr?{}%-?VDN?_D93G^WS|xc4B+y#j+5@$kiV7I>*fXs z(^!#A>v^>meO^K_wUnbxQxNt`DTSEecZdD%@UehS8uzl(1tqPNCaKU0+y30|IqT_) zz`iQ8R~n9IuuaSP0dMOF0#v|LRZI+#aWOd_>HpH*%>Yb@EZ(hUMJ3c%JK;!7R4Se# zwj7GOb82dY#K44XXreHQEif((ZuE$VMw9<{@<+PfA1E)(SN_<*yyDX&uW=&mbRp%Z zl=%8f*l{_U=$rSJz*HbU3c@TW{CI`ZlWmLS0%N57$-E~;tI0TbA);&tkh3o($~nu& zrI55BXhMkYYY~+c2r)_TiHH|35H|$N5mgmdNTWST3!~+={S#>0W-M<2cmKbOtAAW? zsmw3IaJK{E-SNPxjR&i=`~pF6qf_Ymv?Iq5iSH^ef#~|rbzPSQ97NvwZF$2!M0OF& zdfPG54TYtOmmV7~IcD$ROCUC>0D~N~eam3b3A=>*b3#^-1iK6_o5^EORNR?dksn0v zHx(7el)?8z9m9)R(^@tNuw$L5@5iK>wI{QCfpUe)L<&p01)V$d@P=x{k~ZsuLryIW z8@WIENHJs%OpA`u+G<$oF|AT}${M>+WYqlc3a4E|POHG=9D1CrneB2nPFy&dCF&u; zd=&!@$K)IHODPn$st6g?03dIzt(PwPzKL{4&K9RaI6jKh4^+w62^x9!2V;vWIgJj>&_>0Hqv8L4(A}-4 zIs}*LQ-ObmJgPy~h{^^&?|-P`*yqmjhR6pd(gJRG{SjwNXAZAEe0VzEzz!}RI8dSb zqze=ED@LdmDTo0#yf2Y9JCb4_>!W9tSVH1>$}zmq;ar@={5PX|wTpZM3L1R3LSUP$ z(e0x^YERlcP(uEEq5Ahd?Bi^3+k>mSJ{X2Y*Rju%(k$xB4g6&+8i}ZxWhX{q2TtLB zveS`E#B65Pi-<4@vvKy3TG651{BmajO^n6k??2T)or8Hw46i&8z5n98&geicyihQN*H*%-Qq+f-z}QV**|r%3Zwalb(5w70X?Hec{^d|P znAIr^VKNW9Xe>A75phwbT*uF~bG8{5XbdMrNk#Wl!?5gcQ3*J<=iI3qt>(1SA|+AD z50ecrnSIHT2%c z;0cQ)8sNm)441XbSXa?RQR3h4K=LYemLLmX@D^ z9dHA~w{axm)1}pN&nQHz>PlxqY)b2PhU4uq#aM~xi2Wuj(gK9>`87!LUYOir2t5;| zv^gK{LzX0;AYCf-K3DWo4&LBUjXt_p-qpjOx!uaRpp}{af0pSF1{2B%Kx6HYt6?d` z(D)$$DCNR+2ZC#Uodq_WvCa@M=Rgas;7@QfIgD^vX0B0}N4Lf7o?@Q+6u(5EGq8&RQX&!WT zV_#Y582bQW`oqruIh)rMozp@mW8^j)c{(9ckiORgrkf;J$JSmkl$*>qdCZJQaxGR1 zXZ{!!VknF;f9(e+sZ3RH=u_XnWr2{}EAvM%Apsgd#f|umaYjK%Y;Ff;2xns0Om@A< zF_bVF9tcJ$$b!=+VXlKP&`JgIWw`6opwAV98ajj6DQNua6Ydux%m0AyheO+^S2V7{ zup7Y=o-aO}N}9A40wm)KHNN~adCXb>`;zS+2Wz86soNcm|MFo7c4y&I7yyWc3-Y>x z<7UlswCQm}K8w#N_ZDX7gv9QE3Bv3+H7i}R&jzLAAQ^wUlba6tU8S~6Br z&X0%37arv^NWb*aIq%KC&O`I_SFJ>_ItFxn zFUYDOGXAYkEjq0-MkV(jO4b;^z1vMw-IOuLn9V0j1>(ubrN~pWoHPo|&CCWS?v=Id zD)SyIx)Da-^^e7*Bpn2tTBCJ%3fX6)yH1O)*ez|rcqhYMG3#b|fZ_d^ZW^Ln0PM4l zAu$9CYBPX*#WQ+IB_(#(v>2)%rK{&zLNQN_iL|nopl0Eq~!dhemz%rLpNh zPUv-3N@rF=rR8EH57T0*#hN&+!yGq8Opn`O3WxD#+6=GJvm|ix1+E?w>VuUZ=2504 zCjYur80nXWdKBUGM&Q}$)yc>OI!6V7xlot+RWLo?WNws)IS(mC!O+f&W95SlTgzz7 zAb5`lS6SBg*#}>CEoQlrPqOYnNVuX z*(k85*fIvVl{L{FrjRXZFl z6E?Sp%|*kI%pQ=@)jLpR$hkob*9}Os|D_C>Q+$;aduU)Hl2OfT*#AEJlPs2p$W@k) zY`Vw-S=+6xsn6hzVlVkE|_wNW9$<9@41VoLD>(? zpYwx0<72tvf!)Monie|lQq}~1y<;vZUId~(?X@$7j)OT~h6|!l z>ijgbBFtj^@$k}s2K!WU4rz8Cv8h;RIR?%9KZ*~_-8n^f6Aodiz43P5Sa%Qm(XYr& zO}+p;a3Tf@x@4`WT46E_sU_xRRVPhoYKnqgtz;2i>;sB}%52XA3GL8c(#~wO?Coj6c8le)H(>we~~K z`e03$0c^bq!_DOaPnEfZNp7i#J0q|IRoAAnCuqwUrp+cI16z{HOeoMDAKsSQsJkWt zA3Idl{)uUm`8Rg$1y!E0x~hpp{Oo9O?t~ld&M)iF48fOH0Zhh(4?~>wq3H`GjwFE+ zuLJ&l1c8b%)9(Lpa-Yu$EZoNfr4)cL%vIOvd3Fj3?`pboaP1LUaZ?DTpFio8gb7yC zU=zLG)z+0L{nJ6j{cY@erAuXdHTu*7ig5A*PGv+}KfbIGE5wb2h>)0}K$~pYwG~=0 z8Xdx&cC2vZoD+Bg-3QY9zgyMaRgRYfxvRG6AFU6*5LVx*_Wpab$o#-HEz(C{0s3`B+i( zc!IR6inwbpUFn~ilS zG!Iq_1xCxr5uss6@^^^p8mwAlI>k$ep5xX9<*{VZ2We|Fjht*6j?>6Ch|1=4(R0M| zEMc-g%veo5ClE?~GLO=dl>s}aYNUl^*tB)7^h+%_=QMX_Bsdts^S(mLBZI1m4 zEn`Sc5nA0Sa!bQvc?o(0V1R!Ykjj|H$bx9g<1Vl@(>tHy$I3O^nN=A`Yj+(OX7_)lpD9K#`h&;4EcM0!!%nYVFW}c)VHu zq}sWM6BU1odV-!HKDY{Kd}Vc=HNwJ~CH8qFT1m2AcP8tU*thXU$=;DpO+r98*}Hhf z<73#bPlNv+G2H${*Zy(gLEfL(M!>m^@48TTE*ycAj@;s5#e(hL8xgJ;TFj#m(Pj0?Xoz2I*#*811pV4C%X#v6s}a;K z#rzLQTA5d@6H%}A)P@*Z=5~UhG`TqZYg|H<{0^vz3gL%;%8E%t?|nFQ6y2AuBpp{m zTqbb5qLX~#5l)&=y@YGXA07ytTyO!F>p$7INu(H~cunyK*kCWyrFg)a9P+DI<+l&L{?5qoHMoX4hgT_ib*QTHAgQL#Ox{j$y zAbx}pOmDAPAaax7RjaUyF2M$4kLhtj_^X3%c7);^UWzUz9OOkzPnC38+ySy4^NFt< zOwhRv^h?9YO~y5ym~!deWW9j(ERpSH-xsC#Ij8X#9S(-KnZ42t{u=mellLC3=$w%l z=f@*A8rmiTaq3`VRlDs8%~~Ie+0incg89(XA@m~0O&RF;f9r}T1#PzQ5a(3rSy;8~ z30Pe0t#V2HG6`|b#<<{Q?H~=rDZ==nZFjKBO3g=~@}V`*vHwC}({`oO_}BB^sr3;A z`u2j#mJm`sL2Rttek4lR-469!lGl*8Xq_Ck5k?E0;F(+fG0A;!ug!NiiR=NDLx&Y2 z6b25{dv{+t3%2%}D~=n@yeh@A331(I3lr@UeW0T_UWPh ze@>p33)dh=VMUZDPeUh6PTSZ(Xu%GY97(ey`r^NL3FC@+sm<-IV({ch#Zqm~{pPHI z>tFa`;D453>Dge_05^QOjb)E1U?IN}II4jlvkQjha6Zk|czhYO$sIT$oLgZ?fmvl- zZ~42bx-aCxnmC055+bXDj!6`L5bk-tBc4v33pp`rXTaBz)h{g0El=-qF!lz?Guf~+ zs+UY1a^f3`=4~STze2n!7&^Zd$?6VZq__;?;v$xwd-9BXi`v zWLez`eqO9(e#CHVDSX7avZ4^M5}p%r_5<|aY4dVst>pOQk|J__*-@T@kX^R0$4l;m z$2XyX2rbTJLMnr_a*!{}Jz8Xil!xihY*lnDfW0!Z=FjM+vGC9BHopDC-EL#R3*x@Cj|X;rLdwlP!3VBkNug2gVO_AM_1X7>N)whQ79-`NnR z$QqWCi~`%hvpyL%WT_Qa?H9CUGtx5)+0JU3frQdYvt(UL#9xSxFp}JPm4jGx7i7TZ zzm)lf41KKPpM5uGKrJkEe2II(`8`$Wnh-XMCbv8UIS*cMdSxF8fDDMshRD?bItK&*-G?=&Nz9%(VtMrBb zjbQm%3Zbv>gjcNDj=15oE5Q-;0Yz_)O$3j>$TAbeNt#h9QIwu`&{`~P6WAUtQt+wz zsgIzu-0w_EyrESEA}#csm6FCo%;fI`0DU-T#cojF$&L~zB6Nd2;m*CFLi>QK$*}s% z8RH1Cah?SE(}J0$v(h9^m@yC&7xb~ei;5M+H8Pk@Bp60qN7m7p(qO@+4JmHp>1cqp zQGa3&ba$tk=8c2V^$UTan~`Z?+pQV#he1XY{LK3xjydE1o$G}%T?oO7BM_`O5WBuC zbMtYA`lOXV3hdfS3!|M|&@a|UD^|-doX{yqU1);Qf7VE_ZfYLaeRCvSdO;6WD51H0 zoR8K>G%b1+^S|~54E(U%FF$#gI^cdA!%vEsKvucp=AFfMnf_wKVD-SaLK?csoTP5$UVn)iYS-B-|G+65p;U9RDq~oliZfQKfROOWZZt@-=~Bsc9ig}YfA&G1Uwr5~RK0B(Mp)-nDw5zd zRrvy8UH4tT@1e=px5Pr*;CwN^;JS4YWoHofg>R^SWyCLZSeMpz}>2iJVGTflx|G)bpjju=|<1}Di`EWDme zFlspAtK@8Fhxq6sn-g~S9c(Ks9yQciOS0~Q+<5bkP*7uY4>oAi%!M!Q^bR!vBm zOHbFnFA%9lq@ia_|M=~eG89Kqv24SS(Q z*1DL$3~$v;tm=N$xGH@9?^yuKbA^MrMljr0OZkpkG_005;>x&vjhUZDuTw%J;UgDc zA!|kTxWF)U;B+`y_cLwu$9WS5 zo!;==&+Q-*73wG3enOzf7}3AhN5otWIR{;~DnJsBTUVhRXLa9>H_ja(Ub@eRW?7aGwJHS@)CGI0$|zL)*N0D)RjM^B<~^S24LB#HL{os1YQx|UUgZZ zM{zQs58xQ2l;E>85;A%PyB;O8S(%t|JLkq`$`u`Miw^&Xr*n*sq>a{g$F^S7f!B3iwfNW0_WRLF8s2Oi_W=1T#?Jm{t^Cl=wP;i7p$PfdBGFBV_Lp;)ywQn}v>^o`9>6Wsv2xxSP@61{*D{>)i+j0p-en3!xuBfbCy&|K(E zV+Kq%laV_ST2r(25J*WkNYcPwTlmdg4=nyxtcI;8R@n5(#=jp%JLLIQ^2Y)6enkp6 z)0Zbxm`t=(>upYrR=r^j9M}6?D5?MvI6ADaWr29(mNyJ`U7m;yysldzB5ueJ5RZ-= zB00M?K#bidn*6+PyzgGpv0<7sOnJPmE)q#JuBWrVZrD8wv`-iwD{u$q-Rx+#pfoU_Ke7>xk$i)GA^8MQ@Wr;gDxDDK!3OGFD`iw&gowEfv-AZ?lusMGNe6p|3VQV{!$; z|F{ZRn%E8`vE^+MwH+$Kx@%ii{_`87B2?Lo{tE(#$F#nW!>wELy! zZDX?)3xRAGRt;tNTfxX-j%n}uof_DhB8@)*&S4jYk(qn=yCBC|qKh|x8onzgS2D}Y z3G0c+^v=c{t!`k$U6uV&qH@u6&>h*?{pP42Wm*{)L-p8tCM^RbWmOyzD<4GKu7MFd zKY~8NOk;y_@1Gy1g3j?kHF)##d23;~>^)Y&&6AHr5!NIUOBS%(Vwt_c&{Ug-W9p>5 zVgp!Gbs9~iReuQLgLOEc&|yhH(+yt4ck&guBN)2&5{A|aEJ@b@A(F3F7c6D5zX9D` zIZt30+EQ*?+B}Bw6>%}aWW!12=`GSUz4rtm&k)9)m>LUGLr76C{Dvpu6uqNYihWMB z=>phIE)Txw0H>x?K*9coAiM5nxHFVLyo9-tvr*rphNT&;vU?WBSnErb@@4(#Ux7a1 z=04o>7F5^F5<#9J2XLWr9f-oCp`n%uHD|`QOCWnxHN|68iC;lyVSf;-4}fb#+LMBe)fl zlAhs7MWos2~5Zq;>)xI>y2}LC*9(sa+6_{-lDrkf2JBw26A?f+>}$ zuem=0-t7EA{|0dY0%iq6PYT7fdR}+3qSpkQC0e#lT-@(a-At2pnHF46$R8b`W|6N0 z1h6hJ=+$C`6IDz~TA?Ijg2%qGf)u`Hq-k~Qcj?-EUk%1D*i*_YNwx_?6m zJ133SLC_&4O;_**v)I+hwHN{}4z9>I4;cjyWHmTqT!QB8j!sydJ|&I8qj>XEb5s+U zcqWy&0y3z&I-(NH#yL=ni`bcKu7qpE*+sHowTvwJ!JK`CXlfA&R}r!+;On@Q~KS^}k%>_0r0lFgisDXuQ*c`5m0$H|tVdX8`EN3m`h zDU#`mSm@FQbDW1ME)NpJl{?Er%D;N0C_K8w$Fw^j8{`Uf!OKCXH-?A1ro+&ThXw|3 zRN!_mH?0^?a2>X!tEpi8gaLwuyk ze!y3sLdbWIgxPrhF=~zMYgn42DMYdR@ZY*c@szTWd#qXsEu(S5Y?QW zCJ1llw?H))O0=O6cL~ujNXmmuGz1bC#tkIEP^kkPqF>!gzD%WRt+@0VJP_CQ*Xq&c_M?0217H}2_Jl$<)Sr&H28D(%g^_SK z;2rj0$Ul}nzPRR$OK-jB*UIO6{+(j;-pcVCebMyt-Y8mD^q{Sd?E?BatI;vA#D3`) ze69kJVYDc=JxXbWsZNmN0GX$VH|47i-YuaC&b1xKf{SY6ylE(3Gu8mYt&Sx)JPGLz!Z+{u77Ss(db{ag_^Wa8j}?ZaQp;=(ns(TvI_l z5k=3+xZDzyJ8fQT(+%sK7Ty^}__nnrAmCv4BR`hnZg;Zv+a|1=d*gAL*Wl#N~qL%nxaB!mhUZ z7Clb^zi}0n+EoSVudQ-7S@S1+M%A8Z}@7FXL1ByE8EsR^4R+wCZ2HpQ7qojjjr0L8rp z(HfgtrY&a%DPVrqB^n4->0BoI6C9|2QjXXY?2vNMSShOG%k?8`7?ryVu z`+&Y_1Yi(>p((Fu2C7yLkBjaS9CDBc!lk=;*HUlOLX7k&{NRa%rf+M~DJ^`Oxk54N zRd=S5&Ni2U7gEoOkD-o(QF~9e*$suuO?oGb0Z&Y4VvmbFc7IfKTuYNp+t-HRj(}X1 z3I9+5XuxMA(xLdd1|WbdsNVQg?^l27CEVGPTcQ6W^yWI)ID>hE_G$Z!SFJZp-x*29>9=f;3sSrLwX$h)JZ#gGZ%lUYRX@{ad?AA84r`gkzx5{!?>uZk}l|2xr<{D*Pd;*TMZGGkeaI z4h^RPGDcxdl?b7FTsDaN38Bn63>`~lLTJ@hgoutGL6!;^g1RL%Idc`zQ5iOP6`<0? zvZ>0*S^PI!G;6Q}GaZHT=%%&1J5WQ(o9BKU{ViG4wLl~&k5)~k;c9Bx`q3FtYN%5o zMf%5r`4nGNlwhdI75C{CatATb9*P)Hg~_k#48!7!tV|ZHAm(lw3u=RVS!6aokUQ6p zO)sK?7D{3&p^GMgMpaUxlPsnc|Ful0R&`E+21|}LPjohLWTN3{?i?v=C*5^Oxms_qPkP2+V?rhMYL~qO0>BCbD6Ht)Z*)~`!jyo&7XfP1 zF$W6uO_^U;wqJsdf$z^kVPyk+C@c^ON@JF()^k(&=^wsF&BmUfNeIvBokr%ge-EI9 zd%_}U0wP%Ys~eLKOW>n39rp(RDH_ju;+U7AO2mL2=cU!~`l=Aj@XRMin?+;&{@fRl zeNW8-vxL@S*>S=BRo@qn`=^9Atnox2n6ZkRmYdDMcx<9!4bbOxZjMh9IZDveqppWeBJx1)UfO8 zPHWM^O+|R-z7(?^-w1#Uzi1=ZHW?LURV#R5GyMS9-9v!z1eJBP2Io)pEKr_pb&~Pm zz6AnK12aPsU@-NjVf;Dl_hF!79E=TO>fE%5vaVM z&c2}CbGH9#w1FX5gb`2+jb7g!;L*2&6}B?27$^+p$D8X{7mUg!&Gfqhb?)T*uPM9# zvIFT>wkP|3WzOU>89Ec|zq@#CN{U$QTe;OE;?lD`J91v)RP+1`M?5;-Q zG`?f}^x;LKu^biHr#2HsnTJqQx+odb81xC3u>o!QpU50O0sUYAO|HHzRmHx93#a9k zr2~UeL8N5_2G~Ve7wfJD1!rUnW2S@ax?Zf(WJLMe?!d06?YOrkljjeDrjXnT?`9d=gg+_s!1qAX=i6#~VXuPH;k&e)W=$00;&p+pW6KMXr zRY6J8)s#CEZ-r1bqLXLv7vAa?UXO&BEU~l`vx1!$^hULFc)Sl;mA_Wj4vpvnD`TK) z-0~;F*p{bykd~nLv^oR1Xog-@p*?qWuo^mQR~x6Dgpb*3M~GUytyx_!Sv{^9p{7V? zfBk=b9U%(9^-2#mfKO-7nVB7r8HWKh-TD`^8jzrG1sS z!v$8?h}>l!&{IyUvI`Ypubp2Uw)cmx54xsd<+2(>D7@Sb;CPDvwR?7C&$Q+CF$G1dfNqh?(sKAws2#QpiDJI4}W=QRIu0;AspyIj#0}iJB+2tWJl` zIH7l$IdO3o>?WlXBYE$d;&iQ5q-xx%En;{Xp@?#sTO3fc~c zff$fIKgxqE;f2~h(LsZr!nZc`h_|^gp>&xL4C>@0BbdZT%2gZ~twI{S;K_06 zBi3-<3gmo@E7tf16;k_8yTTeg@(2k^l|SLh;gJuaEhk6C?O7l` zAS=gARsIC91e;l%e3s*J)_mHH z36Dzp>RLIup0g2F-r75GO!QJ5*Tz=8Cd?&V0M;aCb8&4f&xfjh%ed@E&D12TF-#s| zOWWqT65wBl9)aFXcp{4~O|EYp;OdHR5m4Ly+t=g!%Q8+8MjCp+;4gadN>+5-4XYS! zTa3_qBR SZ@kuu~#Nah5-qD1@(acYT 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≥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Űfyw#pAz)_~!y>MfXEur7U6UhkDhG2@?UMU8}}2R6=Dt zjb$~KRE9_GS3ZI1NcB@W-7-8vW?uc8m>wxfgSrr=yZ1<~A?(MUVqZ1m4Au7SQa^FweR*T+Ts5K2q<6%#de+bJ{)~Ik9+$d zIg-w2JPBQ}8O$?Ps*7Yt$nyvoK%Z?Rg*BbORH#8VU4`-+0dM8xnQTH4@ZDK^r+M5k zjOhrOQ%q0|dD2=)W=dh8p9YZ(N$z0m7yF^k(7eO21SE~Dj0J~PX8OE1J|y6A3yRC; zJLmb`9#H|m1X)&LD>l|)Ky?wf>&5&sU3fvPH5#3@)Vi35X6!l)7 zIsu1 z&O{1TmJW<>#__ppK-p(o4KqmTVRs})fyfY9$vH8UnDDK+JekNQ2BjVW8(SX|Ju!$7 zVY3qc`mb=9de=T&2g-g-@MMO0^G$H|4XI3`tw%Ehe;V@6D0Cb@4cVI^?{1QBu6DiAnSs0F$ zQin4Ba2sJn@ua|+&z0_aI-uM%r!-79^9XT{b}FZtmi}I2KzqbNDhJfjP4%!hc8pS9 znZZ$g%SFCST!nQ;=QFkUW!xUf8y_oZd693x#XA@Z;omhXoWxOqobzgwH=5g)rNOU~ zOy6jV$0MkbF)IfNXVJHz-QKB{D0SHp(&m3#3y+Wb7)YD&QoQi|f=DacBY*zt&{F-r z`kG~&p^>?b+V&jNHr0XIenm$n4+OW81NKC*~)_ zG)Ri9MmFbza^^LQXEodYlhaO?xoJsy>*8|gB3_53wTrk;3)9pMQ;KfdaJq--R0y92 zi?4{FH?Lx&=Vk2BVLao_NDSo`%zIY4mEGg7=Kw;K%brcH7%ij@e~r%uFO zq?yX{3qZDy6qF`_4TgM5T;2=e1K%FNm3G))keJY4iTK#KVo(BCfLt2#>kR3U$eJzC zgO&aMs6REqJ!_WkG%>>#Vl*YpSkHs}_O@=&r`Y@f&ij?ym~RoI6cdO1z6&k3&%;B4 zX)ktoN-kPGfCj@YFpe~xJ09KUHlaUU#*uaH7TReoz_KgVRh*{L?i2c3%mSIfr5(x2 z-EGsMgZ`C3T}cDVqlAB4Abr%6wAIvle8i~8@03;znZPTp+Ghxs&zia_#V36lXI1%& zgZBmD*WNVv@6;^ou5dL;>ffQasPDnTlg*?GTQ9Ta7w`V4l|Sp+^7|j!3B|DUXI`Z| z2oswbGmmxXL)*mJjlrcJ6-saKdtD9QJUG346^jPd>(5tuWy5Khyd4wGRT!^mwT*|A z0EZKAmahf&C&UTtT95&Ly(G5~N1HB@HxM+c82_BvZYF}`*Bx0%o&2DXs;lOuKmKUF z@V)U)kWwqr2v2E1KL3259|HzToxmSc);iEwKq3hSq?zaSfo5ida?#sF7%lA~b&c79822--XhCwT z0rQE?>pe#dMz%g87F9UD4BaQPXPi5A z=n0FGE7d6hwhtz?@B zFIx9TL+A?cgRoV9%emeG$!STW&{jn{WP0C({PLKMs}$|Q0T*}wWt(2V&^ zg8PPXsS`TcTrhgPUa{cEX26$;JQk;Y9S1tB^1nDWxlr?+*k&(jTeam^&wmt2bd*A` zE5jVWRbyV@d=ToWHyaNLf`VMvLEU8%p6^iJl= zS5#50<4qeG5kx?x+_nbyg0Wv4C2UWoISn7SVkLzYtHnbI`)jM}>!A*7y)I)}lK7Ho zGoYsJpBV4&eFImyN^!uQOnLL9l;O%#C9*ZbB-JK*^uFwx7wSoeEGECaC{*V%nNon^m)6soRr=V;p>Lg zpu+wIX^G1SR`PcA6ZsNGwrU^Wmhl&@b&`!BdglN%JK)>$EGv;adiR*eV#Pq8> z=Trc&*}RXWf2TgwoMxleb54tdp&#t8xPrXK^^ zkcdBqvIYq!{GGy|u9uk|3j1Owzee(zFlPRZEA*BN)0Ot7)(dMwu$4TUVX^u|7b+T( zen_=KfRb!gq8icF;j1P;lpAkSIJv}9FT}9R zc6$+r41Bqh=AGwHCjI6i`J>kmwn2OquJ;-B^6*K?kVv2mtb{u!SZ6E1P~ZQYuzP1J z<8V?sgx;CT#e)7koYtz6ct|GB=?M!dft{StUnE)pm`=u0yQeUBf)KIW7AT%p0KYE1 z`B7Tn7YxEni@*y;TZX+OVtNQB3^0Nf{874BNCpHMt%Ck)6vSg#Fw^Svu!p(mB4>{k zsAMVC^*i>oH3YkqeL+mT;);0HkC*lqL2r})$##;M&Vb#PXf-)evX)@1L=GgFa8Nxm zamK?kf&$*mLwmc*qoM^^!4X_R*Q!gbx?!wOJo<~B0b`?eMH*6dMdawVp{hs0Ns(?F zAd>-l1q^s}G1zf1>9q8ku#hs%HRK%LGHb(=7)iJ2Gbw>L-CvcH;xWAv`2jl zla7~h%&HM*=q_ijF}5Ncu>GGKpC7@k(`mrc&pIKrxI4kL%ZJpr5lSoFdmX-iS}qJ> zUOwj(frHy$!hfK=Q#_Nnix&8;P`THpVJF_#i&ZW`$$vJm+FC z*^pT;f+nbFuP>}NCDK^vx$Aa&aJRcZ_Vwy`qa7_b?Nl)CYvb1WIC0$ai{dE5h-+yg zmoJc>tZmFS;xT~!DOG73M=jcrZyNT`pAgFQk`yzg(91kG5aPZos4sD9jUT#a483GWqT-^(02V-e^W=p zHlO4)htj0C1pln9y*At?TyXG&R72QXZ*&s(yff#vr^Pzc0s+LoM-$npsp@LFD4a*n zui?C1)2?5(P(2iv2k+;vd7q}*y@b1juccpoMUht;a~@qT^wJY0`5TPynnoQ|n5qM6 zr-m5fJ`GoBx4=RIxBGYfWQ|5DR=xXc0Y&K99MOE$#}^oFEgKxTI>Y6TTmzeE&l|Os z`Jl#pcRoQ-;>C2lm{t+`*?+=0UAU*xb2Umcsnm~iX#Bs<0+<^04Q0MvK}w!tS~sNj zS=&ARBv=z+X=k$3~bu5ROD9-F(7BJA=-7E zOgrw^4gDkFW;s_+;kboV^vD@D#mrmckz)(&T;KhlUZI_Rx41YJ_`SB~9Vs}4&9Yty zlg5fNR+5f<&GgW%wMN?J(-I5qZ(#pDp%$jTJ6r5)(t=8h`|1*-*PuS@I{)%AIOYaV9xF?iG3sO&|2&5_;L15 z<)7J)%-Phk=KL$loI88XW9Sgo!yDMzeX6n>6QsY2V5N3wu3b#}Zc0;U1sz?kJd$%= z<=gd@`d$_(LVyepa6{ZwurB^%vAQ$`a`n)_WbFLIr&DNu$?5y3up}qZgM^_w>fiXy zKdM>?y`bHw&%TJW&iyb_7*fgwxe6P?)s9jbsZL9NXv}_(7Ip#LO7YbYkVP)#mXem* zSr8Gi3U{e$VYd7%oIwlo-`o!!E`e{C4VL!hn{wyLjq>DfD6~~0)cP>Y9nb)fH3zu3`Sho~s z5yD=_=a>k#byMSm@@n9L3X|K$F1|c39@^irop3mt?5}+y)jD2{K0Rpa?_G^Il?lHU@>&L+9rFZTZ~Bb6#R_1I^k| z-VfY5wLYd^d==_$0=jraBYoxrWemhyEe9PDTt6CbDY#a^71%9DPDRSNJ5s^3Ad?T( zNEZtN(}>$~EI>j*AVv)mLDc;k2lyPvFVD0Yl-01cAaSw9j+L#XoZqK>{P)4@YdI_e}#b4Jryceb@5gUU$+xY^tLMfQ`Y+=DEoc5kr| z#P(G4C@c)ciFHf1wLeJV)vXXtYQHq#GJoxYpS%^5Ae*btJ5h41#|ePy%}CTzv6@kd zgXA`-il(=~lghcx5ft6lt_)EzW-Q*Ebu-tMBDtG4iBSXrS5-yNJ;qO=%X#DTnN!?} zh0dWM2a3!O#4pHLbdw_+*N2B1byv)(eoG(ydatKtdCz8)4Z=LyG0)Xe=WwqQ=<{-C!v2wn`=3ys^LRu%xq3 z26F(6u_1eHD|%f_!Z_SI-)_UAIi=6f-yYw)*538j*{qKz(J?bUj9QZ@ZL=S;&(w3o zsGSJQi(=X~(s_z=;eABwg)u*%xL4Us-AoM5@AUe&>#Y2<%m|}2`!Ndg0te`4A$Xi( zh?%Rlvi;)(mL3s}!bchCyocr$rgP8k>j@c;)I{$&I~E}iiI!6z6Ro{pPR5n zCR-7)^KRffyl9Cfr9B7SkKeU@FVqhhwq=o4{LL#s{n zq-1p_7vt5LLFe}qI|?+@MkE)6kNBE+*&gxW1M4GZnn~;r4j!}V=(vcgd=7haSL64_ zTT)=wy{ZCBXP!Mp=V$(hyTv8A5&G8i4MO1Z@^N;USNaGv5EClY5H`ws3Eu0SUOs$u zRIQwm@rS--lLNN*RO>$cV`X^_$ZO10Pe$fVgqoi56ldgpr*i-vB7WJ(w5lAkVR@^HjPR{`jtNaxXG;n2b0c|S%3I4_gKSN&;>tO zX`Hr1&&MI%irk!+E~dTFTwTh}k?}#@DvbI|(zsk4*Zy zo7@Tf6&>|9a?M9%2`X{H)}=P3_Ht&u26EuX7_~d^S1hv!{(~kfL=S{Z9Gf$R^tFan zVWIayYll7AFxcnqzmPw?9+27T2xNGf&S0}tnde;tq+7q#%}t$uI9IYx%$6*k5MNHv z9py>cP*;A68OF==INdU7E;qxJrrJvi^;>@Xq0GH$@>YS67lG$(Pq-RA-WrT zfEJKv@3rA_^ucAdo3lDIlaH@~D{P#gh2%5fP-0g01Zfj_&1mu`8)X0u%nb+SS-#d*3#|VvXJ$;5M^L#tMzaH(vfU0_YH~%E|2VQDT0#?onfxwN+>V@Q}tWs1N^l41dD$mh8Yixb+1bW>AY*i z(aPrfUsoC!BEiR{df{#3{f*3sv~F}3>F+|}2rqc?6ry3jTr{b-{e4O$|0WE5fq!<{ zBx=BP&+o~dPQm^R%|E=2j9HbW-xcSmGNno@gnm+A+M2$(7(XGM0RL}ytfc9K%rZDI z#v`~-pUS&hQ41D*gWp{c*<{rn=50_3$T4gVKD^2#YqsD_>?s+E`D=r*lmPmlC1+g@Uyqk0kY!9b zQG%+2FF|nJ4DtEwMq@`eCZNFyav!%pV2Hg^ znVa(5@LW(kQBo4D%M9!TH?M?7#f^96cp&*3^eh4&hVH={s}8Wf2t zKgzJgr$JbM6~;%L7CFN#&x`5RD9*a>MXE0hlvN|dzK5X6HBEV073J3s^MPzd!j!X-)a3}E)KY-tzl96Y3!zOa@E2he5lzK(nS^Vh zc5%o6H&fnQ^RJ9-mcJ1K6I^{T0;v_u9Ob%O)!f?J+S|Hx*Uc%i@3X!73NyA9KoDtT zq007oi6T!4jpH@Nd#EG@nHEV>o2EHh-9(8u(7@g|AuAe#K45tKA@Ew&Xg+v3LNq9H zJG~2WAp(|RkEg+!{~w;W=bzC-ptx5ejisc1vqOFIk9L4`%J6qa=X+d717(u%Ys9s` z{A_yIpqd5Kk&ACW5BCFGS?VC)gPrQXE3FC1f$!D0SVpS@leZfKORmHH^^o3XKB&P87L~~H` ziSbtGN@9%feg7qnKULGzW$0OR`D*mfdN^^JZ_Rwz+H~kok7$Fb#7Ut4Rr~zy=kA~> z2h|S{Q6QWM^~5{je7Td}r9uipW0tj2{IH8rx7WGRu$u@ZVN&JPEw7qGH|M_Je zf0Iv%(-FPu+iy5)K$qSrhfc|j?x*q3h>_$w@Et5jRJuurn6KU%*^}*y7gfe6+O~#Tx;~u`RwGVs1t)4Pq5mf85gN9&DGg-{pQR* za_l8}I`jR&-;{f5rMPaSRT>a4d+U#~vpT(_zrlXMZmBxnr+|5IJQ5ql%!APD^H?AS zEg}Xs!(!#}q)$*|6#$Are^W&E)tqR1H*gTL^by92<@(F%!Xjt2r{RmFs&z%)LN88R6&xLYnaRP^?BXUTbPiZ zUQM-OT;2=dGzd>=>l?%!Q%Qgju9Ljqz}4wp%iLD*t*I~37s8cl@`#Sh1Z8lp*)T=} zA+awt&SIkSF@l!{vHy29tACu_CHs+@ZJ**Z||sA!Hyg(?B@AIHRwsAeyN zdU#<4T&AvD{c0Zr4;S=nN|l72Y#k5*(0F6iM8O4)P#B%~Izs3*f zVI}z$PTkD+t2x@LeiPU0?Z{#&6dn@Ks>tw{cnBLLp2v|DrcKQUy#G_Y?dernjg4r_ zc5tdtRJPz!!I@m!0qJK8GUX#OvdZt9)o=_^=-sWQZoSN)cq@g)7JaJ(k9RZ`6Wv$0AEV0hA zRA;gw{PFr&N$UN`NIrQvMjRESVZA5^c_FY2=@r0m60isx1VEs-tyTQ(Ei{1dK6pzHkOVD);ADEV}L3h$7RKfD2c-eW6xUpj=qJ znJr-(&=a!#!D%;Jq>e}r_?dFQUZ-{cCTdaaJzaYvb=Mi*q@ThA1FeK)71C>+ViF_W zB4G9_Rn#F6E=SW+YJZ5fi;+eD*7Xl|2;J zojR*iA6Y(WW$TL;iIF zXhvXlz^j|O`sXLCZXK+bpMM1Ae4knNBc_ZwR!#bEDA!8}kT*LHPck|SOAuF-jQPPF z&uuBA{Qhot<13FdKfdp5T>CmgndMpEtE!c2#Kb)u=%+FRMYcN?N$*xFsJ=n-u*jpC z`+STHE>CZEx4FE$E)0EMX*u&q63;a^3<^x~?__VdaA*jNp;LuJ9;;#0k<9dQPs-zn zM}#8&HD4Ct$Im=q!MM}mt-U0w8z-J$)LfLfPP|4PQ{+*bZKr(!aT*{rss@C`#AH!_ z9$jc~+XczSA7&=P=W6?GMn)M+B(&6?h!7TRd07UpK0*M-vQYB);qkQa{Gt80+D662 z!Y_pHU{8JtP&tSF?kt~xK^2GqV88Q}-C;xlyo^<`FwK-Tq0njD)V>aYoX+8{*AJ=O z$ll5flMxO!nDJDOwY+@zPR0*c_UZdS5Q1Da?z)j%F*Oe@#$Op{U^9u#rHx)NiEL+o zb7H+$&I%!*3U~I5b9P-KYKmd*tK}~_9FX@i>^)dvLZ2rjg(yF^9sd3M`FRTd_1VQ4 zQGlym-P2|YFcFm2E_$-#MOX+`TOR~dpDrQZPJA@fraI>`+0ddN-8yloB1Y*LCjtO8 zRAT`sd!M}7Fnxl32AUr}MfYuj*FXD*R@PlQck0}#@irlA;`(OB?zdblj#H)eW#;QR z98?5UGb*TJp0lHq-pYnjmjrQwiyhuN5os}YG%P>xvTSoSl>ZUC9yk=Vw;sjhD1{_* zlck|_ux>tPnBj9ZuE@7aFtN$(E2a6UQsh08#LHc2|FT3{+`foPE|z)gQtva{b4G(S z7dXf{h7S={mT&I3+8WY@_-whA2f8cuJJD=t*K<<$^5$z_J)UHVVI3-Zt0EG2MogN} zA@N1c{KjD0BC`-wg-KuySVda=fW7rjiFv_97sweaFaspM9CxtM8*q zl=bPal+da30n6Y<4{b4(Tgltl%;cqP&P4}W5WnNiy5YSW#y6&kgAO~gURTS#+Ves4 zBehYDAIs2ON9$kW#71m`%C-bTw2mIu@fuqX(~CsG-7Z(c#!dL41i)Qh&;>-kIA|N~ z!T0X8al8oBVmA}mk`=GU2ig8|()5IaIoe4aKev{l%&{GR@AO`y3g$$Cq5JCr$~PkB zM%$E5YwPQhA4w6#Flsp*==vNWBQ z4x|sBDM$W1?@exvDEk@zwnq>o6iaLDU(E>Xrdas;4xW3Lbx}q{1LUKL(c2~bX`cOo zEPZ`tOdV0T_Q9b@ad&qq4#nXB#i4j{cc9QZS&v_8?Wh-UN z6cJQl@>=gEv}<*V1!a5QZ%W7EDb=t@R_e3V&a)maVblmqB$uM+-InK}c=zpX0&j`j zE3{*^CaEY>t&;CFfxOm|i=C-8>M&YF_Nr?VwGD}}Dr)Zh6$W~Hx-Ab#1`W84a@_V0 zz}cqL(iX3gMuIInY`(jzwF6_zLC{JXywtkx|2pkHB$$GYGlwhKHG3d`fZ-{H6JYVJ zccf~${mbIBo?=RFs_B`z`*K0^DL0%_*hF+xKCEt2)TWK`JN?3VB*asRsA{Tc$>eIq zS8%MM6ah5w5|)%ZcQ&+;U%)Rik2-Fa3!Oke3bIK-Cc#rWeubsuAuj+^h$0Q6eC`vt z_ykm#MzL!acbWTjE}PwZ`?p8!UyF{u;nG`SPMe=$VYCO4MpRipli0TWuw+_1aPk7} zW%KxZ%2u*O{-CYw;WKugghGOluE5_0Xf-%e=8G(MC-58^mQEjEqnj90#>2w>=KY?Hcx(yLJwj4a0++-fg;46*{k6XBpz) zevxI8{@Kk?^Xo3Qk9|$!#GzB#BDsk~V%jwu)>>(u9>UMrE&4WcXCa>pUkfcJt%qCY z5vbdnLNI~HJ!eI1d0Iynqh5c%@WP9c=`dXHU)DKicB9m-d~PJe0HbTAGwPN^#U1i+ zrjDOc!mQb)OSZ=fAOC%l_;NY&{pzFt#0T%`SM_37T3*3%fBeIKh6}?O1jk9p7OUDnWjCLR8#v&Kql_zKYWJG${<}@R6-85jumGMlTc8UjpGhJ~U@%jiG8` z>NWl+1l+7!UD#+;O%Eno;oHQ`qA?zbSK)I0?&l)tIlo8qDxmDSdz9&r#|r2boER;> zXii!?`6KQOTQ?-h>G7z)dK;bX7;ab~?@CcEPNmL6Od+*uCt))%PuS|N`2#)ny~XJE zEA_Hj>O6B5^h7sS3Sm#?perpXkL|*dm-ZkTQ6t~mZ6N?z^>v07Ee0<0U5m2P)e=>| zJYJje58Mz-XL`uJU@w$>9sYJTi{-+g78t=6GDZ%XS9w=XfRa}%PE+rfv=BOaTEx3Z z8>jv0sB8G!#?tuhbOLR3!e-@HQFU@s)tf0pgWd}BN(@h^t6-BKFlUL00sX21J``j5sb@{Fd z54ec*2&LmQ3ZQ(AJv**d}gSBPz+P}opEn6k)P|Dvvw@~Yf zj=+3F0KuQj3Q#Jd3;0t8+-rmRVTFxUR!Sm7qk-iVd613Ym zgdW3wg@EV6yd8hMEnaL9QjCv81j0w|jXR87A82<>ocwyS%=oTc0Sxf_BC?yA^LuEc zM9O>%(ISqP7Zo$7gM68J+ zVzQZBKBj6|O;pBFgE#wpI)AHtvhT`m*H~F^q~@MM(K=Z)e7 z176MEKPqh6P1b%U`bLL41Q-SNT`}OrI!dL3Y{y2t_HGzVg=U7`*xVhy`sd-2U86t% zK+aAFS43+rJ3Vzrnz)WH^XCO_4J#;9H9?4*Uk^%r6RCM8B zR*QBUwx_umRJAmuhYD&^SV_KyC(E@O(^gCt|5cnExs+?I+|YSn1{I#N&k`vex)*^t zIhLAkP>=o_AIEGdqV&!dIei@5vj~zOr4G)Swq~`_4#FV7){v8)%Prxp1G+=;jv$I1 zw}c;GDL}tb&vEzA>3a`w8F(LRK1Jed0YqZgTaFa8NUZL3w#=NAINcdkKiH}yHLW)c z5^d+yA4wnh!?O^owB~StJwz_W1)U8i&-dq11fFEKwx;8*<+>qpj}3t^b`e+)xB%(%)bL=1`u!vU!Gs?s;bD4;ZDOLhEc|M_9gTLMEM@H0}%X@ z6`hX{jG5^s-B$E)IF)0les*Iy`&xHglF8>U|8Q?tA^w#<#GIia_Y_-=B_jkkJZeA8 z{Z*#d_5xckw`!6zwBazXeQz``$IC6rL-UGsMj44`-ZGb5AL=w7&iT+Rt44j{3ck1n zFV@L6$8~e2UJaUg;J3wE?Z$lA;GxI1U+o(TeyrwsHU{O&*UCw8!pMqWc2T%7qEKpe zTExh&;c#oI1=p0+SUGD)?QRk`BQi<|`rSpf(n?fK7VbBo zRw1@=$d;yy0YC0kjpupQVIZqJkw6%&Mu{Y+xY#d=U2cp|f@=kbja?#`%nFKczb$ch zF+5_F%$LW=MD*fMA>-I2wMf5&6$&x zTJUBWmrgFk?ueBSQGq+<9xilidg*zHll&?$Wc6PS_E@~H}pvh~&IhE|;g zuD#jEy|2S`y;j>l809l0b#&v5WdHPH`}kLlAFkm^Rdg+yU>p2>5wt3?ve6Pc$%Zq4 z1+=e7Z(#IVqKV30#Ku|RNaLDmP|!@A4PI+J3B;}yob}-i_HzObnmZcA9_{3nwh>?R%D(82BqC4Y<0bT{Y@ftx& zKyN*PucNC^*R6=zDrmW&#A?Y5EQ%b`+CActEs*+Hf%1dZb6Q+7nK}v6u<^d5E4+;T zRrV_}b|hfLZDPq&Jo{cnN~h#lcP5qqFCL0i&S&NwGq95wh#0l3T~@h~SK>IHX((4r zXzg2p9jszV`{{nmPp_ou3|wcJ7baySC9j7{$+#6KvQ>>?ohzeFaCr(-NBHEKcw=Z_Ld zga!?Yo^r*$XT4CkP`V=eO3m;6tlxPfcZ^{d?WB=H`FiQQniZ#_Gk zGwxvN)p9F8We*!Q$$Kj1B%*3L6BTTe|8*}YXi9}$me@YKgi?!^?4}}zhl3|FHc1FJ z3vnPTI~yeM)3#o8pb?+X>#W0810h(Sn|@t2_)8cRSuQAd8Aiv5dd&S) z8+Bkcv#pHp79OIOOFPsHM3?7~i(`jRr+w2$;n)NG?@r;(k2&VCLpUSna3i3ijUlZg zv}_2N2o3`~BFEEY&6hViG+TC^Pg>_Xn82SQ`xL9jxY(~}CKS3CfJ z4SBp|!Zqz#?5^nf)PNRtBT{4T+SgghEf7aaA)_ewDDWlSS&gzcpnGuX90EqVNJQp3u|F`EfMHTpux^OCQob4XHw$Tn3Rm6H*-X zv9XsDxf*-&zXZoyNV~Hddg9Giv6|90Bl@Ie^e6cJ{-Gluyq2VBtN9)eTJvgjmY367 z-cNX7QwU7YNUr;4&z9g48kBwF%tX%e8!ElJcdu8Ki8B`TVHr>x^P$CHS$W>yI`;n0gVB?>SuHPn zoYW^@%VR&~;4z_oaxNXq`zcu#^>n{+1xGxhRZmt~0l`ZBAzX;LI1Q~5>#EdzO)A7m zR2bLctoPw>vqP7my5Y`btpZWXZ{c=913BzJFuah-G|-X6%eub}lh;A**Q<90Jy2uy zdO_I~ytI+%H{q;T_KFCa88j*DLP>a&ofz9S=nJ2H>Vv82dh%%cXdyl`YA%Y|?4Je> zy4{6B1FgrN?kQIi9tkj5H+!`jkFHZ#ruOya3ZwrHIqI(^z~bDd?+J^2bt|>*ng=vm^`Xb1y^WP5EfeKXS@i}Ch zxfkR6NG&!P0K=WXxB>}Q8Cb8Co34SPT=uNnUsK`m%>AbHSL|b)mG0`+Qw?MGd)iqb z*$FX?3`vuoF{dLB63<8Ue?NT@20bntz;r>(NnK!WGgNmMNYmMl@}?PG@fQ_xxfZ|w zJ#>@&DmK~Rusz!&sf@%h=pG3Jo9;J)yaQKVwi>-?yWHt_Zrj&wuc3O2(%Q%qX#>;o$jhkK@d2w{Z2O}LHz_CAHttK-{16-f zJ2Xb4$p)#OO>&Sm@Hj1>I`HQF$Zw!DHZ6t=QI<{P1}<_CF?zNxoz8iUyRD{}cHO#v zJUa2Ljq7Lf?$}e!J?~*&xqxlM7hu^zAqzn2>J)LHdbWQ-vCDqAf}qlMlvV@MhvTCT z`wE|xH|@rvz(|9I^&B{dAIaNlIyqsZXP|M)U}EMCM31NP#XEu(VN`pp_>P)}sveaX zrEAT%2XWaqA2yWjO<@}2N&Of!Ca{8dUYQ{C@=*)+06JZbj#^h=#Kjn5SO`XG-H8p= zE)ekbST&Lz$%@E_VowZ_nI8%0Y#t1kX&KM2kk#JeIv*J2g${fSfiLQj8&jUPK}Ck} z<{H&`eQ~V4)lzq)E!f;ruwL4Q^Y2{EU1gXi`5~!X- zY8pzdujRfoMqthVco3{DBCk90Z zMS4`+?)ENq@ty*G^6-AUu)8VuYL(=ae24eZ@c?IQ#6f>#RG>~cJ2lH3H!-q)=xK3X z7bBQ2+DSOZmKifS-P<~qHTE)6k&!vAakQ&Xo3*afS!hl1hTtu)5vC?63+n09SQyCT z7N*@@rZ_+Ij@aIxrsA#J8D|&MZc>uWSx8|nP)~WSYx@!t`)k1QX;#-y%ZHZ8%-}GmGsJs?&^eOogFxq?w?FH?2jNz?X2x=6=7_q{uZa$guZ?oEYDr5w-y8yrFUp0a3ij#qo5babw`l!dHFk$n@Vw#(%y{o zpPv?`&8)3;T0jU~7^T5F$g>BW-Q+8tE@pS;m!~HWzUyIb>P3q;+)mzWX2mtkYko{7 zjkng#$Y2y^O7_E`=+?zC>2)J82XaSr3q2hWZZM2B;5Gfvogxmgz}oL{F0+`V@skq9 zkYQ|k8!gE9(Q`N6xva3sPn;lpjcas_94VOPA16daL^fWVt{vTj49PWHTlDZiuP$G4l_!4wZGl62p`fa~h!3KTA!ZFDl2kyUS)S z1x;=VVI8yOvCjFc%33iYhdsStei~b@X(qO~Iq7^-0PD9UEQg8bEF z@NvfBhDFWrb>?$2V`Jrsae`uc6$0ezoF#^d)YLMm_1~iUi<6l|aJ<)v&1YTQ!ITQ) zdD?@mdSe7ax)jmJ5o?tMSIv4Y$m6m$c*=_9LT@@aKIQF5Sw?>1eTBb3tUA2p4-v;+ z0h^)o6(M_1i~Nh?U70Y4=lJXG+33ArpuE>oa_gn+BzVungQXv;m**awhqAgh_~Z@y zIIrw1Zl5VI2CI;~8P9ln+g zkI5;M5|@TfCuD+7@As^uhBO)WH&jL_vloy+<^U$FsY`dA27^bt&D2=*ztRPi1L48@ z>JrT&3hn(IXTwPR5y_E@GbvbbndPo079~8NX67px)2u+xgkF_(>P;aYd~1wcxCi6V zqpMQ3i=`Q^$!ULZq66$`v_*2`Rn(y#b^s9k!d0-W#gLAWs3lm~lhIIZEf5>vr{c|= z;CC`)bE(M8zEgr=%z<}am8$rc1$HXwtIjXGUM0(5P5XNYm;0=%lDsE4;&}(K1YXVT zv-}6)%ayF$sJP&~&u7ms)KU}J86SjE)2HPe+7+Q=!lJwP%uR$?8ImpK`zf4MH1S&& zFe(PiEh=;gur4mR>VEXtR(j2f+MTlCZyR&p)+Gp|0IjN}os2455vyIfg@V)%PfKXS zP-jIjHyBq$Mi9Wy4gfz~KxiAn9|U~}j&Q(aUO=VG&+Q2#T}X2Tp<*%XH4Vd4egJKk zNFo`Ak9r49j~1A63tK3;ebD4O62H^tWuKzC;fk2?>aSOFO}4)ue6pC{VN-XKfG{b! zKU%&zD#xUZ>0SO&@DFHZ_?WBMNX^aW!%VY|5fUx8kgV8+@TuRK6}BL- zg$s*aUXvwtKBZ8PaLwHL4>FOYIbUL>eYf*^#eSoDIukoXeyf@!v&`Z&`e3jkJh)M@ zh!43wQznN_q_2{(<;(u`rN8`cDMn0VrggK(nn0aCd>(3F4lrl0OM%*W#TML%lT9W# zQ0fLVyBr95qVmW z%z`p0yXww6#oXh~N%W5NMl5@JkT1CIK3)xtiMN$o7{M;E!tF+B-q-Ft?W^c z?)mA-)=hGQdJnG|+dz|0tVIBF(Ia%;b<2&??p!aWl*Tn~3Q16t`AqS0$P8Pfu^rjj zvxL^8x!Z(57uLz~q>#A9F@KO*(j=;2Z{2oV?XAW_p+)&yTIVBnTt$2+=d|eaAGfO~b!o_+1_wJuvS z<-X3ZYK8A9$*?R_892kXNPg7QLF|dtSm+*dKjrxKcHlAzHQ;i*9LZixZ;<8IyZC?F zxK8VSOgvL@>ezd;DHjoBlTT7RkNxg<75m=7i8A@VIZm96S>iaiVLy!nLO3iw?r#)K zgbyh=z4zK+wJFsLFBGG=aKF_f9p}m%Z;(tv4}Nq0jE6bbi^O+`>k=I9MXNFpL9^X! zVm{v4t=&H%FZQT~+HtW#A?9rQRz@Vk6!Q)M+}?lh3gNi_x#m4gq*aw8(WkJ$0K*M@ z?VzG-wro~e6mn$Jk%SR?n+jj9+LYw1bROo4jae94fvFXUQWTbQn-x0pQmUg9)IEAV zoEb5>x9?v?omy8vZLU-$D;e=mI(v|=LcFZuLy-~fLcGL2Zht`HTVS#CR`e^CIC8pn z61P1}R?v;G4vkU8LwE5|Cn{{42Ql``8MBi}~eE z1wgY?C8!GW$wzrhi|k|A%@=R6!hJNksdSD061|BB1;Z5SA!f%sO5;6|>kl%=ssoeY z+t`3w0p(|t!p}cRyW;9bnf-_*X+`;doxS0~J<;wzZTjWcFB&eyN-X7j<}o-vi*GTj zqka>|cx>`J7g1}c+NfHeK%p!wkb~E?qNbwy{m7h2OCUtn_m1vW%2mSQ)FMWN+ELZa z|K&CBKKJCU;a5b2M`x+V#AG^ao$ubVG&Lrs>R0(jUb|{{|5Tr?qD|1JJ|=#zP}D`Y zh7BO?l(MvdTLYYfq@qr81H|_oRNqt>U%_YfujAZs!tZ=tSMN8InYo?y>F~qLX@r>Q z%hU+#9y}!to^Unah`w_3TdNWpaY>DVE7+i)eCk;leKfdC@&$Kl>+9ehNwOfE~x&~Mpsw#jrE zaDSZKL!~fMmYNOpkxX@LF&<8`akS`=&4*+z`c^fuok_bxZxHvXpG<;Jv+8okxb#6uk6x^ z$)$!x7l$Shb*v^U^dr+&_VnL%`ZtY_jb^(3L04+!pcALXoj?0a{Yba0Ro6hW8SJp@ zg3g)%XGJ`O3<6F(N(ZIF{!AR8Mj4h>C(xp}SW4RV%W*57Fp|QZOLy%bWM>H=R%&^q z3($3BNbnghU-UAQ>V#gh6_!rgxsj~2HDzvCD3F$0tAaqnrk1`40mg%@kV?i9_A(UY ze9!@FCDABW94bLxky?vS-+4(A%*>D`&FNCFGjpAV_yp5Le-jgHW zO#N;HH`}0W4jPL{7vK5hz(Lqrc_y$^u6wQ_Uix@%2y*-jOzKM>@mu~`w4duU^)2#} z&$f{ovY-fk56@_YsL%T%>jY|`93&-o$9I6;8y$4t*_qHWH z^}-c9W6)s}l1qN%pH$YfA0+!F(q2Gyc=k~1(!@tCGE1?`W4nLJ1vOL{Jjpy#Ujf1W zgpm@&uh0roN_%9TVhX*!d^{6Z{9YT$r6dbK`Iso$M@iSB{FO9Mw@GZWa4;X-2uXw^ z^u(@>qIyWSnW?KV=+)=P9?N2{^G=Fqmj zJm`YWd<}@V>g+%X7-QZEj*dy;8RqcAu|7b>4-S45BQ*Hhob0mW6}#PRe;uLdAP^Qs zv9>W%il_GLx%AdfUtweY#SJPm6Q*g7D(@@@x8JB@VzVS+fvG=3%EM*r#VL0&0ex+O zvW0$T9=V0Bq)6@$_hesLlm{V(l$nm7l(nZ4*}OG`4H{kSK4BzOX_St{iUZ5+*s!Oz zu(=k)l>jsS1-ynYCt!C9>nDr9dNcC(Eln_8<12fKG60h-DK-SP5M3MIy|f;iKQiY2 z3lap9;ShbRZB>1$7KQk_w`OOgA&aMNHsFV~E!=Z%#=QvkuaL@m1KZ!AoD^&!u84HX zRS#vgw%(vwXRSGropP^)p%(eY{&QY&NvIw>Me)xMG|_stF>Rg$f{)0SS!_rs!9+Rycsw z!@E_*vjR8)P?P^ye3>)wJ^)O#&J*ajh6F{fzgL*GeEc5AK>tU)i~9RODat@WV*&sG zc)*~AwtVR~t6FXd008`PBLfHl7S1L%7Ph8#%$81ewyNI|08rRIu0PUC986u@5CIU- z2T%aOKZT2uwoET8<{JaWgpFJ}m)xCcxi^|f+eW|xYOB#lSFO~S1-83&fh;5c4V5dM z2{PW;0Y8*q6f%zdOH)CfHaT71e&x<}u;_6j_r6CF3-HU8nq;x{`tGGi4@LQ^nMn&K zj4({w@%zmc$Noz6lTl1-kx-hofk2@Iz_#0cz;A3wwGRf!4u^r@1Q+U;hV%NBcVB(c z`&Qw}N}+2{{+YQ&?bX2kx9vO&5E^d5O)jK)B0Q8tK8C<_}b+UyXP2@3=}v@ zb)3Y-JP90G2w3o|aM9YvFdKt$PoivH;$QNF#Ou%#1@wflEeYycMpe;^#9??bkDgf6 zFnZ;<2#(@J`u{*`e@6la z0M-4!qwp`dPycuqfbs$N&&|ID%)j9P093%M2qgjl;A~-S!lL^911j?WhSIjwmj7?4 z|7i9vD5L*C$+B|(3y#^>^~e9nkN-Bb@gvbnEITRiH(&q;2Jis<|F79U3@!R^IAzw4 zOY9$l+0ez-*xKZu>OO(~PyGL#$p7s~%p;ipnbsd3_(xj*FO^|n|6w)E$IAQ&Li{nz H$J74 0 + + +def test_tpkx_minLOD_not_zero(): + ds = gdal.Open("data/esric/Usa_lod5.tpkx") + gt = ds.GetGeoTransform() + # Corresponds to lon=-100 lat=40 + X = -11131949 + Y = 4865942 + x = (X - gt[0]) / gt[1] + y = (Y - gt[3]) / gt[5] + assert ds.GetRasterBand(1).ReadRaster(x, y, 1, 1) != b"\0" diff --git a/frmts/esric/esric_dataset.cpp b/frmts/esric/esric_dataset.cpp index 52880d6875a1..8c1d11eff8b4 100644 --- a/frmts/esric/esric_dataset.cpp +++ b/frmts/esric/esric_dataset.cpp @@ -216,6 +216,7 @@ class ECDataset final : public GDALDataset CPLErr InitializeFromJSON(const CPLJSONObject &oRoot); CPLString compression; std::vector resolutions; + int m_nMinLOD = 0; OGRSpatialReference oSRS; std::vector tilebuffer; // Last read tile, decompressed std::vector filebuffer; // raw tile buffer @@ -368,19 +369,20 @@ CPLErr ECDataset::InitializeFromJSON(const CPLJSONObject &oRoot) if (TSZ != oRoot.GetInteger("tileInfo/cols")) throw CPLString("Non-square tiles are not supported"); - auto oLODs = oRoot.GetArray("tileInfo/lods"); + const auto oLODs = oRoot.GetArray("tileInfo/lods"); double res = 0; // we need to skip levels that don't have bundle files - int minLOD = oRoot.GetInteger("minLOD"); - int maxLOD = oRoot.GetInteger("maxLOD"); - int level = 0; + m_nMinLOD = oRoot.GetInteger("minLOD"); + if (m_nMinLOD < 0 || m_nMinLOD >= 31) + throw CPLString("Invalid minLOD"); + const int maxLOD = std::min(oRoot.GetInteger("maxLOD"), 31); for (const auto &oLOD : oLODs) { res = oLOD.GetDouble("resolution"); if (!(res > 0)) throw CPLString("Can't parse resolution for LOD"); - level = oLOD.GetInteger("level"); - if (level >= minLOD && level <= maxLOD) + const int level = oLOD.GetInteger("level"); + if (level >= m_nMinLOD && level <= maxLOD) { resolutions.push_back(res); } @@ -652,7 +654,8 @@ CPLErr ECBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pData) buffer.resize(nBytes * parent->nBands); - int lxx = static_cast(parent->resolutions.size() - lvl - 1); + const int lxx = parent->m_nMinLOD + + static_cast(parent->resolutions.size() - lvl - 1); int bx, by; bx = (nBlockXOff / BSZ) * BSZ; by = (nBlockYOff / BSZ) * BSZ; From 767eed57dc07d62d7fdf1f4ca70da46ef76f38fb Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Jun 2024 17:23:38 +0200 Subject: [PATCH 0182/1119] ESRIC: validate tile size --- frmts/esric/esric_dataset.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frmts/esric/esric_dataset.cpp b/frmts/esric/esric_dataset.cpp index 8c1d11eff8b4..2909f60c1ab3 100644 --- a/frmts/esric/esric_dataset.cpp +++ b/frmts/esric/esric_dataset.cpp @@ -281,6 +281,8 @@ CPLErr ECDataset::Initialize(CPLXMLNode *CacheInfo) TSZ = static_cast(CPLAtof(CPLGetXMLValue(TCI, "TileCols", "256"))); if (TSZ != CPLAtof(CPLGetXMLValue(TCI, "TileRows", "256"))) throw CPLString("Non-square tiles are not supported"); + if (TSZ < 0 || TSZ > 8192) + throw CPLString("Unsupported TileCols value"); CPLXMLNode *LODInfo = CPLGetXMLNode(TCI, "LODInfos.LODInfo"); double res = 0; @@ -368,6 +370,8 @@ CPLErr ECDataset::InitializeFromJSON(const CPLJSONObject &oRoot) TSZ = oRoot.GetInteger("tileInfo/rows"); if (TSZ != oRoot.GetInteger("tileInfo/cols")) throw CPLString("Non-square tiles are not supported"); + if (TSZ < 0 || TSZ > 8192) + throw CPLString("Unsupported tileInfo/rows value"); const auto oLODs = oRoot.GetArray("tileInfo/lods"); double res = 0; @@ -614,7 +618,7 @@ ECBand::ECBand(ECDataset *parent, int b, int level) double factor = parent->resolutions[0] / parent->resolutions[lvl]; nRasterXSize = static_cast(parent->nRasterXSize * factor + 0.5); nRasterYSize = static_cast(parent->nRasterYSize * factor + 0.5); - nBlockXSize = nBlockYSize = 256; + nBlockXSize = nBlockYSize = parent->TSZ; // Default color interpretation assert(b - 1 >= 0); From ca582758cde2544516cb99d9cdc2e6f416a46e2e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Jun 2024 19:43:39 +0200 Subject: [PATCH 0183/1119] GDALTranslate(): use directly generated VRT even if BLOCKXSIZE/BLOCKYSIZE creation options are specified --- apps/gdal_translate_lib.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/gdal_translate_lib.cpp b/apps/gdal_translate_lib.cpp index c8a5f2612a1e..45d079c61a25 100644 --- a/apps/gdal_translate_lib.cpp +++ b/apps/gdal_translate_lib.cpp @@ -2554,7 +2554,10 @@ GDALDatasetH GDALTranslate(const char *pszDest, GDALDatasetH hSrcDataset, /* Write to the output file using CopyCreate(). */ /* -------------------------------------------------------------------- */ if (EQUAL(psOptions->osFormat.c_str(), "VRT") && - psOptions->aosCreateOptions.empty()) + (psOptions->aosCreateOptions.empty() || + (psOptions->aosCreateOptions.size() == 2 && + psOptions->aosCreateOptions.FetchNameValue("BLOCKXSIZE") && + psOptions->aosCreateOptions.FetchNameValue("BLOCKYSIZE")))) { poVDS->SetDescription(pszDest); hOutDS = GDALDataset::ToHandle(poVDS); From e2b8518e3d990b153058eb80f8afdeeccc348522 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Jun 2024 19:49:04 +0200 Subject: [PATCH 0184/1119] ESRIC: add a EXTENT_SOURCE open option and default to FULL_EXTENT Fixes #10229 --- autotest/gdrivers/esric.py | 50 +++++- frmts/esric/esric_dataset.cpp | 311 ++++++++++++++++++++++++++++------ 2 files changed, 305 insertions(+), 56 deletions(-) diff --git a/autotest/gdrivers/esric.py b/autotest/gdrivers/esric.py index 71c082969a1e..e5c8ddf5066c 100755 --- a/autotest/gdrivers/esric.py +++ b/autotest/gdrivers/esric.py @@ -112,16 +112,18 @@ def test_esric_4(esric_ds): @pytest.fixture -def tpkx_ds(): - return gdal.Open("data/esric/Usa.tpkx") +def tpkx_ds_extent_source_tiling_scheme(): + return gdal.OpenEx( + "data/esric/Usa.tpkx", open_options=["EXTENT_SOURCE=TILING_SCHEME"] + ) ############################################################################### # Check that the configuration was read as expected -def test_tpkx_2(tpkx_ds): - ds = tpkx_ds +def test_tpkx_2(tpkx_ds_extent_source_tiling_scheme): + ds = tpkx_ds_extent_source_tiling_scheme b1 = ds.GetRasterBand(1) assert ( @@ -145,8 +147,8 @@ def test_tpkx_2(tpkx_ds): # Check that the raster returns right checksums -def test_tpkx_3(tpkx_ds): - ds = tpkx_ds +def test_tpkx_3(tpkx_ds_extent_source_tiling_scheme): + ds = tpkx_ds_extent_source_tiling_scheme # There are no tiles at this level, driver will return black b1 = ds.GetRasterBand(1) b2 = ds.GetRasterBand(2) @@ -167,8 +169,8 @@ def test_tpkx_3(tpkx_ds): @pytest.mark.require_driver("PNG") -def test_tpkx_4(tpkx_ds): - ds = tpkx_ds +def test_tpkx_4(tpkx_ds_extent_source_tiling_scheme): + ds = tpkx_ds_extent_source_tiling_scheme # Read from level 1, band 2, where we have data # Overviews are counted from zero, in reverse order from levels @@ -213,3 +215,35 @@ def test_tpkx_minLOD_not_zero(): x = (X - gt[0]) / gt[1] y = (Y - gt[3]) / gt[5] assert ds.GetRasterBand(1).ReadRaster(x, y, 1, 1) != b"\0" + + +############################################################################### +# Test opening a tpkx file with fullExtent / initialExtent + + +@pytest.mark.parametrize("extent_source", [None, "INITIAL_EXTENT", "FULL_EXTENT"]) +def test_tpkx_default_full_extent(extent_source): + open_options = {} + if extent_source: + open_options["EXTENT_SOURCE"] = extent_source + ds = gdal.OpenEx("data/esric/Usa.tpkx", open_options=open_options) + assert ds.RasterXSize == 2532 + assert ds.RasterYSize == 1921 + assert ds.RasterCount == 4 + assert ds.GetSpatialRef().GetAuthorityCode(None) == "3857" + assert ds.GetGeoTransform() == pytest.approx( + ( + -19841829.550377003848553, + 4891.969810249979673, + 0, + 11545048.752193037420511, + 0, + -4891.969810249979673, + ) + ) + assert ds.GetDriver().GetDescription() == "ESRIC" + assert ds.GetFileList() == ["data/esric/Usa.tpkx"] + assert ds.GetRasterBand(1).DataType == gdal.GDT_Byte + assert ds.GetRasterBand(1).GetBlockSize() == [256, 256] + assert ds.GetRasterBand(1).Checksum() == 62015 + assert ds.GetRasterBand(1).GetOverviewCount() == 3 diff --git a/frmts/esric/esric_dataset.cpp b/frmts/esric/esric_dataset.cpp index 2909f60c1ab3..df9c8971ae9a 100644 --- a/frmts/esric/esric_dataset.cpp +++ b/frmts/esric/esric_dataset.cpp @@ -34,6 +34,8 @@ #include #include #include "cpl_json.h" +#include "gdal_proxy.h" +#include "gdal_utils.h" using namespace std; @@ -200,6 +202,8 @@ class ECDataset final : public GDALDataset } static GDALDataset *Open(GDALOpenInfo *poOpenInfo); + static GDALDataset *Open(GDALOpenInfo *poOpenInfo, + const char *pszDescription); protected: double GeoTransform[6]; @@ -220,6 +224,9 @@ class ECDataset final : public GDALDataset OGRSpatialReference oSRS; std::vector tilebuffer; // Last read tile, decompressed std::vector filebuffer; // raw tile buffer + + OGREnvelope m_sInitialExtent{}; + OGREnvelope m_sFullExtent{}; }; class ECBand final : public GDALRasterBand @@ -355,6 +362,58 @@ CPLErr ECDataset::Initialize(CPLXMLNode *CacheInfo) return error; } +static std::unique_ptr +CreateSRS(const CPLJSONObject &oSRSRoot) +{ + auto poSRS = std::make_unique(); + + bool bSuccess = false; + const int nCode = oSRSRoot.GetInteger("wkid"); + // The concept of LatestWKID is explained in + // https://support.esri.com/en/technical-article/000013950 + const int nLatestCode = oSRSRoot.GetInteger("latestWkid"); + + // Try first with nLatestWKID as there is a higher chance it is a + // EPSG code and not an ESRI one. + if (nLatestCode > 0) + { + if (nLatestCode > 32767) + { + if (poSRS->SetFromUserInput(CPLSPrintf("ESRI:%d", nLatestCode)) == + OGRERR_NONE) + { + bSuccess = true; + } + } + else if (poSRS->importFromEPSG(nLatestCode) == OGRERR_NONE) + { + bSuccess = true; + } + } + if (!bSuccess && nCode > 0) + { + if (nCode > 32767) + { + if (poSRS->SetFromUserInput(CPLSPrintf("ESRI:%d", nCode)) == + OGRERR_NONE) + { + bSuccess = true; + } + } + else if (poSRS->importFromEPSG(nCode) == OGRERR_NONE) + { + bSuccess = true; + } + } + if (!bSuccess) + { + return nullptr; + } + + poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); + return poSRS; +} + CPLErr ECDataset::InitializeFromJSON(const CPLJSONObject &oRoot) { CPLErr error = CE_None; @@ -395,51 +454,15 @@ CPLErr ECDataset::InitializeFromJSON(const CPLJSONObject &oRoot) if (resolutions.empty()) throw CPLString("Can't parse lods"); - bool bSuccess = false; - const int nCode = oRoot.GetInteger("spatialReference/wkid"); - // The concept of LatestWKID is explained in - // https://support.esri.com/en/technical-article/000013950 - const int nLatestCode = oRoot.GetInteger("spatialReference/latestWkid"); - - // Try first with nLatestWKID as there is a higher chance it is a - // EPSG code and not an ESRI one. - if (nLatestCode > 0) { - if (nLatestCode > 32767) + auto poSRS = CreateSRS(oRoot.GetObj("spatialReference")); + if (!poSRS) { - if (oSRS.SetFromUserInput(CPLSPrintf("ESRI:%d", nLatestCode)) == - OGRERR_NONE) - { - bSuccess = true; - } + throw CPLString("Invalid Spatial Reference"); } - else if (oSRS.importFromEPSG(nLatestCode) == OGRERR_NONE) - { - bSuccess = true; - } - } - if (!bSuccess && nCode > 0) - { - if (nCode > 32767) - { - if (oSRS.SetFromUserInput(CPLSPrintf("ESRI:%d", nCode)) == - OGRERR_NONE) - { - bSuccess = true; - } - } - else if (oSRS.importFromEPSG(nCode) == OGRERR_NONE) - { - bSuccess = true; - } - } - if (!bSuccess) - { - throw CPLString("Invalid Spatial Reference"); + oSRS = std::move(*poSRS); } - oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - // resolution is the smallest figure res = resolutions[0]; double gt[6] = {0, 1, 0, 0, 0, 1}; @@ -466,6 +489,59 @@ CPLErr ECDataset::InitializeFromJSON(const CPLJSONObject &oRoot) compression = oRoot.GetString("tileImageInfo/format"); SetMetadataItem("COMPRESS", compression.c_str(), "IMAGE_STRUCTURE"); + auto oInitialExtent = oRoot.GetObj("initialExtent"); + if (oInitialExtent.IsValid() && + oInitialExtent.GetType() == CPLJSONObject::Type::Object) + { + m_sInitialExtent.MinX = oInitialExtent.GetDouble("xmin"); + m_sInitialExtent.MinY = oInitialExtent.GetDouble("ymin"); + m_sInitialExtent.MaxX = oInitialExtent.GetDouble("xmax"); + m_sInitialExtent.MaxY = oInitialExtent.GetDouble("ymax"); + auto oSRSRoot = oInitialExtent.GetObj("spatialReference"); + if (oSRSRoot.IsValid()) + { + auto poSRS = CreateSRS(oSRSRoot); + if (!poSRS) + { + throw CPLString( + "Invalid Spatial Reference in initialExtent"); + } + if (!poSRS->IsSame(&oSRS)) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Ignoring initialExtent, because its SRS is " + "different from the main one"); + m_sInitialExtent = OGREnvelope(); + } + } + } + + auto oFullExtent = oRoot.GetObj("fullExtent"); + if (oFullExtent.IsValid() && + oFullExtent.GetType() == CPLJSONObject::Type::Object) + { + m_sFullExtent.MinX = oFullExtent.GetDouble("xmin"); + m_sFullExtent.MinY = oFullExtent.GetDouble("ymin"); + m_sFullExtent.MaxX = oFullExtent.GetDouble("xmax"); + m_sFullExtent.MaxY = oFullExtent.GetDouble("ymax"); + auto oSRSRoot = oFullExtent.GetObj("spatialReference"); + if (oSRSRoot.IsValid()) + { + auto poSRS = CreateSRS(oSRSRoot); + if (!poSRS) + { + throw CPLString("Invalid Spatial Reference in fullExtent"); + } + if (!poSRS->IsSame(&oSRS)) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Ignoring fullExtent, because its SRS is " + "different from the main one"); + m_sFullExtent = OGREnvelope(); + } + } + } + nBands = EQUAL(compression, "JPEG") ? 3 : 4; for (int i = 1; i <= nBands; i++) { @@ -488,7 +564,71 @@ CPLErr ECDataset::InitializeFromJSON(const CPLJSONObject &oRoot) return error; } +class ESRICProxyRasterBand final : public GDALProxyRasterBand +{ + private: + GDALRasterBand *m_poUnderlyingBand = nullptr; + + protected: + GDALRasterBand *RefUnderlyingRasterBand(bool /*bForceOpen*/) const override + { + return m_poUnderlyingBand; + } + + public: + explicit ESRICProxyRasterBand(GDALRasterBand *poUnderlyingBand) + : m_poUnderlyingBand(poUnderlyingBand) + { + nBand = poUnderlyingBand->GetBand(); + eDataType = poUnderlyingBand->GetRasterDataType(); + nRasterXSize = poUnderlyingBand->GetXSize(); + nRasterYSize = poUnderlyingBand->GetYSize(); + poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize); + } +}; + +class ESRICProxyDataset final : public GDALProxyDataset +{ + private: + std::unique_ptr m_poUnderlyingDS{}; + CPLStringList m_aosFileList{}; + + protected: + GDALDataset *RefUnderlyingDataset() const override + { + return m_poUnderlyingDS.get(); + } + + public: + ESRICProxyDataset(GDALDataset *poUnderlyingDS, const char *pszDescription) + : m_poUnderlyingDS(poUnderlyingDS) + { + nRasterXSize = poUnderlyingDS->GetRasterXSize(); + nRasterYSize = poUnderlyingDS->GetRasterYSize(); + for (int i = 0; i < poUnderlyingDS->GetRasterCount(); ++i) + SetBand(i + 1, new ESRICProxyRasterBand( + poUnderlyingDS->GetRasterBand(i + 1))); + m_aosFileList.AddString(pszDescription); + } + + GDALDriver *GetDriver() override + { + return GDALDriver::FromHandle(GDALGetDriverByName("ESRIC")); + } + + char **GetFileList() override + { + return CSLDuplicate(m_aosFileList.List()); + } +}; + GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo) +{ + return Open(poOpenInfo, poOpenInfo->pszFilename); +} + +GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo, + const char *pszDescription) { if (IdentifyXML(poOpenInfo)) { @@ -529,10 +669,8 @@ GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo) poOpenInfo->pszFilename + "}/root.json") .c_str(), GA_ReadOnly); - auto poDS = Open(&oOpenInfo); - if (poDS) - poDS->SetDescription(poOpenInfo->pszFilename); - return poDS; + oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions; + return Open(&oOpenInfo, pszDescription); } CPLJSONDocument oJSONDocument; @@ -550,7 +688,7 @@ GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo) return nullptr; } - auto ds = new ECDataset(); + auto ds = std::make_unique(); auto tileBundlesPath = oRoot.GetString("tileBundlesPath"); // Strip leading relative path indicator (if present) if (tileBundlesPath.substr(0, 2) == "./") @@ -563,10 +701,76 @@ GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo) CPLErr error = ds->InitializeFromJSON(oRoot); if (CE_None != error) { - delete ds; - ds = nullptr; + return nullptr; } - return ds; + + const bool bIsFullExtentValid = + (ds->m_sFullExtent.IsInit() && + ds->m_sFullExtent.MinX < ds->m_sFullExtent.MaxX && + ds->m_sFullExtent.MinY < ds->m_sFullExtent.MaxY); + const char *pszExtentSource = + CSLFetchNameValue(poOpenInfo->papszOpenOptions, "EXTENT_SOURCE"); + + CPLStringList aosOptions; + if ((!pszExtentSource && bIsFullExtentValid) || + EQUAL(pszExtentSource, "FULL_EXTENT")) + { + if (!bIsFullExtentValid) + { + CPLError(CE_Failure, CPLE_AppDefined, + "fullExtent is not valid"); + return nullptr; + } + aosOptions.AddString("-projwin"); + aosOptions.AddString(CPLSPrintf("%.18g", ds->m_sFullExtent.MinX)); + aosOptions.AddString(CPLSPrintf("%.18g", ds->m_sFullExtent.MaxY)); + aosOptions.AddString(CPLSPrintf("%.18g", ds->m_sFullExtent.MaxX)); + aosOptions.AddString(CPLSPrintf("%.18g", ds->m_sFullExtent.MinY)); + } + else if (pszExtentSource && EQUAL(pszExtentSource, "INITIAL_EXTENT")) + { + const bool bIsInitialExtentValid = + (ds->m_sInitialExtent.IsInit() && + ds->m_sInitialExtent.MinX < ds->m_sInitialExtent.MaxX && + ds->m_sInitialExtent.MinY < ds->m_sInitialExtent.MaxY); + if (!bIsInitialExtentValid) + { + CPLError(CE_Failure, CPLE_AppDefined, + "initialExtent is not valid"); + return nullptr; + } + aosOptions.AddString("-projwin"); + aosOptions.AddString( + CPLSPrintf("%.18g", ds->m_sInitialExtent.MinX)); + aosOptions.AddString( + CPLSPrintf("%.18g", ds->m_sInitialExtent.MaxY)); + aosOptions.AddString( + CPLSPrintf("%.18g", ds->m_sInitialExtent.MaxX)); + aosOptions.AddString( + CPLSPrintf("%.18g", ds->m_sInitialExtent.MinY)); + } + + if (!aosOptions.empty()) + { + aosOptions.AddString("-of"); + aosOptions.AddString("VRT"); + aosOptions.AddString("-co"); + aosOptions.AddString(CPLSPrintf("BLOCKXSIZE=%d", ds->TSZ)); + aosOptions.AddString("-co"); + aosOptions.AddString(CPLSPrintf("BLOCKYSIZE=%d", ds->TSZ)); + auto psOptions = + GDALTranslateOptionsNew(aosOptions.List(), nullptr); + auto hDS = GDALTranslate("", GDALDataset::ToHandle(ds.release()), + psOptions, nullptr); + GDALTranslateOptionsFree(psOptions); + if (!hDS) + { + return nullptr; + } + return new ESRICProxyDataset(GDALDataset::FromHandle(hDS), + pszDescription); + } + return ds.release(); } return nullptr; } @@ -850,6 +1054,17 @@ void CPL_DLL GDALRegister_ESRIC() poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "json tpkx"); + poDriver->SetMetadataItem( + GDAL_DMD_OPENOPTIONLIST, + "" + " " + ""); poDriver->pfnIdentify = ESRIC::Identify; poDriver->pfnOpen = ESRIC::ECDataset::Open; poDriver->pfnDelete = ESRIC::Delete; From c06e5d76302c220402ebd9a89059fb99069d7dbe Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 19 Jun 2024 08:23:47 +1000 Subject: [PATCH 0185/1119] State that VSISetPathSpecificOption path should not include trailing slash --- port/cpl_vsil.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/port/cpl_vsil.cpp b/port/cpl_vsil.cpp index 61d9a535041e..f5b3b401de32 100644 --- a/port/cpl_vsil.cpp +++ b/port/cpl_vsil.cpp @@ -3503,7 +3503,7 @@ void VSISetCredential(const char *pszPathPrefix, const char *pszKey, * * @param pszPathPrefix a path prefix of a virtual file system handler. * Typically of the form "/vsiXXX/bucket". Must NOT be - * NULL. + * NULL. Should not include trailing slashes. * @param pszKey Option name. Must NOT be NULL. * @param pszValue Option value. May be NULL to erase it. * From 77f03d1673fa1fc214cc5eaadb524a187ccd0d17 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 18 Jun 2024 19:49:42 +0200 Subject: [PATCH 0186/1119] Doc: esric.rst: document EXTENT_SOURCE open option --- doc/source/drivers/raster/esric.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/doc/source/drivers/raster/esric.rst b/doc/source/drivers/raster/esric.rst index bd77fb232c7f..cef8bc745a55 100644 --- a/doc/source/drivers/raster/esric.rst +++ b/doc/source/drivers/raster/esric.rst @@ -139,6 +139,25 @@ ________________________ file until the raster size drops below INT32_MAX is a possible workaround, but the highest resolution levels will not be read. +Open options +------------ + +|about-open-options| +The following open options are available: + +- .. oo:: EXTENT_SOURCE + :choices: FULL_EXTENT, INITIAL_EXTENT, TILING_SCHEME + :default: FULL_EXTENT + :since: 3.9.1 + + Which source should be used for the extent of Esri Tile Package (.tpkx) datasets. + By default, or if specifying ``FULL_EXTENT``, the "fullExtent" element of + the root.json file will be selected, which normally covers all tiles with data. + ``INITIAL_EXTENT`` can be specified to use the default initial extent described + in the "initialExtent" element of the root.json file. + ``TILING_SCHEME`` will return the extent of the whole tiling scheme, which + is typically world coverage. + See Also -------- - Implemented as :source_file:`frmts/esric/esric_dataset.cpp`. From 8ba3aca0d5941c702d3373f3f4104f5f39548945 Mon Sep 17 00:00:00 2001 From: Andrew Bell Date: Wed, 19 Jun 2024 05:05:41 -0400 Subject: [PATCH 0187/1119] Viewshed: address potential issues with line-based interpolation (#10237) Add tests that have been verified by hand-computation. Correct existing tests to match fixed visibility/height computations along lines (multiples of pi / 4) Close #10205 --- alg/viewshed.cpp | 84 +++++--- alg/viewshed.h | 3 +- autotest/cpp/CMakeLists.txt | 3 +- autotest/cpp/test_viewshed.cpp | 259 +++++++++++++++++++++++ autotest/utilities/test_gdal_viewshed.py | 57 +---- 5 files changed, 329 insertions(+), 77 deletions(-) create mode 100644 autotest/cpp/test_viewshed.cpp diff --git a/alg/viewshed.cpp b/alg/viewshed.cpp index 2d29f0d2f677..17e2f4ad8b11 100644 --- a/alg/viewshed.cpp +++ b/alg/viewshed.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include "gdal_alg.h" @@ -236,15 +237,15 @@ double CalcHeightAdjFactor(const GDALDataset *poDataset, double dfCurveCoeff) return 0; } -// Calculate the height at nDistance units along a line through the origin given the height -// at nDistance - 1 units along the line. +/// Calculate the height at nDistance units along a line through the origin given the height +/// at nDistance - 1 units along the line. +/// \param nDistance Distance along the line for the target point. +/// \param Za Height at the line one unit previous to the target point. double CalcHeightLine(int nDistance, double Za) { nDistance = std::abs(nDistance); - if (nDistance == 1) - return Za; - else - return Za * nDistance / (nDistance - 1); + assert(nDistance != 1); + return Za * nDistance / (nDistance - 1); } // Calculate the height Zc of a point (i, j, Zc) given a line through the origin (0, 0, 0) @@ -262,10 +263,8 @@ double CalcHeightDiagonal(int i, int j, double Za, double Zb) // point (i, j, Zc), also on the plane. double CalcHeightEdge(int i, int j, double Za, double Zb) { - if (i == j) - return CalcHeightLine(i, Za); - else - return (Za * i + Zb * (j - i)) / (j - 1); + assert(i != j); + return (Za * i + Zb * (j - i)) / (j - 1); } } // unnamed namespace @@ -580,8 +579,18 @@ void Viewshed::processLineLeft(int nX, int nYOffset, int iStart, int iEnd, for (int iPixel = iStart; iPixel > iEnd; iPixel--, pThis--, pLast--) { int nXOffset = std::abs(iPixel - nX); - double dfZ = - oZcalc(nXOffset, nYOffset, *(pThis + 1), *pLast, *(pLast + 1)); + + double dfZ; + if (nXOffset == nYOffset) + { + if (nXOffset == 1) + dfZ = *pThis; + else + dfZ = CalcHeightLine(nXOffset, *(pLast + 1)); + } + else + dfZ = + oZcalc(nXOffset, nYOffset, *(pThis + 1), *pLast, *(pLast + 1)); setOutput(vResult[iPixel], *pThis, dfZ); } @@ -612,19 +621,28 @@ void Viewshed::processLineRight(int nX, int nYOffset, int iStart, int iEnd, for (int iPixel = iStart; iPixel < iEnd; iPixel++, pThis++, pLast++) { int nXOffset = std::abs(iPixel - nX); - double dfZ = - oZcalc(nXOffset, nYOffset, *(pThis - 1), *pLast, *(pLast - 1)); + double dfZ; + if (nXOffset == nYOffset) + { + if (nXOffset == 1) + dfZ = *pThis; + else + dfZ = CalcHeightLine(nXOffset, *(pLast - 1)); + } + else + dfZ = + oZcalc(nXOffset, nYOffset, *(pThis - 1), *pLast, *(pLast - 1)); setOutput(vResult[iPixel], *pThis, dfZ); } // For cells outside of the [start, end) range, set the outOfRange value. std::fill(vResult.begin() + iEnd, vResult.end(), oOpts.outOfRangeVal); } -/// Set the output Z value depending o the observable height and computation mode. +/// Set the output Z value depending on the observable height and computation mode. /// /// dfResult Reference to the result cell /// dfCellVal Reference to the current cell height. Replace with observable height. -/// dfZ Observable height at cell. +/// dfZ Minimum observable height at cell. void Viewshed::setOutput(double &dfResult, double &dfCellVal, double dfZ) { if (oOpts.outputMode != OutputMode::Normal) @@ -659,14 +677,6 @@ bool Viewshed::processFirstLine(int nX, int nY, int nLine, // This bit is only relevant for the first line. dfZObserver = oOpts.observer.z + vThisLineVal[nX]; dfHeightAdjFactor = CalcHeightAdjFactor(poDstDS.get(), oOpts.curveCoeff); - if (oOpts.outputMode == OutputMode::Normal) - { - vResult[nX] = oOpts.visibleVal; - if (nX - 1 >= 0) - vResult[nX - 1] = oOpts.visibleVal; - if (nX + 1 < oCurExtent.xSize()) - vResult[nX + 1] = oOpts.visibleVal; - } // In DEM mode the base is the pre-adjustment value. // In ground mode the base is zero. @@ -676,6 +686,26 @@ bool Viewshed::processFirstLine(int nX, int nY, int nLine, const auto [iLeft, iRight] = adjustHeight(nYOffset, nX, vThisLineVal.data() + nX); + if (oOpts.outputMode == OutputMode::Normal) + { + vResult[nX] = oOpts.visibleVal; + if (nX - 1 >= 0) + vResult[nX - 1] = oOpts.visibleVal; + if (nX + 1 < oCurExtent.xSize()) + vResult[nX + 1] = oOpts.visibleVal; + } + else + { + // The minimum observable value isn't well-defined at a distance of one. + // We use the actual cell height. + if (nX - 1 >= 0) + setOutput(vResult[nX - 1], vThisLineVal[nX - 1], + vThisLineVal[nX - 1]); + if (nX + 1 < oCurExtent.xSize()) + setOutput(vResult[nX + 1], vThisLineVal[nX + 1], + vThisLineVal[nX + 1]); + } + auto t1 = std::async(std::launch::async, [&, left = iLeft]() @@ -736,7 +766,11 @@ bool Viewshed::processLine(int nX, int nY, int nLine, // Handle the initial position on the line. if (iLeft < iRight) { - double dfZ = CalcHeightLine(nYOffset, vLastLineVal[nX]); + double dfZ; + if (std::abs(nYOffset) == 1) + dfZ = vThisLineVal[nX]; + else + dfZ = CalcHeightLine(nYOffset, vLastLineVal[nX]); setOutput(vResult[nX], vThisLineVal[nX], dfZ); } else diff --git a/alg/viewshed.h b/alg/viewshed.h index d862adebf237..176ff1bf8d9e 100644 --- a/alg/viewshed.h +++ b/alg/viewshed.h @@ -150,7 +150,8 @@ class Viewshed Viewshed(const Viewshed &) = delete; Viewshed &operator=(const Viewshed &) = delete; - CPL_DLL bool run(GDALRasterBandH hBand, GDALProgressFunc pfnProgress, + CPL_DLL bool run(GDALRasterBandH hBand, + GDALProgressFunc pfnProgress = GDALDummyProgress, void *pProgressArg = nullptr); /** diff --git a/autotest/cpp/CMakeLists.txt b/autotest/cpp/CMakeLists.txt index ffffc1b3d3f7..cf5410d1c8d2 100644 --- a/autotest/cpp/CMakeLists.txt +++ b/autotest/cpp/CMakeLists.txt @@ -95,7 +95,8 @@ add_executable( test_marching_squares_contour.cpp test_marching_squares_polygon.cpp test_marching_squares_square.cpp - test_marching_squares_tile.cpp) + test_marching_squares_tile.cpp + test_viewshed.cpp) target_link_libraries(gdal_unit_test PRIVATE gtest_for_gdal) target_link_libraries(gdal_unit_test PRIVATE $) diff --git a/autotest/cpp/test_viewshed.cpp b/autotest/cpp/test_viewshed.cpp new file mode 100644 index 000000000000..be733b02d65c --- /dev/null +++ b/autotest/cpp/test_viewshed.cpp @@ -0,0 +1,259 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Project: C++ Test Suite for GDAL/OGR +// Purpose: Test viewshed algorithm +// Author: Andrew Bell +// +/////////////////////////////////////////////////////////////////////////////// +/* + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ + +#include +#include +#include + +#include "gdal_unit_test.h" + +#include "gtest_include.h" + +#include "viewshed.h" + +namespace gdal +{ + +namespace +{ +using Coord = std::pair; +using DatasetPtr = std::unique_ptr; +using Transform = std::array; +Transform identity{0, 1, 0, 0, 0, 1}; + +Viewshed::Options stdOptions(int x, int y) +{ + Viewshed::Options opts; + opts.observer.x = x; + opts.observer.y = y; + opts.outputFilename = "none"; + opts.outputFormat = "mem"; + opts.curveCoeff = 0; + + return opts; +} + +Viewshed::Options stdOptions(const Coord &observer) +{ + return stdOptions(observer.first, observer.second); +} + +DatasetPtr runViewshed(int8_t *in, int edgeLength, Viewshed::Options opts) +{ + Viewshed v(opts); + + GDALDriver *driver = (GDALDriver *)GDALGetDriverByName("MEM"); + GDALDataset *dataset = + driver->Create("", edgeLength, edgeLength, 1, GDT_Int8, nullptr); + EXPECT_TRUE(dataset); + dataset->SetGeoTransform(identity.data()); + GDALRasterBand *band = dataset->GetRasterBand(1); + EXPECT_TRUE(band); + CPLErr err = + band->RasterIO(GF_Write, 0, 0, edgeLength, edgeLength, in, edgeLength, + edgeLength, GDT_Int8, 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + + EXPECT_TRUE(v.run(band)); + return v.output(); +} + +} // namespace + +TEST(Viewshed, all_visible) +{ + // clang-format off + const int edge = 3; + std::array in + { + 1, 2, 3, + 4, 5, 6, + 3, 2, 1 + }; + // clang-format on + + SCOPED_TRACE("all_visible"); + DatasetPtr output = runViewshed(in.data(), edge, stdOptions(1, 1)); + + std::array out; + GDALRasterBand *band = output->GetRasterBand(1); + CPLErr err = band->RasterIO(GF_Read, 0, 0, edge, edge, out.data(), edge, + edge, GDT_Int8, 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + + for (uint8_t o : out) + EXPECT_EQ(o, 127); +} + +TEST(Viewshed, simple_height) +{ + // clang-format off + const int edge = 5; + std::array in + { + -1, 0, 1, 0, -1, + -1, 2, 0, 4, -1, + -1, 1, 0, -1, -1, + 0, 3, 0, 2, 0, + -1, 0, 0, 3, -1 + }; + + std::array observable + { + 4, 2, 0, 4, 8, + 3, 2, 0, 4, 3, + 2, 1, 0, -1, -2, + 4, 3, 0, 2, 1, + 6, 3, 0, 2, 4 + }; + // clang-format on + + { + SCOPED_TRACE("simple_height:normal"); + + DatasetPtr output = runViewshed(in.data(), 5, stdOptions(2, 2)); + + std::array out; + GDALRasterBand *band = output->GetRasterBand(1); + CPLErr err = band->RasterIO(GF_Read, 0, 0, edge, edge, out.data(), edge, + edge, GDT_Int8, 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + + // We expect the cell to be observable when the input is higher than the observable + // height. + std::array expected; + for (size_t i = 0; i < in.size(); ++i) + expected[i] = (in[i] >= observable[i] ? 127 : 0); + + EXPECT_EQ(expected, out); + } + + { + std::array dem; + SCOPED_TRACE("simple_height:dem"); + Viewshed::Options opts = stdOptions(2, 2); + opts.outputMode = Viewshed::OutputMode::DEM; + DatasetPtr output = runViewshed(in.data(), 5, opts); + + GDALRasterBand *band = output->GetRasterBand(1); + CPLErr err = band->RasterIO(GF_Read, 0, 0, edge, edge, dem.data(), edge, + edge, GDT_Float64, 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + + // DEM values are observable values clamped at 0. Not sure why. + std::array expected = observable; + for (double &d : expected) + d = std::max(0.0, d); + + // Double equality is fine here as all the values are small integers. + EXPECT_EQ(dem, expected); + } + + { + std::array ground; + SCOPED_TRACE("simple_height:ground"); + Viewshed::Options opts = stdOptions(2, 2); + opts.outputMode = Viewshed::OutputMode::Ground; + DatasetPtr output = runViewshed(in.data(), 5, opts); + + GDALRasterBand *band = output->GetRasterBand(1); + CPLErr err = band->RasterIO(GF_Read, 0, 0, edge, edge, ground.data(), + edge, edge, GDT_Float64, 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + + std::array expected; + for (size_t i = 0; i < expected.size(); ++i) + expected[i] = std::max(0.0, observable[i] - in[i]); + + // Double equality is fine here as all the values are small integers. + EXPECT_EQ(expected, ground); + } +} + +// Addresses cases in #9501 +TEST(Viewshed, dem_vs_ground) +{ + auto process = [](const std::array &in, Viewshed::Options &opts) + { + Viewshed v(opts); + + GDALDriver *driver = (GDALDriver *)GDALGetDriverByName("MEM"); + // 8 cols x 1 row + GDALDataset *dataset = driver->Create("", 8, 1, 1, GDT_Int8, nullptr); + EXPECT_TRUE(dataset); + dataset->SetGeoTransform(identity.data()); + GDALRasterBand *band = dataset->GetRasterBand(1); + EXPECT_TRUE(band); + CPLErr err = band->RasterIO(GF_Write, 0, 0, 8, 1, (void *)in.data(), 8, + 1, GDT_Int8, 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + + EXPECT_TRUE(v.run(band)); + return v.output(); + }; + + auto run = [&process](const std::array &in, Coord observer, + const std::array &ground, + const std::array &dem) + { + Viewshed::Options opts = stdOptions(observer); + + std::array out; + opts.outputMode = Viewshed::OutputMode::Ground; + DatasetPtr ds = process(in, opts); + GDALRasterBand *band = ds->GetRasterBand(1); + CPLErr err = band->RasterIO(GF_Read, 0, 0, 8, 1, out.data(), 8, 1, + GDT_Float64, 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + for (size_t i = 0; i < ground.size(); ++i) + EXPECT_DOUBLE_EQ(out[i], ground[i]); + + opts.outputMode = Viewshed::OutputMode::DEM; + ds = process(in, opts); + band = ds->GetRasterBand(1); + err = band->RasterIO(GF_Read, 0, 0, 8, 1, out.data(), 8, 1, GDT_Float64, + 0, 0, nullptr); + EXPECT_EQ(err, CE_None); + for (size_t i = 0; i < dem.size(); ++i) + EXPECT_DOUBLE_EQ(out[i], dem[i]); + }; + + // Input / Observer / Minimum expected above ground / Minimum expected above zero + run({0, 0, 0, 1, 0, 0, 0, 0}, {2, 0}, {0, 0, 0, 0, 2, 3, 4, 5}, + {0, 0, 0, 1, 2, 3, 4, 5}); + run({1, 1, 0, 1, 0, 1, 2, 2}, {3, 0}, {0, 0, 0, 0, 0, 0, 0, 1 / 3.0}, + {1, 0, 0, 1, 0, 0, 1, 7 / 3.0}); + run({0, 0, 0, 1, 1, 0, 0, 0}, {0, 0}, + {0, 0, 0, 0, 1 / 3.0, 5 / 3.0, 6 / 3.0, 7 / 3.0}, + {0, 0, 0, 0, 4 / 3.0, 5 / 3.0, 6 / 3.0, 7 / 3.0}); + run({0, 0, 1, 2, 3, 4, 5, 6}, {0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 3 / 2.0, 8 / 3.0, 15 / 4.0, 24 / 5.0, 35 / 6.0}); + run({0, 0, 1, 1, 3, 4, 5, 4}, {0, 0}, {0, 0, 0, .5, 0, 0, 0, 11 / 6.0}, + {0, 0, 0, 3 / 2.0, 2, 15 / 4.0, 24 / 5.0, 35 / 6.0}); +} + +} // namespace gdal diff --git a/autotest/utilities/test_gdal_viewshed.py b/autotest/utilities/test_gdal_viewshed.py index eda228c25c5a..e2504f66c2d5 100755 --- a/autotest/utilities/test_gdal_viewshed.py +++ b/autotest/utilities/test_gdal_viewshed.py @@ -29,8 +29,6 @@ # DEALINGS IN THE SOFTWARE. ############################################################################### -import struct - import gdaltest import pytest import test_cli_utilities @@ -90,7 +88,7 @@ def test_gdal_viewshed(gdal_viewshed_path, tmp_path, viewshed_input): cs = ds.GetRasterBand(1).Checksum() nodata = ds.GetRasterBand(1).GetNoDataValue() ds = None - assert cs == 14613 + assert cs == 14695 assert nodata is None @@ -118,7 +116,7 @@ def test_gdal_viewshed_non_earth_crs( cs = ds.GetRasterBand(1).Checksum() nodata = ds.GetRasterBand(1).GetNoDataValue() ds = None - assert cs == 14609 + assert cs == 14691 assert nodata is None @@ -231,7 +229,7 @@ def test_gdal_viewshed_value_options(gdal_viewshed_path, tmp_path, viewshed_inpu cs = ds.GetRasterBand(1).Checksum() nodata = ds.GetRasterBand(1).GetNoDataValue() ds = None - assert cs == 35091 + assert cs == 35108 assert nodata == 0 @@ -442,47 +440,8 @@ def test_gdal_viewshed_south_up(gdal_viewshed_path, tmp_path, viewshed_input): assert ds.RasterXSize == width assert ds.RasterYSize == height assert ds.GetGeoTransform() == pytest.approx(expected_gt) - expected_data = ( - 255, - 255, - 255, - 255, - 255, - 255, - 255, # end of line - 255, - 255, - 0, - 0, - 0, - 255, - 255, # end of line - 255, - 255, - 255, - 255, - 255, - 255, - 255, # end of line - 255, - 255, - 0, - 0, - 0, - 255, - 255, # end of line - 255, - 255, - 255, - 255, - 255, - 255, - 255, - ) - assert ( - struct.unpack("B" * (width * height), ds.GetRasterBand(1).ReadRaster()) - == expected_data - ) + for val in ds.GetRasterBand(1).ReadRaster(): + assert val == 255 # Tested case with south-up dataset src_ds_south_up_filename = str(tmp_path / "test_gdal_viewshed_src_ds_south_up.tif") @@ -512,7 +471,5 @@ def test_gdal_viewshed_south_up(gdal_viewshed_path, tmp_path, viewshed_input): assert ds.RasterXSize == width assert ds.RasterYSize == height assert ds.GetGeoTransform() == pytest.approx(expected_gt) - assert ( - struct.unpack("B" * (width * height), ds.GetRasterBand(1).ReadRaster()) - == expected_data - ) + for val in ds.GetRasterBand(1).ReadRaster(): + assert val == 255 From a49c3d115b7c90b1dc75c313e24f98463f32e019 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 19 Jun 2024 11:01:57 +0200 Subject: [PATCH 0188/1119] ESRIC: avoid potential nullptr deref (master only) --- frmts/esric/esric_dataset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frmts/esric/esric_dataset.cpp b/frmts/esric/esric_dataset.cpp index df9c8971ae9a..c92d7132d512 100644 --- a/frmts/esric/esric_dataset.cpp +++ b/frmts/esric/esric_dataset.cpp @@ -713,7 +713,7 @@ GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo, CPLStringList aosOptions; if ((!pszExtentSource && bIsFullExtentValid) || - EQUAL(pszExtentSource, "FULL_EXTENT")) + (pszExtentSource && EQUAL(pszExtentSource, "FULL_EXTENT"))) { if (!bIsFullExtentValid) { From 7c2b40f2d158d0ea33288d8d1b934deefbb0dd52 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 19 Jun 2024 11:39:57 +0200 Subject: [PATCH 0189/1119] Python bindings: fix/workaround installation into /usr/local/lib/python{version}/dist-packages when prefix is /usr/local Fixes #10242 --- .github/workflows/cmake_builds.yml | 6 +++--- swig/python/CMakeLists.txt | 16 +++++++++++++++- swig/python/trimmedsysconfig.py | 8 ++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cmake_builds.yml b/.github/workflows/cmake_builds.yml index 0c5485b69de4..825ec91eaa3a 100644 --- a/.github/workflows/cmake_builds.yml +++ b/.github/workflows/cmake_builds.yml @@ -214,15 +214,15 @@ jobs: test -f $GITHUB_WORKSPACE/install-gdal/share/man/man1/gdaladdo.1 export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/install-gdal/lib $GITHUB_WORKSPACE/install-gdal/bin/gdalinfo --version - PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/python3/dist-packages python3 -c "from osgeo import gdal;print(gdal.VersionInfo(None))" - PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/python3/dist-packages python3 $GITHUB_WORKSPACE/scripts/check_doc.py + PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/python3.8/site-packages python3 -c "from osgeo import gdal;print(gdal.VersionInfo(None))" + PYTHONPATH=$GITHUB_WORKSPACE/install-gdal/lib/python3.8/site-packages python3 $GITHUB_WORKSPACE/scripts/check_doc.py - name: CMake with rpath run: | export PATH=$CMAKE_DIR:/usr/local/bin:/usr/bin:/bin # Avoid CMake config from brew etc. (cd $GITHUB_WORKSPACE/superbuild/build; cmake .. "-DCMAKE_INSTALL_PREFIX=$GITHUB_WORKSPACE/install-gdal-with-rpath" "-DCMAKE_INSTALL_RPATH=$GITHUB_WORKSPACE/install-gdal-with-rpath/lib") cmake --build $GITHUB_WORKSPACE/superbuild/build --target install -- -j$(nproc) # For some reason, during the install phase of above invocation, the Python bindings are rebuilt after the build phase, and without the rpath... Can't reproduce that locally - # PYTHONPATH=$GITHUB_WORKSPACE/install-gdal-with-rpath/lib/python3/dist-packages python -c "from osgeo import gdal;print(gdal.VersionInfo(None))" + # PYTHONPATH=$GITHUB_WORKSPACE/install-gdal-with-rpath/lib/python3.8/site-packages python -c "from osgeo import gdal;print(gdal.VersionInfo(None))" - name: Rerun using Mono run: | export PATH=$CMAKE_DIR:/usr/local/bin:/usr/bin:/bin # Avoid CMake config from brew etc. diff --git a/swig/python/CMakeLists.txt b/swig/python/CMakeLists.txt index bccd75ac94a6..d694c71c208b 100644 --- a/swig/python/CMakeLists.txt +++ b/swig/python/CMakeLists.txt @@ -401,8 +401,15 @@ if __name__ == '__main__': # Install extension configure_file(${CMAKE_CURRENT_SOURCE_DIR}/trimmedsysconfig.py ${CMAKE_CURRENT_BINARY_DIR}/trimmedsysconfig.py @ONLY) + if (DEFINED GDAL_PYTHON_INSTALL_PREFIX) + set(PREFIX_FOR_TRIMMEDSYSCONFIG "${GDAL_PYTHON_INSTALL_PREFIX}") + elseif (DEFINED CMAKE_INSTALL_PREFIX) + set(PREFIX_FOR_TRIMMEDSYSCONFIG "${CMAKE_INSTALL_PREFIX}") + else() + set(PREFIX_FOR_TRIMMEDSYSCONFIG "") + endif() file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/get_python_lib.py - "from trimmedsysconfig import get_python_lib;print(get_python_lib(prefix=\"${GDAL_PYTHON_INSTALL_PREFIX}\"))\n") + "from trimmedsysconfig import get_python_lib;print(get_python_lib(prefix=\"${PREFIX_FOR_TRIMMEDSYSCONFIG}\"))\n") execute_process( COMMAND ${Python_EXECUTABLE_CMAKE} ${CMAKE_CURRENT_BINARY_DIR}/get_python_lib.py OUTPUT_VARIABLE SITE_PACKAGE_DIR @@ -448,6 +455,13 @@ if __name__ == '__main__': set(SETUPTOOLS_USE_DISTUTILS stdlib) elseif ("${SITE_PACKAGE_DIR}" MATCHES "dist-packages") set(INSTALL_ARGS "${INSTALL_ARGS} --install-layout=deb") + if (NOT DEFINED GDAL_PYTHON_INSTALL_LIB AND "${PREFIX_FOR_TRIMMEDSYSCONFIG}" STREQUAL "/usr/local") + # Scenario of https://github.com/OSGeo/gdal/issues/10242 + # For some reason patched setuptools of Debian fails to install by default + # in /usr/local/lib/python{version}/dist-packages even when passing + # --prefix=/usr/local and --install-layout=deb + set(INSTALL_ARGS "${INSTALL_ARGS} \"--install-lib=${SITE_PACKAGE_DIR}\"") + endif() if(Python_VERSION VERSION_LESS 3.12) # It no longer seems needed to mess with SETUPTOOLS_USE_DISTUTILS # with Debian Python 3.12 (or maybe its setuptools 68.1.2 version...), diff --git a/swig/python/trimmedsysconfig.py b/swig/python/trimmedsysconfig.py index a6037f7417bf..efb7c26357d8 100644 --- a/swig/python/trimmedsysconfig.py +++ b/swig/python/trimmedsysconfig.py @@ -63,6 +63,14 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None): and "real_prefix" not in sys.__dict__ and sys.prefix == sys.base_prefix ): + if ( + prefix + and os.path.normpath(prefix) == "/usr/local" + and os.path.join(libpython, "dist-packages") in sys.path + ): + # GDAL specific to address https://github.com/OSGeo/gdal/issues/10242 + # Not sure why Debian's patched distutils didn't do this + return os.path.join(libpython, "dist-packages") return os.path.join(prefix, "lib", "python3", "dist-packages") else: return os.path.join(libpython, "site-packages") From 5d4c2d3a3ac604fe8de2cc5a3c81055e88da50d9 Mon Sep 17 00:00:00 2001 From: "Michi, der" Date: Wed, 19 Jun 2024 18:17:44 +0200 Subject: [PATCH 0190/1119] Add new OGR read-only driver for OpenDRIVE (XODR) (#9504) --- .github/workflows/ubuntu_20.04/Dockerfile.ci | 15 + .github/workflows/ubuntu_22.04/Dockerfile.ci | 15 + .github/workflows/ubuntu_24.04/Dockerfile.ci | 15 + apps/test_ogrsf.cpp | 2 +- .../5g_living_lab_A39_Wolfsburg-West.xodr | 7424 +++++++++++++++++ autotest/ogr/ogr_xodr.py | 308 + cmake/helpers/CheckDependentLibraries.cmake | 2 + .../development/building_from_source.rst | 20 +- doc/source/drivers/vector/index.rst | 1 + doc/source/drivers/vector/xodr.rst | 198 + docker/alpine-normal/Dockerfile | 20 + docker/ubuntu-full/Dockerfile | 19 + docker/ubuntu-full/bh-gdal.sh | 4 +- frmts/drivers.ini | 1 + frmts/gdalallregister.cpp | 3 + ogr/ogrsf_frmts/CMakeLists.txt | 1 + ogr/ogrsf_frmts/generic/ogrregisterall.cpp | 3 + ogr/ogrsf_frmts/ogrsf_frmts.h | 2 + ogr/ogrsf_frmts/xodr/CMakeLists.txt | 22 + ogr/ogrsf_frmts/xodr/ogr_xodr.h | 259 + ogr/ogrsf_frmts/xodr/ogrxodrdatasource.cpp | 183 + ogr/ogrsf_frmts/xodr/ogrxodrdriver.cpp | 63 + ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp | 87 + ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.h | 42 + ogr/ogrsf_frmts/xodr/ogrxodrlayer.cpp | 115 + ogr/ogrsf_frmts/xodr/ogrxodrlayerlane.cpp | 153 + .../xodr/ogrxodrlayerlaneborder.cpp | 122 + .../xodr/ogrxodrlayerreferenceline.cpp | 106 + ogr/ogrsf_frmts/xodr/ogrxodrlayerroadmark.cpp | 133 + .../xodr/ogrxodrlayerroadobject.cpp | 111 + .../xodr/ogrxodrlayerroadsignal.cpp | 168 + 31 files changed, 9611 insertions(+), 6 deletions(-) create mode 100644 autotest/ogr/data/xodr/5g_living_lab_A39_Wolfsburg-West.xodr create mode 100644 autotest/ogr/ogr_xodr.py create mode 100644 doc/source/drivers/vector/xodr.rst create mode 100644 ogr/ogrsf_frmts/xodr/CMakeLists.txt create mode 100644 ogr/ogrsf_frmts/xodr/ogr_xodr.h create mode 100644 ogr/ogrsf_frmts/xodr/ogrxodrdatasource.cpp create mode 100644 ogr/ogrsf_frmts/xodr/ogrxodrdriver.cpp create mode 100644 ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp create mode 100644 ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.h create mode 100644 ogr/ogrsf_frmts/xodr/ogrxodrlayer.cpp create mode 100644 ogr/ogrsf_frmts/xodr/ogrxodrlayerlane.cpp create mode 100644 ogr/ogrsf_frmts/xodr/ogrxodrlayerlaneborder.cpp create mode 100644 ogr/ogrsf_frmts/xodr/ogrxodrlayerreferenceline.cpp create mode 100644 ogr/ogrsf_frmts/xodr/ogrxodrlayerroadmark.cpp create mode 100644 ogr/ogrsf_frmts/xodr/ogrxodrlayerroadobject.cpp create mode 100644 ogr/ogrsf_frmts/xodr/ogrxodrlayerroadsignal.cpp diff --git a/.github/workflows/ubuntu_20.04/Dockerfile.ci b/.github/workflows/ubuntu_20.04/Dockerfile.ci index af9107f7e3bb..50aa2c17d8a5 100644 --- a/.github/workflows/ubuntu_20.04/Dockerfile.ci +++ b/.github/workflows/ubuntu_20.04/Dockerfile.ci @@ -258,6 +258,21 @@ RUN mkdir sqlite \ && cd .. \ && rm -rf sqlite +# Build libOpenDRIVE +ARG OPENDRIVE_VERSION=0.5.0-gdal +RUN if test "${OPENDRIVE_VERSION}" != ""; then ( \ + wget -q https://github.com/DLR-TS/libOpenDRIVE/archive/refs/tags/${OPENDRIVE_VERSION}.tar.gz \ + && tar xzf ${OPENDRIVE_VERSION}.tar.gz \ + && rm -f ${OPENDRIVE_VERSION}.tar.gz \ + && cd libOpenDRIVE-${OPENDRIVE_VERSION} \ + && cmake . -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/ \ + && make -j$(nproc) \ + && make install \ + && cd .. \ + && rm -rf libOpenDRIVE-${OPENDRIVE_VERSION} \ + ); fi + RUN ldconfig COPY requirements.txt /tmp/ diff --git a/.github/workflows/ubuntu_22.04/Dockerfile.ci b/.github/workflows/ubuntu_22.04/Dockerfile.ci index 394f7448c20c..84d95dbe3c60 100644 --- a/.github/workflows/ubuntu_22.04/Dockerfile.ci +++ b/.github/workflows/ubuntu_22.04/Dockerfile.ci @@ -104,6 +104,21 @@ RUN mkdir mongocxx \ && make install \ && cd ../.. \ && rm -rf mongocxx + +# Build libOpenDRIVE +ARG OPENDRIVE_VERSION=0.5.0-gdal +RUN if test "${OPENDRIVE_VERSION}" != ""; then ( \ + wget -q https://github.com/DLR-TS/libOpenDRIVE/archive/refs/tags/${OPENDRIVE_VERSION}.tar.gz \ + && tar xzf ${OPENDRIVE_VERSION}.tar.gz \ + && rm -f ${OPENDRIVE_VERSION}.tar.gz \ + && cd libOpenDRIVE-${OPENDRIVE_VERSION} \ + && cmake . -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/ \ + && make -j$(nproc) \ + && make install \ + && cd .. \ + && rm -rf libOpenDRIVE-${OPENDRIVE_VERSION} \ + ); fi # ESRI File Geodatabase API RUN curl -L -O https://github.com/Esri/file-geodatabase-api/raw/master/FileGDB_API_1.5/FileGDB_API_1_5_64gcc51.tar.gz \ diff --git a/.github/workflows/ubuntu_24.04/Dockerfile.ci b/.github/workflows/ubuntu_24.04/Dockerfile.ci index 16bb2c18b01a..9de48391106c 100644 --- a/.github/workflows/ubuntu_24.04/Dockerfile.ci +++ b/.github/workflows/ubuntu_24.04/Dockerfile.ci @@ -106,6 +106,21 @@ RUN mkdir mongocxx \ && cd ../.. \ && rm -rf mongocxx +# Build libOpenDRIVE +ARG OPENDRIVE_VERSION=0.5.0-gdal +RUN if test "${OPENDRIVE_VERSION}" != ""; then ( \ + wget -q https://github.com/DLR-TS/libOpenDRIVE/archive/refs/tags/${OPENDRIVE_VERSION}.tar.gz \ + && tar xzf ${OPENDRIVE_VERSION}.tar.gz \ + && rm -f ${OPENDRIVE_VERSION}.tar.gz \ + && cd libOpenDRIVE-${OPENDRIVE_VERSION} \ + && cmake . -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/ \ + && make -j$(nproc) \ + && make install \ + && cd .. \ + && rm -rf libOpenDRIVE-${OPENDRIVE_VERSION} \ + ); fi + # ESRI File Geodatabase API RUN curl -L -O https://github.com/Esri/file-geodatabase-api/raw/master/FileGDB_API_1.5/FileGDB_API_1_5_64gcc51.tar.gz \ && tar xzf FileGDB_API_1_5_64gcc51.tar.gz \ diff --git a/apps/test_ogrsf.cpp b/apps/test_ogrsf.cpp index 3f4592b4fd28..6567012256b0 100644 --- a/apps/test_ogrsf.cpp +++ b/apps/test_ogrsf.cpp @@ -2467,7 +2467,7 @@ static int TestSpatialFilter(OGRLayer *poLayer, int iGeomField) if (poUniquePtrFeature != nullptr) { bRet = FALSE; - printf("ERROR: Spatial filter (%d) failed to eliminate" + printf("ERROR: Spatial filter (%d) failed to eliminate " "a feature unexpectedly!\n", iGeomField); } diff --git a/autotest/ogr/data/xodr/5g_living_lab_A39_Wolfsburg-West.xodr b/autotest/ogr/data/xodr/5g_living_lab_A39_Wolfsburg-West.xodr new file mode 100644 index 000000000000..57cc798a758c --- /dev/null +++ b/autotest/ogr/data/xodr/5g_living_lab_A39_Wolfsburg-West.xodr @@ -0,0 +1,7424 @@ + + +