Skip to content
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

CASL-557 buffer transformation #377

Merged
merged 3 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package naksha.psql.executors.query

import naksha.geo.HereTile
import naksha.geo.SpGeometry
import naksha.model.*
import naksha.model.GeoEncoding.GeoEncoding_C.TWKB
import naksha.model.request.ReadFeatures
import naksha.model.request.query.*
import naksha.psql.PgColumn
Expand Down Expand Up @@ -95,13 +97,16 @@ class WhereClauseBuilder(private val request: ReadFeatures) {
)

is SpIntersects -> {
// TODO: Add transformations!
val twkb = PgUtil.encodeGeometry(
spatial.geometry,
Flags().geoGzipOff().geoEncoding(GeoEncoding.TWKB)
val encodedTwkbPlaceholder = placeholderForArg(
encodedGeo(spatial.geometry, TWKB),
PgType.BYTE_ARRAY
)
val placeholder = placeholderForArg(twkb, PgType.BYTE_ARRAY)
where.append("ST_Intersects(naksha_geometry(${PgColumn.geo}, ${PgColumn.flags}), ST_GeomFromTWKB($placeholder))")
val geometryFromTwkb = "ST_GeomFromTWKB($encodedTwkbPlaceholder)"
val geometryToCompare = when (val transformation = spatial.transformation) {
null -> geometryFromTwkb
else -> resolveTransformation(transformation, geometryFromTwkb)
}
Amaneusz marked this conversation as resolved.
Show resolved Hide resolved
where.append("ST_Intersects(naksha_geometry(${PgColumn.geo}, ${PgColumn.flags}), $geometryToCompare)")
}

is SpRefInHereTile -> {
Expand All @@ -115,6 +120,71 @@ class WhereClauseBuilder(private val request: ReadFeatures) {
}
}

private fun encodedGeo(geometry: SpGeometry, geoEncoding: Int): ByteArray? {
return PgUtil.encodeGeometry(
geometry,
Flags().geoGzipOff().geoEncoding(geoEncoding)
)
}

private fun resolveTransformation(transformation: SpTransformation, geometryFromTwkb: String): String {
return when(transformation){
is SpBuffer -> resolveBuffer(transformation, geometryFromTwkb)
else -> throw NakshaException(
NakshaError.UNSUPPORTED_OPERATION,
"This transformation is not yet supported: ${transformation::class.simpleName}"
)
}
}

private fun resolveBuffer(buffer: SpBuffer, geometryFromTwkb: String): String {
val geo = if (buffer.geography) {
"$geometryFromTwkb::geography"
} else {
geometryFromTwkb
}
val distancePlaceholder = placeholderForArg(buffer.distance, PgType.DOUBLE)
val bufferStyleParams = bufferStyleParams(buffer)
return if (bufferStyleParams != null) {
"ST_Buffer($geo, $distancePlaceholder, $bufferStyleParams)"
} else {
"ST_Buffer($geo, $distancePlaceholder)"
}
}

private fun bufferStyleParams(buffer: SpBuffer): String? {
val bufferStyleParams = StringBuilder()
if(buffer.quadSegments != null){
val quadSegPlaceholder = placeholderForArg(buffer.quadSegments, PgType.INT)
bufferStyleParams.append("quad_segs=$quadSegPlaceholder")
}
if(buffer.joinStyle != null){
val joinStylePlaceholder = placeholderForArg(buffer.joinStyle!!.value, PgType.STRING)
if(bufferStyleParams.isNotEmpty()) bufferStyleParams.append(" ")
bufferStyleParams.append("join=$joinStylePlaceholder")
}
if(buffer.joinLimit != null){
val joinLimitPlaceholder = placeholderForArg(buffer.joinLimit, PgType.DOUBLE)
if(bufferStyleParams.isNotEmpty()) bufferStyleParams.append(" ")
bufferStyleParams.append("mitre_limit=$joinLimitPlaceholder")
}
if(buffer.endCap != null){
val endCapPlaceholder = placeholderForArg(buffer.endCap!!.value, PgType.STRING)
if(bufferStyleParams.isNotEmpty()) bufferStyleParams.append(" ")
bufferStyleParams.append("endcap=$endCapPlaceholder")
}
if(buffer.side != null){
val sidePlaceholder = placeholderForArg(buffer.side!!.value, PgType.STRING)
if(bufferStyleParams.isNotEmpty()) bufferStyleParams.append(" ")
bufferStyleParams.append("side=$sidePlaceholder")
}
return if(bufferStyleParams.isNotEmpty()){
bufferStyleParams.toString()
} else {
null
}
}

private fun whereRefTiles() {
val hereTiles = request.query.refTiles
.filterNotNull()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import naksha.model.request.ReadFeatures
import naksha.model.request.SuccessResponse
import naksha.model.request.Write
import naksha.model.request.WriteRequest
import naksha.model.request.query.ISpatialQuery
import naksha.model.request.query.SpIntersects
import naksha.model.request.query.SpOr
import naksha.model.request.query.SpRefInHereTile
import naksha.model.request.query.*
import naksha.psql.assertions.AnyObjectFluidAssertions.Companion.assertThatAnyObject
import naksha.psql.base.PgTestBase
import naksha.psql.util.ProxyFeatureGenerator.generateRandomFeature
Expand Down Expand Up @@ -150,6 +147,71 @@ class ReadFeaturesByGeometryTest : PgTestBase(NakshaCollection("read_by_geometry
assertTrue(featureIds.containsAll(listOf(somePlaceInPrague.id, valencia.id)))
}

@Test
fun shouldReadByCorridor(){
// Given
val feature = generateRandomFeature().apply {
geometry = SpLineString().withCoordinates(LineStringCoord(
PointCoord(longitude = 45.0, latitude = 45.0),
PointCoord(longitude = 45.0, latitude = 46.0),
))
}

// And
val point = SpPoint(PointCoord(45.000001, 45.0))
val buffer = SpBuffer(distance = 100000.0)
val readByCorridor = SpIntersects(point, buffer)

// When
insertFeature(feature)

// And
val features = executeSpatialQuery(readByCorridor).features

// Then:
assertEquals(1, features.size)
assertEquals(feature.id, features[0]!!.id)
}

@Test
fun shouldDistinguishDifferentBufferModes(){
/**
* Note: samples & values based on https://postgis.net/workshops/postgis-intro/geography.html
* Actual distance between LAX and NRT:
* - 8833954.76996256 meters (Cartesian) - used by 'geography'
* - 258.146005837336 degrees (SRS) - used by 'geometry'
*/

// Given
val laxAirportCoord = PointCoord(longitude = -118.4079, latitude = 33.9434)
val nrtAirportCoord = PointCoord(longitude = 139.733, latitude = 35.567)
val laxAirport = generateRandomFeature().apply {
geometry = SpPoint(laxAirportCoord)
}

// When:
insertFeature(laxAirport)

// And:
val featuresWithinThreeHundredMetersFromNrt = executeSpatialQuery(SpIntersects(
SpPoint(nrtAirportCoord),
SpBuffer(distance = 300.0, geography = true)
)).features

// Then:
assertTrue(featuresWithinThreeHundredMetersFromNrt.isEmpty())

// When:
val featuresWithinThreeHundredDegreesFromNrt = executeSpatialQuery(SpIntersects(
SpPoint(nrtAirportCoord),
SpBuffer(distance = 300.0, geography = false)
)).features

// Then:
assertEquals(1, featuresWithinThreeHundredDegreesFromNrt.size)
assertEquals(laxAirport.id, featuresWithinThreeHundredDegreesFromNrt[0]!!.id)
}

private fun executeSpatialQuery(spatialQuery: ISpatialQuery): SuccessResponse {
return executeRead(ReadFeatures().apply {
collectionIds += collection!!.id
Expand Down
Loading