Skip to content

Commit b724f9d

Browse files
PR changes & docs
1 parent 4bd8105 commit b724f9d

File tree

20 files changed

+282
-94
lines changed

20 files changed

+282
-94
lines changed

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/jupyter/Integration.kt

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ import org.jetbrains.kotlinx.jupyter.api.libraries.JupyterIntegration
5757
import org.jetbrains.kotlinx.jupyter.api.libraries.resources
5858
import kotlin.reflect.KClass
5959
import kotlin.reflect.KProperty
60-
import kotlin.reflect.KType
6160
import kotlin.reflect.full.isSubtypeOf
6261

6362
/** Users will get an error if their Kotlin Jupyter kernel is older than this version. */
@@ -70,29 +69,6 @@ internal class Integration(private val notebook: Notebook, private val options:
7069

7170
val version = options["v"]
7271

73-
private fun KotlinKernelHost.execute(codeWithConverter: CodeWithConverter, argument: String): VariableName? {
74-
val code = codeWithConverter.with(argument)
75-
return if (code.isNotBlank()) {
76-
val result = execute(code)
77-
if (codeWithConverter.hasConverter) {
78-
result.name
79-
} else {
80-
null
81-
}
82-
} else {
83-
null
84-
}
85-
}
86-
87-
private fun KotlinKernelHost.execute(
88-
codeWithConverter: CodeWithConverter,
89-
property: KProperty<*>,
90-
type: KType,
91-
): VariableName? {
92-
val variableName = "(${property.name}${if (property.returnType.isMarkedNullable) "!!" else ""} as $type)"
93-
return execute(codeWithConverter, variableName)
94-
}
95-
9672
private fun KotlinKernelHost.updateImportDataSchemaVariable(
9773
importDataSchema: ImportDataSchema,
9874
property: KProperty<*>,
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.jetbrains.kotlinx.dataframe.jupyter
2+
3+
import org.jetbrains.kotlinx.dataframe.codeGen.CodeWithConverter
4+
import org.jetbrains.kotlinx.jupyter.api.KotlinKernelHost
5+
import org.jetbrains.kotlinx.jupyter.api.VariableName
6+
import kotlin.reflect.KProperty
7+
import kotlin.reflect.KType
8+
9+
internal fun KotlinKernelHost.execute(codeWithConverter: CodeWithConverter, argument: String): VariableName? {
10+
val code = codeWithConverter.with(argument)
11+
return if (code.isNotBlank()) {
12+
val result = execute(code)
13+
if (codeWithConverter.hasConverter) {
14+
result.name
15+
} else {
16+
null
17+
}
18+
} else {
19+
null
20+
}
21+
}
22+
23+
internal fun KotlinKernelHost.execute(
24+
codeWithConverter: CodeWithConverter,
25+
property: KProperty<*>,
26+
type: KType,
27+
): VariableName? {
28+
val variableName = "(${property.name}${if (property.returnType.isMarkedNullable) "!!" else ""} as $type)"
29+
return execute(codeWithConverter, variableName)
30+
}

dataframe-geo/build.gradle.kts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ plugins {
66
alias(kotlin.jvm)
77
alias(publisher)
88
alias(jupyter.api)
9+
//alias(ktlint)
910
}
1011
}
1112

1213
group = "org.jetbrains.kotlinx"
1314

1415
repositories {
15-
// geo repositories should come before Maven Central
16-
maven("https://maven.geotoolkit.org")
16+
// geo repository should come before Maven Central
1717
maven("https://repo.osgeo.org/repository/release")
1818
mavenCentral()
1919
}
@@ -22,7 +22,6 @@ repositories {
2222
// jai core dependency should be excluded from geotools dependencies and added separately
2323
fun ExternalModuleDependency.excludeJaiCore() = exclude("javax.media", "jai_core")
2424

25-
2625
dependencies {
2726
api(project(":core"))
2827

@@ -42,6 +41,7 @@ dependencies {
4241
implementation(libs.ktor.client.content.negotiation)
4342
implementation(libs.ktor.serialization.kotlinx.json)
4443

44+
testImplementation(kotlin("test"))
4545
}
4646

4747
tasks.withType<KotlinCompile>().configureEach {
@@ -64,7 +64,6 @@ tasks.processJupyterApiResources {
6464
libraryProducers = listOf("org.jetbrains.kotlinx.dataframe.jupyter.IntegrationGeo")
6565
}
6666

67-
6867
tasks.test {
6968
useJUnitPlatform()
7069
}

dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/GeoDataFrame.kt

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,50 @@ import org.jetbrains.kotlinx.dataframe.DataFrame
77
import org.jetbrains.kotlinx.dataframe.api.update
88
import org.jetbrains.kotlinx.dataframe.api.with
99

10-
class GeoDataFrame<T : GeoFrame>(val df: DataFrame<T>, val crs: CoordinateReferenceSystem?) {
10+
/**
11+
* A data structure representing a geographical DataFrame, combining spatial data with
12+
* an optional Coordinate Reference System (CRS).
13+
*
14+
* @param T The type parameter extending `WithGeometry`, indicating the presence of a geometry column.
15+
* @property df The underlying `DataFrame` containing geometries.
16+
* @property crs The coordinate reference system associated with the data, if any.
17+
*/
18+
class GeoDataFrame<T : WithGeometry>(val df: DataFrame<T>, val crs: CoordinateReferenceSystem?) {
19+
/**
20+
* Updates the `GeoDataFrame` using a specified transformation block on the underlying DataFrame.
21+
*
22+
* @param updateBlock The block defining the transformations to be applied to the DataFrame.
23+
* @return A new `GeoDataFrame` instance with updated data and the same CRS.
24+
*/
1125
fun update(updateBlock: DataFrame<T>.() -> DataFrame<T>): GeoDataFrame<T> {
1226
return GeoDataFrame(df.updateBlock(), crs)
1327
}
1428

15-
fun applyCRS(targetCRS: CoordinateReferenceSystem? = null): GeoDataFrame<T> {
16-
if (targetCRS == this.crs) return this
29+
/**
30+
* Transforms the geometries to a specified Coordinate Reference System (CRS).
31+
*
32+
* This function reprojects the geometry data from the current CRS to a target CRS.
33+
* If no target CRS is specified and the `GeoDataFrame` has no CRS, WGS 84 is used by default.
34+
*
35+
* @param targetCrs The target CRS for transformation.
36+
* @return A new `GeoDataFrame` with reprojected geometries and the specified CRS.
37+
*/
38+
fun applyCrs(targetCrs: CoordinateReferenceSystem): GeoDataFrame<T> {
39+
if (targetCrs == this.crs) return this
1740
// Use WGS 84 by default TODO
1841
val sourceCRS: CoordinateReferenceSystem = this.crs ?: DEFAULT_CRS
19-
val transform = CRS.findMathTransform(sourceCRS, targetCRS, true)
42+
val transform = CRS.findMathTransform(sourceCRS, targetCrs, true)
2043
return GeoDataFrame(
2144
df.update { geometry }.with { JTS.transform(it, transform) },
22-
targetCRS
45+
targetCrs
2346
)
2447
}
2548

49+
override fun equals(other: Any?): Boolean {
50+
if (other !is GeoDataFrame<*>) return false
51+
return df == other.df && crs == other.crs
52+
}
53+
2654
companion object {
2755
val DEFAULT_CRS = CRS.decode("EPSG:4326", true)
2856
}

dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/GeoFrame.kt renamed to dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/WithGeometry.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,28 @@ import org.locationtech.jts.geom.MultiPolygon
88
import org.locationtech.jts.geom.Polygon
99

1010
@DataSchema
11-
interface GeoFrame {
11+
interface WithGeometry {
1212
val geometry: Geometry
1313
}
1414

1515
@DataSchema
16-
interface PolygonGeoFrame : GeoFrame {
16+
interface WithPolygon : WithGeometry {
1717
override val geometry: Polygon
1818
}
1919

2020
@DataSchema
21-
interface MultiPolygonGeoFrame : GeoFrame {
21+
interface WithMultiPolygon : WithGeometry {
2222
override val geometry: MultiPolygon
2323
}
2424

2525
@get:JvmName("geometry")
26-
val <T : GeoFrame> ColumnsContainer<T>.geometry: DataColumn<Geometry>
26+
val <T : WithGeometry> ColumnsContainer<T>.geometry: DataColumn<Geometry>
2727
get() = get("geometry") as DataColumn<Geometry>
2828

2929
@get:JvmName("geometryPolygon")
30-
val <T : PolygonGeoFrame> ColumnsContainer<T>.geometry: DataColumn<Polygon>
30+
val <T : WithPolygon> ColumnsContainer<T>.geometry: DataColumn<Polygon>
3131
get() = get("geometry") as DataColumn<Polygon>
3232

3333
@get:JvmName("geometryMultiPolygon")
34-
val <T : MultiPolygonGeoFrame> ColumnsContainer<T>.geometry: DataColumn<MultiPolygon>
34+
val <T : WithMultiPolygon> ColumnsContainer<T>.geometry: DataColumn<MultiPolygon>
3535
get() = get("geometry") as DataColumn<MultiPolygon>

dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/bounds.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@ import org.geotools.geometry.jts.ReferencedEnvelope
44
import org.jetbrains.kotlinx.dataframe.api.asIterable
55
import org.jetbrains.kotlinx.dataframe.geo.jts.computeBounds
66

7+
/**
8+
* Computes the bounding envelope for all geometries in a `GeoDataFrame`,
9+
* considering the specified coordinate reference system (CRS).
10+
*
11+
* @receiver The `GeoDataFrame` containing the geometries for which to compute bounds.
12+
* @return The bounding envelope that includes all geometries,
13+
* associated with the CRS of the `GeoDataFrame`.
14+
*/
715
fun GeoDataFrame<*>.bounds(): ReferencedEnvelope {
816
return ReferencedEnvelope(df.geometry.asIterable().computeBounds(), crs)
917
}

dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/geotools/ToGeoDataFrame.kt renamed to dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/geotools/toGeoDataFrame.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import org.jetbrains.kotlinx.dataframe.DataFrame
1010
import org.jetbrains.kotlinx.dataframe.api.Infer
1111
import org.jetbrains.kotlinx.dataframe.api.toDataFrame
1212
import org.jetbrains.kotlinx.dataframe.geo.GeoDataFrame
13-
import org.jetbrains.kotlinx.dataframe.geo.GeoFrame
13+
import org.jetbrains.kotlinx.dataframe.geo.WithGeometry
1414
import org.locationtech.jts.geom.Geometry
1515

1616
fun SimpleFeatureCollection.toGeoDataFrame(): GeoDataFrame<*> {
@@ -24,7 +24,7 @@ fun SimpleFeatureCollection.toGeoDataFrame(): GeoDataFrame<*> {
2424
val geometryAttribute = attributeDescriptors?.find { it is GeometryDescriptor }
2525
?: throw IllegalArgumentException("No geometry attribute")
2626

27-
// In GeoJSON the crs attribute is optional
27+
// In GeoJSON, the crs attribute is optional
2828
val crs: CoordinateReferenceSystem? = (geometryAttribute as GeometryDescriptor).coordinateReferenceSystem
2929

3030
val data = dataAttributes.associate { it.localName to ArrayList<Any?>() }
@@ -52,5 +52,5 @@ fun SimpleFeatureCollection.toGeoDataFrame(): GeoDataFrame<*> {
5252

5353
val geometryColumn = DataColumn.create("geometry", geometries, Infer.Type)
5454

55-
return GeoDataFrame((data.toDataFrame() + geometryColumn) as DataFrame<GeoFrame>, crs)
55+
return GeoDataFrame((data.toDataFrame() + geometryColumn) as DataFrame<WithGeometry>, crs)
5656
}

dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/io/read.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ import org.jetbrains.kotlinx.dataframe.io.asURL
1010
import java.net.URL
1111

1212

13-
fun GeoDataFrame.Companion.readGeoJSON(path: String): GeoDataFrame<*> {
14-
return readGeoJSON(asURL(path))
13+
fun GeoDataFrame.Companion.readGeoJson(path: String): GeoDataFrame<*> {
14+
return readGeoJson(asURL(path))
1515
}
1616

17-
fun GeoDataFrame.Companion.readGeoJSON(url: URL): GeoDataFrame<*> {
17+
fun GeoDataFrame.Companion.readGeoJson(url: URL): GeoDataFrame<*> {
1818
return (FeatureJSON().readFeatureCollection(url.openStream()) as SimpleFeatureCollection).toGeoDataFrame()
1919
}
2020

21-
fun DataFrame.Companion.readGeoJSON(path: String): GeoDataFrame<*> {
22-
return GeoDataFrame.readGeoJSON(path)
21+
fun DataFrame.Companion.readGeoJson(path: String): GeoDataFrame<*> {
22+
return GeoDataFrame.readGeoJson(path)
2323
}
2424

25-
fun DataFrame.Companion.readGeoJSON(url: URL): GeoDataFrame<*> {
26-
return GeoDataFrame.readGeoJSON(url)
25+
fun DataFrame.Companion.readGeoJson(url: URL): GeoDataFrame<*> {
26+
return GeoDataFrame.readGeoJson(url)
2727
}
2828

2929
fun GeoDataFrame.Companion.readShapefile(path: String): GeoDataFrame<*> {

dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/io/write.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.jetbrains.kotlinx.dataframe.geo.io
33
import org.geotools.api.data.FileDataStoreFinder
44
import org.geotools.api.data.SimpleFeatureStore
55
import org.geotools.api.data.Transaction
6+
import org.geotools.feature.simple.SimpleFeatureTypeBuilder
67
import org.geotools.geojson.feature.FeatureJSON
78
import org.jetbrains.kotlinx.dataframe.geo.GeoDataFrame
89
import org.jetbrains.kotlinx.dataframe.geo.geotools.toSimpleFeatureCollection
@@ -12,6 +13,8 @@ import java.io.File
1213
fun GeoDataFrame<*>.writeGeoJson(path: String): Unit = writeGeoJson(File(path))
1314

1415
fun GeoDataFrame<*>.writeGeoJson(file: File) {
16+
17+
// TODO: adds ids that breaks order of reading
1518
val featureJSON = FeatureJSON()
1619
file.outputStream().use { outputStream ->
1720
featureJSON.writeFeatureCollection(toSimpleFeatureCollection(), outputStream)
@@ -39,8 +42,9 @@ fun GeoDataFrame<*>.writeShapefile(directory: File) {
3942
val featureCollection = toSimpleFeatureCollection(fileName, true)
4043

4144
val schema = featureCollection.schema
45+
val schemaWithCrs = SimpleFeatureTypeBuilder.retype(schema, crs ?: GeoDataFrame.DEFAULT_CRS)
4246

43-
dataStore.createSchema(schema)
47+
dataStore.createSchema(schemaWithCrs)
4448

4549
val featureSource = dataStore.getFeatureSource(fileName) as SimpleFeatureStore
4650
val transaction = Transaction.AUTO_COMMIT

dataframe-geo/src/main/kotlin/org/jetbrains/kotlinx/dataframe/geo/jts/bounds.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ package org.jetbrains.kotlinx.dataframe.geo.jts
33
import org.locationtech.jts.geom.Envelope
44
import org.locationtech.jts.geom.Geometry
55

6+
/**
7+
* Computes the bounding envelope for a collection of geometries.
8+
*
9+
*
10+
* @receiver The collection of geometries for which to compute the bounds.
11+
* @return The minimal envelope that encompasses all geometries in the collection.
12+
*/
613
fun Iterable<Geometry>.computeBounds(): Envelope {
714
val bounds = Envelope()
815
forEach { geometry -> bounds.expandToInclude(geometry.envelopeInternal) }

0 commit comments

Comments
 (0)