package com.onthegomap.planetiler.reader; import static com.onthegomap.planetiler.TestUtils.newPoint; 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.config.Bounds; import com.onthegomap.planetiler.geo.GeoUtils; import com.onthegomap.planetiler.stats.Stats; import com.onthegomap.planetiler.util.FileUtils; import com.onthegomap.planetiler.worker.WorkerPipeline; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import org.geotools.api.data.SimpleFeatureStore; import org.geotools.api.referencing.FactoryException; import org.geotools.api.referencing.operation.TransformException; import org.geotools.data.DefaultTransaction; import org.geotools.data.shapefile.ShapefileDataStore; import org.geotools.data.shapefile.ShapefileDataStoreFactory; import org.geotools.feature.DefaultFeatureCollection; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.referencing.CRS; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Point; class ShapefileReaderTest { @TempDir private Path tempDir; private static final Envelope env = newPoint(-77.12911152370515, 38.79930767201779).getEnvelopeInternal(); private static final int numInEnv = 18; static { env.expandBy(0.1); } @Test @Timeout(30) @DisabledOnOs(OS.WINDOWS) // the zip file doesn't fully close, which causes trouble running test on windows void testReadShapefileExtracted() throws IOException { var extracted = TestUtils.extractPathToResource(tempDir, "shapefile.zip"); try (var fs = FileSystems.newFileSystem(extracted)) { var path = fs.getPath("shapefile", "stations.shp"); testReadShapefile(path); } } @Test @Timeout(30) void testReadShapefileUnzipped() throws IOException { var dest = tempDir.resolve("shapefile.zip"); FileUtils.unzipResource("/shapefile.zip", dest); testReadShapefile(dest.resolve("shapefile").resolve("stations.shp")); } @Test @Timeout(30) void testReadShapefileWithBoundingBox() { var dest = tempDir.resolve("shapefile.zip"); FileUtils.unzipResource("/shapefile.zip", dest); try ( var reader = new ShapefileReader(null, "test", dest.resolve("shapefile").resolve("stations.shp"), new Bounds(env)) ) { for (int i = 1; i <= 2; i++) { assertEquals(numInEnv, reader.getFeatureCount()); List points = new CopyOnWriteArrayList<>(); WorkerPipeline.start("test", Stats.inMemory()) .fromGenerator("source", reader::readFeatures) .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()); }).await(); assertEquals(numInEnv, points.size()); var gc = GeoUtils.JTS_FACTORY.createGeometryCollection(points.toArray(new Geometry[0])); var centroid = gc.getCentroid(); assertEquals(-77.0934256, centroid.getX(), 1e-5, "iter " + i); assertEquals(38.8509022, centroid.getY(), 1e-5, "iter " + i); } } } @Test void testReadShapefileLeniently(@TempDir Path dir) throws IOException, TransformException, FactoryException { var shpPath = dir.resolve("test.shp"); var dataStoreFactory = new ShapefileDataStoreFactory(); var newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(Map.of("url", shpPath.toUri().toURL())); var builder = new SimpleFeatureTypeBuilder(); builder.setName("the_geom"); builder.setCRS(CRS.parseWKT( """ PROJCS["SWEREF99_TM",GEOGCS["GCS_SWEREF99",DATUM["D_SWEREF99",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",15.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] """)); builder.add("the_geom", Point.class); builder.add("value", Integer.class); builder.setDefaultGeometry("the_geom"); var type = builder.buildFeatureType(); newDataStore.createSchema(type); try (var transaction = new DefaultTransaction("create")) { var typeName = newDataStore.getTypeNames()[0]; var featureSource = newDataStore.getFeatureSource(typeName); var featureStore = (SimpleFeatureStore) featureSource; featureStore.setTransaction(transaction); var collection = new DefaultFeatureCollection(); var featureBuilder = new SimpleFeatureBuilder(type); featureBuilder.add(newPoint(1, 2)); featureBuilder.add(3); var feature = featureBuilder.buildFeature(null); collection.add(feature); featureStore.addFeatures(collection); transaction.commit(); } try (var reader = new ShapefileReader(null, "test", shpPath)) { assertEquals(1, reader.getFeatureCount()); List features = new CopyOnWriteArrayList<>(); reader.readFeatures(features::add); assertEquals(10.5113, features.getFirst().latLonGeometry().getCentroid().getX(), 1e-4); assertEquals(0, features.getFirst().latLonGeometry().getCentroid().getY(), 1e-4); assertEquals(3, features.getFirst().getTag("value")); } } private static void testReadShapefile(Path path) { try (var reader = new ShapefileReader(null, "test", path)) { for (int i = 1; i <= 2; i++) { assertEquals(86, reader.getFeatureCount()); List points = new CopyOnWriteArrayList<>(); List names = new CopyOnWriteArrayList<>(); WorkerPipeline.start("test", Stats.inMemory()) .fromGenerator("source", reader::readFeatures) .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(numInEnv, points.stream().filter(point -> env.contains(point.getCoordinate())).count()); 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(), 1e-5, "iter " + i); assertEquals(38.9119684, centroid.getY(), 1e-5, "iter " + i); } } } }