From b00fbc081a27e2c8b8011f88b9a44961dca8eadb Mon Sep 17 00:00:00 2001 From: Nikita Klimenko Date: Mon, 15 Dec 2025 19:26:21 +0200 Subject: [PATCH] Support auto-detection of .shp file inside the provided directory https://github.com/Kotlin/dataframe/issues/1567 --- .../kotlinx/dataframe/geo/io/read.kt | 38 ++++++++++++++++++- .../kotlinx/dataframe/geo/io/IOTest.kt | 17 +++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/io/read.kt b/dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/io/read.kt index d0a0e7f68a..06e712fd3a 100644 --- a/dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/io/read.kt +++ b/dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/io/read.kt @@ -7,6 +7,7 @@ import org.jetbrains.kotlinx.dataframe.DataFrame import org.jetbrains.kotlinx.dataframe.geo.GeoDataFrame import org.jetbrains.kotlinx.dataframe.geo.geotools.toGeoDataFrame import org.jetbrains.kotlinx.dataframe.io.asUrl +import java.io.File import java.net.URL fun GeoDataFrame.Companion.readGeoJson(path: String): GeoDataFrame<*> = readGeoJson(asUrl(path)) @@ -21,9 +22,30 @@ fun DataFrame.Companion.readGeoJson(path: String): GeoDataFrame<*> = GeoDataFram fun DataFrame.Companion.readGeoJson(url: URL): GeoDataFrame<*> = GeoDataFrame.readGeoJson(url) -fun GeoDataFrame.Companion.readShapefile(path: String): GeoDataFrame<*> = readShapefile(asUrl(path)) +/** + * Examples: + * ``` + * GeoDataFrame.readShapefile("simple_points") + * GeoDataFrame.readShapefile("simple_points/simple_points.shp") + * ``` + * + * @param path path to *.shp or *.shp.gz file, or to a directory containing such a file + */ +fun GeoDataFrame.Companion.readShapefile(path: String): GeoDataFrame<*> { + val url = resolveShapefileUrl(path) + return readShapeFileImpl(url) +} fun GeoDataFrame.Companion.readShapefile(url: URL): GeoDataFrame<*> { + val resolvedUrl = if (url.protocol == "file") { + resolveShapefileUrl(url.path) + } else { + url + } + return readShapeFileImpl(resolvedUrl) +} + +private fun readShapeFileImpl(url: URL): GeoDataFrame<*> { val dataStore = ShapefileDataStoreFactory().createDataStore(url) try { return dataStore.featureSource.features.toGeoDataFrame() @@ -32,6 +54,20 @@ fun GeoDataFrame.Companion.readShapefile(url: URL): GeoDataFrame<*> { } } +private fun resolveShapefileUrl(path: String): URL { + val file = File(path) + val shpFile = when { + file.isDirectory -> findShapefileInDirectory(file) + else -> file + } + return shpFile.toURI().toURL() +} + +private fun findShapefileInDirectory(dir: File): File = + File(dir, "${dir.name}.shp").takeIf { it.exists() } + ?: File(dir, "${dir.name}.shp.gz").takeIf { it.exists() } + ?: throw IllegalArgumentException("No shapefile found in directory: ${dir.absolutePath}") + fun DataFrame.Companion.readShapefile(path: String): GeoDataFrame<*> = GeoDataFrame.readShapefile(path) fun DataFrame.Companion.readShapefile(url: URL): GeoDataFrame<*> = GeoDataFrame.readShapefile(url) diff --git a/dataframe-geo/src/test/kotlin/org/jetbrains/kotlinx/dataframe/geo/io/IOTest.kt b/dataframe-geo/src/test/kotlin/org/jetbrains/kotlinx/dataframe/geo/io/IOTest.kt index dc28300f92..d989f3411f 100644 --- a/dataframe-geo/src/test/kotlin/org/jetbrains/kotlinx/dataframe/geo/io/IOTest.kt +++ b/dataframe-geo/src/test/kotlin/org/jetbrains/kotlinx/dataframe/geo/io/IOTest.kt @@ -65,4 +65,21 @@ class IOTest { assertEquals(simplePointsGeoDf, GeoDataFrame.readShapefile(shapefile.toURI().toURL())) tempDir.deleteOnExit() } + + @Test + fun readShapefileDirectory() { + val shapefileURL = classLoader.getResource("./simple_points")!! + val geodf = GeoDataFrame.readShapefile(shapefileURL) + + assertEquals(simplePointsDf, geodf.df) + assert(geodf.crs == null) + } + + @Test + fun readShapefileDirectoryFile() { + val geodf = GeoDataFrame.readShapefile("src/test/resources/simple_points") + + assertEquals(simplePointsDf, geodf.df) + assert(geodf.crs == null) + } }