-
Notifications
You must be signed in to change notification settings - Fork 77
Generate lexicographically ordered IDs for writeGeojson features #1640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
| simplePointsGeoDf.writeGeoJson(tempFile) | ||
|
|
||
| assertEquals(simplePointsGeoDf, GeoDataFrame.readGeoJson(tempFile.toURI().toURL())) | ||
| val loadedGeoDataFrame = GeoDataFrame.readGeoJson(tempFile.toURI().toURL()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to add more IO overloads ;)
| assertEquals(simplePointsGeoDf, GeoDataFrame.readGeoJson(tempFile.toURI().toURL())) | ||
| val loadedGeoDataFrame = GeoDataFrame.readGeoJson(tempFile.toURI().toURL()) | ||
| assertEquals(simplePointsGeoDf.df, loadedGeoDataFrame.df) | ||
| // TODO: Doesn't work because of how equality between CRS is checked by geotools |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proper way to check CRS equality in Geotools is
CRS.equals, or CRS.equalsIgnoreMetadata if you don't want to compare metedata.
But there, shouldn't they be equal?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
|
||
| val featureBuilder = SimpleFeatureBuilder(featureType) | ||
|
|
||
| // if ID is present, SortedMap in DefaultFeatureCollection sorts rows by ID lexicographically |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, ok. Is that a known issue in Geotools? Is there an official workaround?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think they did it intentionally, doesn't look like a bug
Got pure geotools repro here. I see that just reading and writing back the same collection is idempotent, i.e. rows go the same order. But problem arises when we extract data from collection (create dataframe) and then re-create feature collection from it. Please have a look.
Output of the program:
Original order:
feature-0 -> Point 1
feature-1 -> Point 2
Built feature id: fid-1a9d9e5e_19b2837675d_-8000
Built feature id: fid-1a9d9e5e_19b2837675d_-7fff
Written JSON:
{"type":"FeatureCollection","features":[{"type":"Feature","geometry":{"type":"Point","coordinates":[31.5,51.5]},"properties":{"name":"Point 2"},"id":"fid-1a9d9e5e_19b2837675d_-7fff"},{"type":"Feature","geometry":{"type":"Point","coordinates":[30.5,50.5]},"properties":{"name":"Point 1"},"id":"fid-1a9d9e5e_19b2837675d_-8000"}]}
After write+read:
fid-1a9d9e5e_19b2837675d_-7fff -> Point 2
fid-1a9d9e5e_19b2837675d_-8000 -> Point 1
Somewhat interesting to see that in original feature collections feature-0 and feature-1 are IDs - but no ID in the original file
package org.jetbrains.kotlinx.dataframe.geo
import org.geotools.data.simple.SimpleFeatureCollection
import org.geotools.feature.DefaultFeatureCollection
import org.geotools.feature.simple.SimpleFeatureBuilder
import org.geotools.feature.simple.SimpleFeatureTypeBuilder
import org.geotools.geojson.feature.FeatureJSON
import org.locationtech.jts.geom.Geometry
import java.io.File
fun main() {
val geojson = """
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [30.5, 50.5]
},
"properties": {
"name": "Point 1"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [31.5, 51.5]
},
"properties": {
"name": "Point 2"
}
}
]
}
""".trimIndent()
val featureJSON = FeatureJSON()
// Read original
val original = featureJSON.readFeatureCollection(geojson.reader()) as SimpleFeatureCollection
println("Original order:")
printNames(original)
// Extract to intermediate storage (simulating DataFrame)
val data = mutableListOf<Pair<String, Geometry>>()
original.features().use { iter ->
while (iter.hasNext()) {
val f = iter.next()
data.add(f.getAttribute("name") as String to f.defaultGeometry as Geometry)
}
}
// Rebuild FeatureCollection from scratch (like toSimpleFeatureCollection does)
val typeBuilder = SimpleFeatureTypeBuilder()
typeBuilder.name = "geodata"
typeBuilder.add("the_geom", Geometry::class.java)
typeBuilder.add("name", String::class.java)
val featureType = typeBuilder.buildFeatureType()
val rebuilt = DefaultFeatureCollection()
val featureBuilder = SimpleFeatureBuilder(featureType)
for ((name, geometry) in data) {
featureBuilder.add(geometry)
featureBuilder.add(name)
val feature = featureBuilder.buildFeature(null) // ID is auto-generated
println("Built feature id: ${feature.id}")
rebuilt.add(feature)
}
// Write to file
val tempFile = File.createTempFile("test", ".geojson")
tempFile.outputStream().use { featureJSON.writeFeatureCollection(rebuilt, it) }
println("\nWritten JSON:")
println(tempFile.readText())
// Read back
val reloaded = featureJSON.readFeatureCollection(tempFile) as SimpleFeatureCollection
println("\nAfter write+read:")
printNames(reloaded)
tempFile.delete()
}
fun printNames(fc: SimpleFeatureCollection) {
fc.features().use { iter ->
while (iter.hasNext()) {
val f = iter.next()
println(" ${f.id} -> ${f.getAttribute("name")}")
}
}
}

fixes #1565