Skip to content

Commit

Permalink
CASL-557 buffer transformation (#377)
Browse files Browse the repository at this point in the history
* CASL-557 buffer transformation

Signed-off-by: Jakub Amanowicz <[email protected]>

* CASL-557 geography & buffer style params

Signed-off-by: Jakub Amanowicz <[email protected]>

* CASL-557 review fixes & tests

Signed-off-by: Jakub Amanowicz <[email protected]>

---------

Signed-off-by: Jakub Amanowicz <[email protected]>
  • Loading branch information
Amaneusz authored Nov 8, 2024
1 parent 81842ec commit 49ea4cc
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 10 deletions.
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)
}
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

0 comments on commit 49ea4cc

Please sign in to comment.