From ef24e91f0b453d34511c0d5daf36a967b83cb77e Mon Sep 17 00:00:00 2001 From: Erik Price <188935+erik@users.noreply.github.com> Date: Mon, 2 Jan 2023 09:19:05 -0800 Subject: [PATCH] Add basic support for reading GeoPackage files. (#413) --- NOTICE.md | 1 + planetiler-core/pom.xml | 6 + .../com/onthegomap/planetiler/Planetiler.java | 26 +++++ .../planetiler/reader/GeoPackageReader.java | 109 ++++++++++++++++++ .../planetiler/PlanetilerTests.java | 41 +++++++ .../reader/GeoPackageReaderTest.java | 53 +++++++++ .../src/test/resources/geopackage.gpkg | Bin 0 -> 114688 bytes planetiler-custommap/README.md | 3 +- planetiler-custommap/planetiler.schema.json | 3 +- .../custommap/ConfiguredMapMain.java | 1 + .../configschema/DataSourceType.java | 4 +- .../resources/validSchema/road_motorway.yml | 3 + 12 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 planetiler-core/src/main/java/com/onthegomap/planetiler/reader/GeoPackageReader.java create mode 100644 planetiler-core/src/test/java/com/onthegomap/planetiler/reader/GeoPackageReaderTest.java create mode 100644 planetiler-core/src/test/resources/geopackage.gpkg diff --git a/NOTICE.md b/NOTICE.md index df12a817..0261f32f 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -25,6 +25,7 @@ The `planetiler-core` module includes the following software: - com.github.jnr:jnr-ffi (Apache license) - org.roaringbitmap:RoaringBitmap (Apache license) - org.projectnessie.cel:cel-tools (Apache license) + - mil.nga.geopackage:geopackage (MIT license) - Adapted code: - `DouglasPeuckerSimplifier` from [JTS](https://github.com/locationtech/jts) (EDL) - `OsmMultipolygon` from [imposm3](https://github.com/omniscale/imposm3) (Apache license) diff --git a/planetiler-core/pom.xml b/planetiler-core/pom.xml index 765b7764..8eec1c05 100644 --- a/planetiler-core/pom.xml +++ b/planetiler-core/pom.xml @@ -20,6 +20,7 @@ 2.19.0 0.16.0 3.21.12 + 6.6.0 @@ -138,6 +139,11 @@ guava 31.1-jre + + mil.nga.geopackage + geopackage + ${geopackage.version} + diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/Planetiler.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/Planetiler.java index 2551c1a6..0ba01557 100644 --- a/planetiler-core/src/main/java/com/onthegomap/planetiler/Planetiler.java +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/Planetiler.java @@ -7,6 +7,7 @@ import com.onthegomap.planetiler.config.Arguments; import com.onthegomap.planetiler.config.MbtilesMetadata; import com.onthegomap.planetiler.config.PlanetilerConfig; import com.onthegomap.planetiler.mbtiles.MbtilesWriter; +import com.onthegomap.planetiler.reader.GeoPackageReader; import com.onthegomap.planetiler.reader.NaturalEarthReader; import com.onthegomap.planetiler.reader.ShapefileReader; import com.onthegomap.planetiler.reader.osm.OsmInputFile; @@ -337,6 +338,31 @@ public class Planetiler { })); } + /** + * Adds a new OGC GeoPackage source that will be processed when {@link #run()} is called. + *

+ * If the file does not exist and {@code download=true} argument is set, then the file will first be downloaded from + * {@code defaultUrl}. + *

+ * To override the location of the {@code geopackage} file, set {@code name_path=newpath.gpkg} in the arguments and to + * override the download URL set {@code name_url=http://url/of/file.gpkg}. + * + * @param name string to use in stats and logs to identify this stage + * @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments + * @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and {@code + * name_url} argument is not set + * @return this runner instance for chaining + * @see GeoPackageReader + * @see Downloader + */ + public Planetiler addGeoPackageSource(String name, Path defaultPath, String defaultUrl) { + Path path = getPath(name, "geopackage", defaultPath, defaultUrl); + return addStage(name, "Process features in " + path, + ifSourceUsed(name, + () -> GeoPackageReader.process(name, List.of(path), featureGroup, config, profile, stats))); + } + + /** * Adds a new Natural Earth sqlite file source that will be processed when {@link #run()} is called. *

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 new file mode 100644 index 00000000..15a42b65 --- /dev/null +++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/reader/GeoPackageReader.java @@ -0,0 +1,109 @@ +package com.onthegomap.planetiler.reader; + +import com.onthegomap.planetiler.Profile; +import com.onthegomap.planetiler.collection.FeatureGroup; +import com.onthegomap.planetiler.config.PlanetilerConfig; +import com.onthegomap.planetiler.stats.Stats; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.function.Consumer; +import mil.nga.geopackage.GeoPackage; +import mil.nga.geopackage.GeoPackageManager; +import mil.nga.geopackage.features.user.FeatureColumns; +import mil.nga.geopackage.features.user.FeatureDao; +import mil.nga.geopackage.geom.GeoPackageGeometryData; +import org.geotools.geometry.jts.JTS; +import org.geotools.geometry.jts.WKBReader; +import org.geotools.referencing.CRS; +import org.locationtech.jts.geom.Geometry; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.MathTransform; + +/** + * Utility that reads {@link SourceFeature SourceFeatures} from the vector geometries contained in a GeoPackage file. + */ +public class GeoPackageReader extends SimpleReader { + + private final GeoPackage geoPackage; + + GeoPackageReader(String sourceName, Path input) { + super(sourceName); + + geoPackage = GeoPackageManager.open(false, input.toFile()); + } + + /** + * Renders map features for all elements from an OGC GeoPackage based on the mapping logic defined in {@code + * profile}. + * + * @param sourceName string ID for this reader to use in logs and stats + * @param sourcePaths paths to the {@code .gpkg} files on disk + * @param writer consumer for rendered features + * @param config user-defined parameters controlling number of threads and log interval + * @param profile logic that defines what map features to emit for each source feature + * @param stats to keep track of counters and timings + * @throws IllegalArgumentException if a problem occurs reading the input file + */ + public static void process(String sourceName, List sourcePaths, FeatureGroup writer, PlanetilerConfig config, + Profile profile, Stats stats) { + SourceFeatureProcessor.processFiles( + sourceName, + sourcePaths, + path -> new GeoPackageReader(sourceName, path), + writer, config, profile, stats + ); + } + + @Override + public long getFeatureCount() { + long numFeatures = 0; + + for (String name : geoPackage.getFeatureTables()) { + FeatureDao features = geoPackage.getFeatureDao(name); + numFeatures += features.count(); + } + return numFeatures; + } + + @Override + public void readFeatures(Consumer next) throws Exception { + CoordinateReferenceSystem latLonCRS = CRS.decode("EPSG:4326"); + long id = 0; + + for (var featureName : geoPackage.getFeatureTables()) { + FeatureDao features = geoPackage.getFeatureDao(featureName); + + MathTransform transform = CRS.findMathTransform( + CRS.decode("EPSG:" + features.getSrsId()), + latLonCRS); + + for (var feature : features.queryForAll()) { + GeoPackageGeometryData geometryData = feature.getGeometry(); + if (geometryData == null) { + continue; + } + + Geometry featureGeom = (new WKBReader()).read(geometryData.getWkb()); + Geometry latLonGeom = (transform.isIdentity()) ? featureGeom : JTS.transform(featureGeom, transform); + + FeatureColumns columns = feature.getColumns(); + SimpleFeature geom = SimpleFeature.create(latLonGeom, new HashMap<>(columns.columnCount()), + sourceName, featureName, ++id); + + for (int i = 0; i < columns.columnCount(); ++i) { + if (i != columns.getGeometryIndex()) { + geom.setTag(columns.getColumnName(i), feature.getValue(i)); + } + } + + next.accept(geom); + } + } + } + + @Override + public void close() { + geoPackage.close(); + } +} diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java index d9ff4eb9..1681c22f 100644 --- a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java @@ -1667,6 +1667,7 @@ class PlanetilerTests { .addOsmSource("osm", tempOsm) .addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite")) .addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip")) + .addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg"), null) .setOutput("mbtiles", mbtiles) .run(); @@ -1743,12 +1744,52 @@ class PlanetilerTests { } } + @ParameterizedTest + @ValueSource(strings = { + "", + "--write-threads=2 --process-threads=2 --feature-read-threads=2 --threads=4", + }) + void testPlanetilerRunnerGeoPackage(String args) throws Exception { + Path mbtiles = tempDir.resolve("output.mbtiles"); + + Planetiler.create(Arguments.fromArgs((args + " --tmpdir=" + tempDir.resolve("data")).split("\\s+"))) + .setProfile(new Profile.NullProfile() { + @Override + public void processFeature(SourceFeature source, FeatureCollector features) { + features.point("stations") + .setZoomRange(0, 14) + .setAttr("name", source.getString("name")); + } + }) + .addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg"), null) + .setOutput("mbtiles", mbtiles) + .run(); + + try (Mbtiles db = Mbtiles.newReadOnlyDatabase(mbtiles)) { + Set uniqueNames = new HashSet<>(); + long featureCount = 0; + var tileMap = TestUtils.getTileMap(db); + for (var tile : tileMap.values()) { + for (var feature : tile) { + feature.geometry().validate(); + featureCount++; + uniqueNames.add((String) feature.attrs().get("name")); + } + } + + assertTrue(featureCount > 0); + assertEquals(86, uniqueNames.size()); + assertTrue(uniqueNames.contains("Van Dörn Street")); + } + } + private void runWithProfile(Path tempDir, Profile profile, boolean force) throws Exception { Planetiler.create(Arguments.of("tmpdir", tempDir, "force", Boolean.toString(force))) .setProfile(profile) .addOsmSource("osm", TestUtils.pathToResource("monaco-latest.osm.pbf")) .addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite")) .addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip")) + .addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg"), null) .setOutput("mbtiles", tempDir.resolve("output.mbtiles")) .run(); } 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 new file mode 100644 index 00000000..e8594137 --- /dev/null +++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/reader/GeoPackageReaderTest.java @@ -0,0 +1,53 @@ +package com.onthegomap.planetiler.reader; + +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.worker.WorkerPipeline; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.locationtech.jts.geom.Geometry; + +class GeoPackageReaderTest { + + @Test + @Timeout(30) + void testReadGeoPackage() { + Path path = TestUtils.pathToResource("geopackage.gpkg"); + + try ( + var reader = new GeoPackageReader("test", path) + ) { + for (int i = 1; i <= 2; i++) { + assertEquals(86, reader.getFeatureCount()); + 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)) + .addBuffer("reader_queue", 100, 1) + .sinkToConsumer("counter", 1, elem -> { + assertTrue(elem.getTag("name") instanceof String); + assertEquals("test", elem.getSource()); + assertEquals("stations", elem.getSourceLayer()); + points.add(elem.latLonGeometry()); + names.add(elem.getTag("name").toString()); + }).await(); + assertEquals(86, points.size()); + assertTrue(names.contains("Van Dörn Street")); + var gc = GeoUtils.JTS_FACTORY.createGeometryCollection(points.toArray(new Geometry[0])); + var centroid = gc.getCentroid(); + assertEquals(-77.0297995, centroid.getX(), 5, "iter " + i); + assertEquals(38.9119684, centroid.getY(), 5, "iter " + i); + } + } + } +} diff --git a/planetiler-core/src/test/resources/geopackage.gpkg b/planetiler-core/src/test/resources/geopackage.gpkg new file mode 100644 index 0000000000000000000000000000000000000000..04cc66ee356ce6a53c3202dbd4b44955a3de46a5 GIT binary patch literal 114688 zcmeHw349yXwZ7y{UZw0NQJmbE7+FM$w>XZoT9Rd3i7h)?5|RKyvBvV$TAa~1j?)yd z%?>GTc$C`Z6;dd)^wH8sOIx7DRoVBnr94V`g+j~H*RqrbD75^)8IAU_34#70?bXlc zT65;knRCB;&biCnJ92LAWO;`4g}I=YCk={O3Z+ufLWn}4C z|05i?_iSlT;irBrR;=_a&Icz)b5!6j(!8rVTBoyjiH=E~Jzn|g!g9CmK&juI|Dve|J#s3YnKEB_>h9V5dXXddu ztR|P0xE!|jcB_NvGSAD&&=Iq>t=nNGHoMd6aFK3%_H{af9K>p}bPz}Pc3jYIvlFMa z(`s=Mhsox&R-4S-4wr%GVtSEqi16Os0fy+ZbkOO@UN#)?@=S#EhC>ma^Rgj+Es;to z7LW9LL!mHFb~7ZvL?VRm_l8KFPEWRXSfOdP-MW2cTK@H*H7e~^z_@HW z825+6K~I3$%LF__gFdK{3DO6Gm?a?rp}VJ5z(3qal6gKNpfMef2hbE1zBZ`@LoPfZ zkS*37sTdeiTug6BxvUSD1%sXxw1ntpYqw`g&=ZHX4dKLYu{vXLBOLKeh>xT|t=DfV zD=2pwl?pcGWA;V%1kjRtyhD6gJoaQ(lcz3|qaI&UtSWD8RGuHxDf7H1!eqs9fmbr4 zIS4sY;Yc@$RL~3&F5+Q**|i&FL!NzEwIgsxvbo-Ua(Thc&YNBrem)ra*eWBVo<-Rx zp3piaYdBWk?AnbCdU@6x@Ni6@Co&Qt)lx=%dr3k0uEt}oVIZGf%R+sWo~KZ$%A1;$ z7cNaYyq^gN8J-*Q^o9dN!B9l3{!C_Jc3o#?nZB|OG0Ad~At8hx8D!)#OlKSWiOXi+ zA$VcJZG+jYl$wUK3O5r<8A-LiRJ9Fg?*$A*MbdnB2v_%}T8Z0k+v-L`pY-Ci{P1xh ze<;1;Q!*cuYiC+hujixNlC7wq{EVgoxe8NH50@@+^A8Vv8Lwx=v>Zcq6>07u{Mv8c6gRUAC@d6G`G3tI<2| zde6FgFwN+hftNM2847yNwSf|$;QX{#6q(*Q6hD{vlA1{zFR~##(;}GU zWH=_&%Mgqoc_z5){rE?$@sj(eMKu2 z^K;H)M=Q3sJIR_=f_grNXM3$Z&h}YcJC!Q^&zbUSRW9lI(8$(4r6rrL(u$}`T8 zIkUQen-P$>Z2@SKo{gsAVne1ztYwLvUn8k7Js`N2_WJk`GwRKIa8XM}H z>l^Eun^!f+>CjwPUoYx#nhk~Q!~mZk^5J4T9*%ZEwHP}b@=JWH$?1~QcvVc}Ks;v+ z9+!%jUl@bAtzOwww`S**b1QfKHJ`bm#j?Ai_uIQ~n{qZj|N6EkZ*8$$zT>{F zr=2_N55gjUrRGfq{^*AyKoOt_Py{Ff6ak6=MSvne5ugZA1SkR&fqx+cN^&cT;xh+y z{Qoahv9u@@0g3=cfFeK8?X);qbMSvne5ugZA1SkR&0g3=cfFeKqIOC)tMqd5ugZA1SkR&0g3=cfFeKj& zP^NxEF!sfbj&qVC{?l8)kiczh`LB=jEBqJQj!5G5r@g< zv{swU-42(5=wf=3aES2U-2sN^vUJkv$X+%aKxrbRHyn!aoR^2Q4#xfAaL^NA_A-IvwaqLE2?!<24EK>_o{tFV z@!MsJ3UXFin{JgUx!03g$tKsUV_@bV1DMBYk;(gdK3(>hRE!)ae4EM&%AH1~f(`kY zeUUu@mS;TPAwDb~doq33Q2cV)OpYC7i*-jT28I+L)f-YS z>%(QipeF?_q1Caq+cPETiNo54ux__loiVu4EbvT-kEB4Y*N-nLR+Tq4D$kFJ%&Zqr zgvpBI0oi`myKOc;I zY*=PglPDX-6B_ws1;xsnT{)3KFVA`d9**hrL`EW{TFR(zFDWSB)p*Q)3gokWRj7~B z^Asvoc~g_}!lg-n72dzh@Z5-}HyjuWh9YA1XEF=3>pC;b^p$OhNtSc$gb;pYkdezU zoo(zVE}MOa;6w?p3ud!YY8t*K+)OBCB-Q#-)i$8pBw!#alIC(lxVkshO5AqaRyX=f zNheIp4;K~khtexPCG#*@_Cv&uE&_mJ*dBS%jxv)+UN=g&$s2tPRP$ zL(%Ahd{uc(jdFBX(lEn*4z(4FSE2%`jDl=~Og)`aFEa$5KE}%raSZM<6iPM@t=2Y^ zyEE4E=uHI$5JbyS@wzP7^4|2216hD<}XfqTDBN?wp zaHaG-2G)n5&-Sql7dsKNUW1Q`^m6PV&tgy^Hn{Pk>vV>IH^O^*F*@+0eZy$RW$Q|| za3r3w8iR~Jo((e9x~d(1M3qcr8ed5I@Xs&OP=^V}>8UG!MhrLSZO7 zma7!o-f?}>I5OG+(e^WVIoYO?kt5c6$n>SAiKwYD(GceR-VnPV%~9Mk5>Ynw6dWha z`Os9!Hs3y`j}5V+hzxVgv^#xS(R+&0oP0&$cUA6!qP&}Py*c@s2K94gFP43ybh7xt zk}He9P+U~3OdI?cy{CMR5b!By zQ+=qH3v!-dAM5EGNOz!MLTGjuC5}-w>q~3zV!{Sxg(b9@S)6`sM|o_5Vcm3txzv93 z@s5pUWMqzxF>jNa>k}IvrzsH}iioo|eh!Tm;56FiDy=a7^eY=sDh1p`xAAf1yrk$zpNvKJLyQ zWQYT^c#pVz`T+%jsl|K$BxOn}=ouVH9d;3u(~>fs<&zt8Bu3skVVHwCJ)Hslby!}= z=jD8!A&$)$YXvddkeL!l%Hf%Pe9gXK0D`2!Q@Vs?PMv;(wxE3LiW$eskj8`}7&k_w zxsW7d!RYWJRr!h)%JHfsn90*3oXNxa*?=ZbX3d9WkG&F^lLNFQxG7`P^d}8+!@QI# z^8?FXhGftFrA-|gNczq7`ksXaLcw>4<(8hbM3_B8!U#3va86peGfq|u19}6l)Q@gH zNmX7^p&awaWFqzmpvPn7>@+EradsuCCY61xuQFkesSh7aZdbR>jw`#YsS~TDQkbu+ zBt1BZC()IH4wa&jRM$7I(t{K9asv!!#C)MFKQe-=1)Ssg18gW&f<7O?U#WRlXaeSG z-c|oXeW}`7_Ey<{lo?8YUpi6hC{>r-Uvh3qL-9++hl@`yo?G-t(RoE{3tua|sc?7U z0@b~$1FE`$=L!xNcnX%}Kb3zde@lKr-uLs^yvp21axco=kn?uVtvUW2t@0`5WlF0u zM{$SP+I$>|wNa&LtI{r;-`=B8{sF&XpcG~)?8>P(nk>#K_Lh>z9t&DpEZhjHAV4fE zKXP&{K7D<7I3F;=<+$)}%nq1VK}A`T`>ko%yMOYgk zLZ@9eH%*h19?9>z&+MG)e*Tj02YE|N>(C(PRG|ph8(>cE>l2hos=_h8E%gvnm#N9$ zuf2C(eyelJ)@~lyu*cG3;!uZvJ{&q=VNeK$lP^|F9fWB!g*~|P%DXRyu)CT*v*$aU zrNt4BL;@r7rP^E@QzyMt&M*Ggfw~sw)ZtmYb|Fe-4|jQqk+gdQ0q>9xPoCUzMcZ5h zSv8s3eDBF^uRq`DoC-V~-ttt$($eYW{9)n>4~K{acr%80Cc2*&2F_nw?WOJoL=6&kYL-g?wR% znMR1s03rHJP3E>Tn;vd*PObdWHMv_yfDkiGEMATa2LhH^j@rZPi0)mYYq@J%5FIy>rU??zcZz0z)!0p%Bbc6e=rbb2Y>mwad!WYOUdG z&P&TSI;ZL$`_G?l+;3^wj`=y#G2-R<$X+%8H>s^%ljbIvgkeJ6I>>6ulvVi1VZCvK zb86u~=1hI@vv66CMT{ry3?@z)9UeUey!*ZK0xhRN;K`W|{L1$|CoPAms~+jE_!TO$ zEzI$-P9DuHDj>^!tyK_`6$SS7EQ{Q*$vO4>tlFUnOv=oK!vg`>mC@oh$%+0V#Ij1^JH{a2Gj-|!P24I)OIfz2~<-}M{mMhZ2SLasUg_!iyYY*nXGAb5HK)gL5 zU!+Zy5P~3*9si#vm*V=yOj~*J zHT~{$aecApXSx?O2AuK0C_fXa)Iw~M)MW(1gL15M{1hT;e{=8qqT`k^0mJbI=hq#a;A}h(h zSgw$>Uv>2LA$nZn{PQ!G&p{F*5QB$GJ?JwS-2_XKx&*~!9Bi{TR>*W`t>3iYJs-w% zbkqC3pY0Xp4Fz`twWMk?vf36w7J_8D3Fyyw`>oqx0!2UD+kFEnz#=ZYf?J5%$yH?Q zLdZcJN|!UTtM)Aw8nm3gpS*JCK1<6sEUL3(d7aVSYLS!DaT27!Nz)sT_jW%zeifqG zrkq>;`y@0(U2G`CM8dpi5D+8Rf^?L}FiO|wg#&e=AH&^eoqlu2@6il7`olgvfQSel zFIbnHPVEa~2&J3U4I7`o<|2f~!MBfo`yAMT6VF%Jp&&7F!SFO~e(QV)LKsUI^t;(d zcg}%IkAA;u{Ud{ji(wwV(kp5sIBaHzsC}NCIyaAwefB4il5@nvdjwG(e(O?$5HJr z<+Fv?FqW1MJVuW7iU6<>o0A=@5hf4_58GzP3YA{(x2x}5C3J_3H@mj;eJB(gVM6`k zVMNwgUCD{sG7F-zx@k|p`(4)yFzE2vKV7}iZ)w>gw1!R|U8C-RPwdmkdTyHr!eEc- zRkrAvCFpcIr`BCN?>CM8LZjjJ`NF*e(>JfJ>KIJv9_zm;H{?bvdHOBxANRAOoKSDS zRP!>vYA%BmgzI!EhgRJgy$*3iwdiq`Dga9yVtK)yr>U?~$bb>1%b54u(f$TB&bQq2 zw z)R_7>7E1$-rXWno!lExjwtG3!#lUy5(5MFeL0@=(SRO%dA?~v)^FIH1_n{I$?(;<1 zlBEUc3wH5j8yrwjc@M0tiSHN84S?(wRvdtx=t9e&+o{4W&?=?}uYso|Dni&xX**56c46S^#NTp}Fy;8&wZO+IQZ2=dE{zHc=RH#kG>@KegpU zN|q~JN;a=@p_cBgJL`5^N^|H84~b25cKmA1lhdZ`x+kxm1#RZ^^e?y-+H4;3vAwJp zrE~6)tL)9WkdifGd14iLFBejLZx@wqhBj@CkKu&Tr8rz6&h4^JZqI=%^jFiJ{FayA z&!5`toch7;Z}(R0hAa$Buu6gT8e_dWEE);OcN}a=$jj=31djggbV~=C9`7Bubt9Mw zO=0WLZbOWA3X{26F&cyAxsAg6|CE|H6!@bbiU37`B0v$K2v7tl0u%v?07ZZzKoOt_ zPz3&E5GYmVRw(1E61EBB|3*BN*L+{YXcp4v|M6u6^!YzSpa0Y6|5zVGpa0Y6|LKnj z==1-qxp(^fzn>SDCdfQ$qR;>7^Z(QZn)La9>grSa{C}Ed1oZiT#tI7h{GUGmmzJ!5 zJkS5d)$hXie_6rl3e7DVR#TyVLj47`t?bRR!)1=Lg3_Oso?W`Gt^O3o@-Qv8eJ zON*^VZx!8GdKfFeK|Z$B^kj=Lt%Rpv5N~avAE8}a)VfqFT1p^ z)s1^#d2sq_)AN3L>Z41rOt00Xda(vep0^7x?&#x$Ee5h<)GimKG-OIKzCJ0gxV*n} z<2AilGHBv@``Nur#Ax^K9TC=Y>H_lOY)-lLc}LoHuL$cQ59c;cIUz2-cF=_tp&`Gx z*Cl%~W19n_vKB=?+qCV#V^|;jM%e+q18b<`yVIuM<<`0tapv^kkYBMhWfCbXr>)E?runG z&s>1oX{k7}6Xn|d=l`f>vBO|H_BsU^EI<`@EbI)2iOJ^;Mo4UTh1{ymHhWCL^a-o0 zzqxJb5}2HdpR@F8Ebni_ZY-~Vh$nV&pNHI9)Go-$g8lt1uTM=v&X?XjxaQ1MIl{Ij zS-{#lAqTLhFFTKT&Z*wm?woqItmn)>!aU7bXB&aZ8@q(fb8?#v>{}oQ2AVGCf*;IX zc~zTp>Y=(bm(Kz4+gQ$z7e9Hiq;&`{ATqX3E3b1iGbm~*DE`oM{8n<66Y+$eigqI@urriS9)**-0 zJ8%B)=dZ9jr`9aL()zj}B|L;(SRLV^h_IJJ4l7$bdw_uY!ap8-MYX@?2qQG!`Q#mJ!Ptc4sT(WG&d9_u!V1uS3o+58XBYu5m2b=E4yy z{|<9TEcx~F{a9@-yW_-Sfi#$9x|`f~{q2)$V3w)_zxlcT0%2blUQ0{bu-#O4XO_tf zA*l6qA^eL?pS=S@)_wl^*ZzUMU9Mp^v|HHoCRZwxNlwJVZ6__+X>(3}ukYp3hb|FX zh~9zNZa^7NwY10{3)`n|pp0(j(i;B4LmA=uK9UX+eIHy#>< z=_xlXJo9mEPHf{?*e}|r!Nhh08HKtw#$=^C!wsLg=6Y@`YU+(g*SX>H+aN0Bg>$i7 zpLgF3p1O4d#5HI7R?fgH4_|BtM1kL3y9i#ilGztH!-e*t00-b=GwdiinvG;f?bL|5^?R zpV|52qY-SObTJ{sb73EW(ImT3*s>O4vi1x;c3R+WJNB`R)|=J_v2C$k*af3iu_9LV>Q;|a>Ha|90_5b|E|Nh1RY(m&UAZ%e0w))6+3pTHToUA=4mweH!5Oztt zcSG-E|A&nju&rTZM|c=pItccV?3I^gZHnoXz6ank=j$3BT9oJy$9^lcC|$ihP)XRG zi(LtFo3rezAtq}(<8xQs?Qmj~gy*XpE~4%)9`ItW_O3RvdAUyw9s=ukgAN^1SC;}7#iU37`B0v$K2v7tl0u%v?07c-F zM_|l_m)>hvRGc>;{rsZz;M?!>288GK@gGxM-`;BK>>3L2Y%6}{gbm?W2gKi{-&jn@ z`qTsAu+>Hen2`;Yt>!jQtI1{cbefzlPwc02D>v5H*4GuZozlu(l@A|a3X z#CvV1bat3}tZlYVYo(Ao^%j<%cF#6bryFP7uC_G^iK%(4c1w3FekRq^=IHM7;0iIX zQ9iS?+wHJeJ@IQYv*HGclCQ6s78G&(uQ{T?AN^1SC;}7#iU37`B0v$K2v7tl0u%v? z07ZZzaH1e!R2G(3C=?rt6uftL0Pm#{-=Y)|=Q8T*jdk^|+BIwI8`jpXUfEQ)YG;|| z2F13r2XvaxXl7|%Q@^ZvQT?vwIn9&mziJ*;Kcu-|bC>#N&5zVy(cGl@o90^elbWkE zUsNB_tWh7c}scBO`qiIp!qq#^^uf9uT&@55ksyRvh zy!u8>u|}!BQhmAlV)eNCbLwIB9`%5lQG3)o)J}Dmx?OEjuTwXwYt(x6$?6Jqxq7y` zM4eyue%U)^e<}M@*^6b*l|5bdMA>6y51c4fLG`2vPy{Ff6ak6=MSvne5ugZA1SkUk z6a@0NQVy#I!(68<6I1Q{?5<3t#eb zgtR6%uSPs+6w@U_dX^&3ARe76rY8xhE;mmn9#x6ya-_L8K-9^?m%L>{dNvM;cvdMK z-j2fx@lY!q3YixPU-I+<^KKk25YNsR(g$!jSNNiN2H$4mTW(&ZkZN)jc_18?38#g# zrNWoI;`m{q@I`YJCsg8zd?8ifFgNxE-{#<3Zdu7yic-b>isC~>2a5^{_p9DeomrsH zo5($1`JCc@jp3hStN#R{V+*QPT1!RY_;#LS{eHXxlgB$EJ@M%ekFbxzGc<^`Bh0Ee z)8epV%?EKgZ0+q<2dSLll1gG~b0JS`X$a}IlghZ1O0vDfYA4;Dtt%`0SYIVswt?8K z+oc1Oy_Hmphk^!`#OAcSJ3C3YgJ7YFEn*E0@*^qd^?G8qw%hEa)!J!=Zo)zkS;fS= zI;@2C5#+8;+})78w|*V5+FQq$=|o`84}Ksp4e=r!J1`9&8!+~6mxzo27`}ge@N7s{ z%{pM^v^rb@{0?hRr^#Xk$JIRz!s2orr&N7H)f!M|U2N#I)Cw~Qns#~jr88q0cgST- zW5()eZ1yQJvHi=G`rt3%B2O35~b81NwiLR{#l z%dtmipR6i3nUteOaWY=6fx)YJnGiqynZ@y7Y%+IRWdoHbCZe)B6!ziW-$F#Q3hh~s z!`5YT>>yjLI}EWbLI(Y4ex<5>!v^JO#k4vJ`Q$Vb_}UNFN60p#Ho^){{bcNGFS)ZID;)#VHtcjSfvReEni;JLYD6AjNBdYR-hQiUh>2#6NLc0Ano5STc zbrRRK6;w&wPOR!AqKs0`Y75XX3W!$ToH(YKk-6OZZQxKm2~jr0~F))kTZJmR*|u%E--Lp)42LY!6?NmGez z$T&8VI-z%jeo7!b%y88rx}K0X$mo$b#?!_1*GY|G&JuYLVo~HcL74I4I0*3|k`|dh zG6*Y<0|dO3>&o(V>y#J73^HxY?lfC{lkAhy9AlbmNp<3dWu8cHc#t73>uCtNQrI;l zt4cg!=nV&kf+4Z)VhlKyhxK{(dhr{CWqUJ(Ayfxl4#IYIX5JY`>_>oLANy>?@zlR`c-Fdo%WKNxagSMc2>k6epd=p<} zPXI60^LU5&uz2hd@kqRUFXE}oU@RCNUZg5tu|hdsl>{@HKO^`2Y(SHFNlHYT#>^9$ zJ)zO9da-vj)-8ornMOeh)$y1Xzxhl<%vf-b#~|52ERHb40bf?<=tB<{GrQU1`~Q^} zDoTt6JMoWxJ{bfq?#azlT(KNCKUGp7Cpx)J2~wD~v;GMBKZy^XSqoZZ*47^Yoh|X_ zL?=%f2aTGwhPOfQNc_6!CD=Tq0hyClK8WtlS@wqEi-GIUjw}%@fSrWD}D(2fm!?GdeAC~zbZPZ z9R;0Z*6w$Kwn=;qcn3hx(e!Q5cO?EY@B*L#^WsgdAgjc$j7~262IvO!;!^^kki>6@ zPA*svT5Hx`7XZci&~g6XK<}6rYkELirToBh*0(_4G;43uf)-2s<!;+|8WpP03wvq9%d{HEyS?6sgZX6@ur&|4CJU39V<`Kyutp|66jmH5!{te=C9 zAa5VYFYz}-C;1)_>g%E>K);gs8>5q#ehGA?Svv+@$8M7No1&8|&IgT~wZ9qzjZ1vg zp=%3hvw89UM?sHCd{cCC^AXVF=Ed7i1MQUf=IG>x-+-P5#z#SKOMFXoviTm+-R8x0 zU7&7>-x{54G=Nr^7gxLmirbqtI$3u;=sNRag&L%h^0!4NYhgpRwPx*Wd7ylW-wrJA zis3og4IJY2(h;3Bd=+%HS$p{3g7mNPO6Gj}znouL;yF@qxjr zJ3v1ImR|(L-*MWBGQ9zM-K@QECuom=M5=I;p%2bSZSa9~57> zb_#e1Yf56)K6(r2yHfsn(TTsm3Hl4d>#sq-k@)4{{RxDy`<+ukDwGas5^$0@Uk$9*pjZVCLHRvm5t^an=ol<@scmp8F{>|q>mrMMU zq7yIt7<8vu`{lbq_ey+ybmG|vXpdR@g(_^n8iG z2KL_^^NSnNq}B*GPPX%ebFju^8pTvQ+WohHt$Mg6)i= z8!&pV#Q)U)ySPUwhBT!DkJ7{sLWR&O3%vyN(vq!gjtGfZbugJEw4eGSQUxgj6oG#q z1cdRwuued8RP*k?PpN51DFPG$iU37`B0v$K2v7tl0u%v?07ZZz@b7>?E_OK- planetiler.addOsmSource(source.id(), localPath, source.url()); case SHAPEFILE -> planetiler.addShapefileSource(source.id(), localPath, source.url()); + case GEOPACKAGE -> planetiler.addGeoPackageSource(source.id(), localPath, source.url()); default -> throw new IllegalArgumentException("Unhandled source type for " + source.id() + ": " + sourceType); } } diff --git a/planetiler-custommap/src/main/java/com/onthegomap/planetiler/custommap/configschema/DataSourceType.java b/planetiler-custommap/src/main/java/com/onthegomap/planetiler/custommap/configschema/DataSourceType.java index b128f136..39e12c3f 100644 --- a/planetiler-custommap/src/main/java/com/onthegomap/planetiler/custommap/configschema/DataSourceType.java +++ b/planetiler-custommap/src/main/java/com/onthegomap/planetiler/custommap/configschema/DataSourceType.java @@ -6,5 +6,7 @@ public enum DataSourceType { @JsonProperty("osm") OSM, @JsonProperty("shapefile") - SHAPEFILE + SHAPEFILE, + @JsonProperty("geopackage") + GEOPACKAGE } diff --git a/planetiler-custommap/src/test/resources/validSchema/road_motorway.yml b/planetiler-custommap/src/test/resources/validSchema/road_motorway.yml index eb928576..558e65d0 100644 --- a/planetiler-custommap/src/test/resources/validSchema/road_motorway.yml +++ b/planetiler-custommap/src/test/resources/validSchema/road_motorway.yml @@ -9,6 +9,9 @@ sources: osm: type: osm url: geofabrik:rhode-island + gpkg: + type: geopackage + url: https://example.com/geopackage.gpkg tag_mappings: bridge: boolean # input=bridge, output=bridge, type=boolean layer: long