From 738e1816570905bd112985217cf4b5928de4480f Mon Sep 17 00:00:00 2001 From: Michael Barry Date: Mon, 24 Apr 2023 06:43:03 -0400 Subject: [PATCH] handle empty geopackage geometries (#561) --- .../planetiler/reader/GeoPackageReader.java | 13 ++++++-- .../reader/GeoPackageReaderTest.java | 31 +++++++++++++++--- .../src/test/resources/empty-geom.gpkg | Bin 0 -> 102400 bytes 3 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 planetiler-core/src/test/resources/empty-geom.gpkg diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/GeoPackageReader.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/GeoPackageReader.java index e4568a9b..e326436a 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/GeoPackageReader.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/GeoPackageReader.java @@ -25,11 +25,14 @@ import org.geotools.referencing.CRS; import org.locationtech.jts.geom.Geometry; import org.opengis.referencing.FactoryException; import org.opengis.referencing.operation.MathTransform; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Utility that reads {@link SourceFeature SourceFeatures} from the vector geometries contained in a GeoPackage file. */ public class GeoPackageReader extends SimpleReader { + private static final Logger LOGGER = LoggerFactory.getLogger(GeoPackageReader.class); private final boolean keepUnzipped; private Path extractedPath = null; @@ -122,6 +125,7 @@ public class GeoPackageReader extends SimpleReader { public void readFeatures(Consumer next) throws Exception { var latLonCRS = CRS.decode("EPSG:4326"); long id = 0; + boolean loggedMissingGeometry = false; for (var featureName : geoPackage.getFeatureTables()) { FeatureDao features = geoPackage.getFeatureDao(featureName); @@ -136,11 +140,16 @@ public class GeoPackageReader extends SimpleReader { for (var feature : features.queryForAll()) { GeoPackageGeometryData geometryData = feature.getGeometry(); - if (geometryData == null) { + byte[] wkb; + if (geometryData == null || (wkb = geometryData.getWkb()).length == 0) { + if (!loggedMissingGeometry) { + loggedMissingGeometry = true; + LOGGER.warn("Geopackage file contains empty geometry: {}", geoPackage.getPath()); + } continue; } - Geometry featureGeom = (new WKBReader()).read(geometryData.getWkb()); + Geometry featureGeom = (new WKBReader()).read(wkb); Geometry latLonGeom = (transform.isIdentity()) ? featureGeom : JTS.transform(featureGeom, transform); FeatureColumns columns = feature.getColumns(); diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/GeoPackageReaderTest.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/GeoPackageReaderTest.java index 1957a262..ea0675be 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/GeoPackageReaderTest.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/GeoPackageReaderTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import com.onthegomap.planetiler.TestUtils; -import com.onthegomap.planetiler.collection.IterableOnce; import com.onthegomap.planetiler.geo.GeoUtils; import com.onthegomap.planetiler.stats.Stats; import com.onthegomap.planetiler.util.FileUtils; @@ -13,7 +12,8 @@ import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; @@ -45,9 +45,7 @@ class GeoPackageReaderTest { List points = new ArrayList<>(); List names = new ArrayList<>(); WorkerPipeline.start("test", Stats.inMemory()) - .readFromTiny("files", List.of(Path.of("dummy-path"))) - .addWorker("geopackage", 1, - (IterableOnce p, Consumer next) -> reader.readFeatures(next)) + .fromGenerator("geopackage", reader::readFeatures, 1) .addBuffer("reader_queue", 100, 1) .sinkToConsumer("counter", 1, elem -> { assertTrue(elem.getTag("name") instanceof String); @@ -67,4 +65,27 @@ class GeoPackageReaderTest { } } } + + @Test + @Timeout(30) + void testReadEmptyGeoPackage() throws IOException { + Path path = TestUtils.pathToResource("empty-geom.gpkg"); + + try ( + var reader = new GeoPackageReader(null, "test", path, tmpDir, false) + ) { + for (int iter = 0; iter < 2; iter++) { + String id = "iter=" + iter; + assertEquals(1, reader.getFeatureCount(), id); + AtomicInteger found = new AtomicInteger(0); + WorkerPipeline.start("test", Stats.inMemory()) + .fromGenerator("geopackage", reader::readFeatures, 1) + .addBuffer("reader_queue", 100, 1) + .sinkToConsumer("counter", 1, elem -> { + found.incrementAndGet(); + }).await(); + assertEquals(0, found.get()); + } + } + } } diff --git a/planetiler-core/src/test/resources/empty-geom.gpkg b/planetiler-core/src/test/resources/empty-geom.gpkg new file mode 100644 index 0000000000000000000000000000000000000000..c8294df1a48d481bf81d80d2dbf178b3aaeeeb17 GIT binary patch literal 102400 zcmeI5%TpW49mhxJA;1RgU3;|~cGqp~5}~pLlEB9Jkwk_8YZpmakM**SYf39MVq$3q z&kP1!d6dA9Y~`4$JvgUSCFfKQd0g^GeUA{1;|AFOci8{(@jg50E?=;32giB- zqF*m8eY{|Ex1SDh<99ylVJ=<6|J2>z8UD}k*XF4`7|uZe1V8`;KmY_l00ck)1V8`; zKmY_@HGy+(7USs=_GkFaAp6@j_-k&6{TW())hQu95C8!X009sH0T2KI5C8!X0D%`G z@O{pCgFDxEzL=JBbVZbCj^wGFS2Am>VwNNm$yhwbub2HQC-y*DSypnQlwoVV3SFTJ zm9mtmTdGEjcfTng&THH`$NBr!RAw`ZMpZE*l@s}<5_#o>R0Hji$j;ecuJm8z&YgHH zmfpLwkR(^8^z7xRCT97iHTx>C~-wB9dCZ>(*m-``puk$$aIIS^DHQwHTP$m)vn=H;cRrjPrz|7T4Q{&U2na@strT@s^G5iaTeQHL92hu1b2!H?xfB*=900@8p2!H?xfB*;_ zCIVs)n-FNZ?LU1s0QUb5liZOP5C8!X009sH0T2KI5C8!X0D)sjpl!eZ=T09(x+pRT zfB*=900@8p2!H?xfB*=900|LV!#pS6Dc3@nE52KM6}H31xh>(dGdJ3Ls2fU(x@aWORA=1L`l0^n~7{j zl4Om1PDz2PD$!Om5(#>|zOt+a){P6C!rULP`H_Ur@Z0X4Fku5h^Yv6?Y*Kz!dF>~$sm4o;ME zbW?p$5H%`f)-~C<7V7J+5Uh6@+L=A&bou>`r!z*IRPSGZJy@>(;hT#*srMygMkIoi zDN>T+KTVNXoc&KPF8XXmjl@#C-Xd50YFew^d{?QMU)4y1x6vz@t#fm~-ELeHH(CAM z6jbpMC3Eq#UZ6^rOwX<6p0n{-GR2zqScqHh}^)Yx!f}dy2dTf?Ynz&iFpixOvZK%E8o%aWvu1l93Ps}LmMLVe zCnh6qRy@6vuL{U4Z=-P3wTvca3W7pc1a(U#V^%=#!pUCO-Antfr;L1KUDf4r>M`eZ zx~8Wc&(2rtt8RapYRZ<7l?&@dNj1b@@7UKU>-r$O3)^SL+^k|(BGIBb|H zio}-nN>r(J#YQKaq*-0l6Vpqn+O^M~Z6AAV(is>Tsi|`7?Cz{xOVY8(?KFE{s#e09 z@T`jJ;iX!^+XC;aX4Y#{R(N?`iuCuo?oIEhOBJD58X<(5>cl{#@5Q4s`;ht+`b%&1 zI9(GHj?&$#V#;}iNy}`nj11TvdK(qe?roFH9u3b5&1mZiWvQ%7)y`p#pAWOQlNmja zH{9FnN>A*G%bu=P@9a*_op(<-T^^6)$wswoHRUQ_bTyW#>}6N9JqpA|G8sXyO0{8p zVvaTX;)+NW)5FwWUyiC-MO@QFHmEQ<+;Y)99$z7&YC@Kc4)UyjW22c=BwFp^NIBpb z8)U3#Vv&w{Mwk7gMgM3nHG1pnX!Ppn%4Z&*N0K)@Ua!yIlKY6g1@{$g4L9?$HQ06i zLSto6^y;AZ90zG#D~;<+cdskz*;B%0DOeSueAzS8wf|d9#V_C5&4#gk`cQVe%VG4r zJKm~t)b#^~?$^1UXjGND5N7YuBiqhWwC~wPYv5sh2{~Gz>?yjYWf5BaLd90cUsg72GQ{1{!n#R#KE(zAb8M1C zpJa&Uqke{z&E4BWb!?8bCKJVV z)tI@@D{SDxp5=|n`|32Q{)}c!;Md&royufs{Q$=1rmi?OwCvgzsBed(#i<)!qrCXNc*YTw+r#pS<1UC@fAP|7wXF#A!fnznXzVq#-sW1M-fWwQ4(c{%=nPXYzhd*eH#ihIOUh6!j^9T-!c4zV;>wK00JNY0w4eaAOHd&00JNY0w4ea ztt9ZMSD!p+`C5OoxdVOu-`TUmo&4{~KMnZ$zI1l?tepD##B?i*i@1*ffv$-oPzV$O z1lmDhXXFAibN8V1z_ESLFmq?l-nM$O*tEO7mQk1u;`XQbcC9JA_GiF7CdthFvfRuz zp3kJB2k`C;6DFNvsp!~_wp}`U4%&%EPtVmk$>_=NDHq+4ws+^v^GqUgNp@`CZo5Pj znZ1^xogy(3e5-RZG17OB8DCB7iY_!s_U;V5&!mzqW&5_7kl34$+6bwQNm+vUB$7y_ z!;2(kSTJ?BF#~RrPDWx2#7J^X%o%puHw_c*`d?AnGS0WWH{R=Y-M+Y|0rkqlpK2M% zuR5G9ZM^H$?YZuZ8qzeD6|3HLmv7eSXPsw*k^F`iPxvYkH+$EF&0u-$>u1!g++b4Q z_G*bd6|^iRjjx2Ld7p%j)F$rrK6SeLM_7Lx?fdB+%Y7D( z5sS<0b!ss(Cee*?%gk-rfIbPw=8SiiBvV2}<%?_DmSMBi4CeJ(rpC)AC=={OAxn$l zSzd1v