Skip to content

Commit

Permalink
(dsl): Support geoPolygon query (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
LeonaNedeljkovic authored Dec 21, 2023
1 parent 92bcadd commit f1a33dd
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 0 deletions.
36 changes: 36 additions & 0 deletions docs/overview/queries/elastic_query_geo_polygon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
id: elastic_query_geo_polygon
title: "Geo-polygon Query"
---

A query returning hits that only fall within a polygon of points.

In order to use the `GeoPolygon` query import the following:
```scala
import zio.elasticsearch.query.GeoPolygonQuery
import zio.elasticsearch.ElasticQuery._
```

You can create a `GeoPolygon` query using the `geoPolygon` method with list of coordinates in the following manner:
```scala
val query: GeoPolygonQuery = geoPolygon(field = "location", List("0, 0", "0, 90", "90, 90", "90, 0"))
```

You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema) `GeoPolygon` query using the `geoPolygon` method with list of coordinates in the following manner:
```scala
val query: GeoPolygonQuery = geoPolygon(field = Document.location, List("0, 0", "0, 90", "90, 90", "90, 0"))
```

If you want to change the `_name`, you can use `name` method:
```scala
val queryWithName: GeoPolygonQuery = geoPolygon(field = "location", coordinates = List("0, 0", "0, 90", "90, 90", "90, 0")).name("name")
```

If you want to change the `validation_method`, you can use `validationMethod` method:
```scala
import zio.elasticsearch.query.ValidationMethod

val queryWithValidationMethod: GeoPolygonQuery = geoPolygon(field = "location", coordinates = List("0, 0", "0, 90", "90, 90", "90, 0")).validationMethod(value = ValidationMethod.IgnoreMalformed)
```

You can find more information about `GeoPolygon` query [here](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-polygon-query.html).
Original file line number Diff line number Diff line change
Expand Up @@ -2680,6 +2680,42 @@ object HttpExecutorSpec extends IntegrationSpec {
}
} @@ after(Executor.execute(ElasticRequest.deleteIndex(geoDistanceIndex)).orDie)
),
suite("geo-polygon query")(
test("using geo-polygon query") {
checkOnce(genTestDocument) { document =>
val indexDefinition =
"""
|{
| "mappings": {
| "properties": {
| "geoPointField": {
| "type": "geo_point"
| }
| }
| }
|}
|""".stripMargin

for {
_ <- Executor.execute(ElasticRequest.createIndex(geoPolygonIndex, indexDefinition))
_ <- Executor.execute(ElasticRequest.deleteByQuery(geoPolygonIndex, matchAll))
_ <- Executor.execute(
ElasticRequest.create[TestDocument](geoPolygonIndex, document).refreshTrue
)

r1 <- Executor
.execute(
ElasticRequest.search(
geoPolygonIndex,
ElasticQuery
.geoPolygon("geoPointField", Chunk("0, 0", "0, 90", "90, 90", "90, 0"))
)
)
.documentAs[TestDocument]
} yield assert(r1)(equalTo(Chunk(document)))
}
} @@ after(Executor.execute(ElasticRequest.deleteIndex(geoPolygonIndex)).orDie)
),
suite("search for documents using FunctionScore query")(
test("using randomScore function") {
checkOnce(genTestDocument, genTestDocument) { (firstDocument, secondDocument) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ trait IntegrationSpec extends ZIOSpecDefault {

val IndexPatternAll: IndexPattern = IndexPattern("_all")

val geoPolygonIndex: IndexName = IndexName("geo-polygon-index")

val prepareElasticsearchIndexForTests: TestAspect[Nothing, Any, Throwable, Any] = beforeAll((for {
_ <- Executor.execute(ElasticRequest.createIndex(index))
_ <- Executor.execute(ElasticRequest.deleteByQuery(index, matchAll).refreshTrue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,46 @@ object ElasticQuery {
validationMethod = None
)

/**
* Constructs a type-safe instance of [[zio.elasticsearch.query.GeoPolygonQuery]] using the specified parameters.
*
* @param field
* the type-safe field for which query is specified for
* @param coordinates
* list of longitudes and latitudes of the desired points written as string (e.g. ["40, 31", "25, 31"]) or geo hash
* (e.g. ["drm3btev3e86", "drm3btev3e87"])
* @tparam S
* document for which field query is executed
* @return
* an instance of [[zio.elasticsearch.query.GeoPolygonQuery]] that represents `geo_polygon` query to be performed.
*/
final def geoPolygon[S](field: Field[S, _], coordinates: Chunk[String]): GeoPolygonQuery[S] =
GeoPolygon(
field = field.toString,
points = coordinates,
queryName = None,
validationMethod = None
)

/**
* Constructs an instance of [[zio.elasticsearch.query.GeoPolygonQuery]] using the specified parameters.
*
* @param field
* the field for which query is specified for
* @param coordinates
* list of longitudes and latitudes of the desired points written as string (e.g. ["40, 31", "25, 31"]) or geo hash
* (e.g. ["drm3btev3e86", "drm3btev3e87"])
* @return
* an instance of [[zio.elasticsearch.query.GeoPolygonQuery]] that represents `geo_polygon` query to be performed.
*/
final def geoPolygon(field: String, coordinates: Chunk[String]): GeoPolygonQuery[Any] =
GeoPolygon(
field = field,
points = coordinates,
queryName = None,
validationMethod = None
)

/**
* Constructs an instance of [[zio.elasticsearch.query.HasChildQuery]] using the specified parameters.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,61 @@ private[elasticsearch] final case class GeoDistance[S](

}

sealed trait GeoPolygonQuery[S] extends ElasticQuery[S] {

/**
* Sets the `queryName` parameter for the [[zio.elasticsearch.query.GeoPolygonQuery]]. Represents the optional name
* field to identify the query.
*
* @param value
* the text value that represents the name field
* @return
* an instance of [[zio.elasticsearch.query.GeoPolygonQuery]] enriched with the `queryName` parameter.
*/
def name(value: String): GeoPolygonQuery[S]

/**
* Sets the `validationMethod` parameter for the [[zio.elasticsearch.query.GeoPolygonQuery]]. Defines handling of
* incorrect coordinates.
*
* @param value
* defines how to handle invalid latitude nad longitude:
* - [[zio.elasticsearch.query.ValidationMethod.Strict]]: Default method
* - [[zio.elasticsearch.query.ValidationMethod.IgnoreMalformed]]: Accepts geo points with invalid latitude or
* longitude
* - [[zio.elasticsearch.query.ValidationMethod.Coerce]]: Additionally try and infer correct coordinates
* @return
* an instance of [[zio.elasticsearch.query.GeoPolygonQuery]] enriched with the `validationMethod` parameter.
*/
def validationMethod(value: ValidationMethod): GeoPolygonQuery[S]
}

private[elasticsearch] final case class GeoPolygon[S](
field: String,
points: Chunk[String],
queryName: Option[String],
validationMethod: Option[ValidationMethod]
) extends GeoPolygonQuery[S] { self =>

def name(value: String): GeoPolygonQuery[S] =
self.copy(queryName = Some(value))

def validationMethod(value: ValidationMethod): GeoPolygonQuery[S] =
self.copy(validationMethod = Some(value))

private[elasticsearch] def toJson(fieldPath: Option[String]): Json =
Obj(
"geo_polygon" -> Obj(
Chunk(
Some(field -> Obj("points" -> Arr(points.map(Json.Str(_)): _*))),
queryName.map("_name" -> _.toJson),
validationMethod.map("validation_method" -> _.toString.toJson)
).flatten: _*
)
)

}

sealed trait HasChildQuery[S]
extends ElasticQuery[S]
with HasIgnoreUnmapped[HasChildQuery[S]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,68 @@ object ElasticQuerySpec extends ZIOSpecDefault {
)
)
},
test("geoPolygon") {
val query =
geoPolygon("testField", Chunk("40, -70", "30, -80", "20, -90"))
val queryTs =
geoPolygon(TestDocument.stringField, Chunk("drm3btev3e86", "drm3btev3e87"))
val queryWithName =
geoPolygon(TestDocument.stringField, Chunk("40, -70", "30, -80", "20, -90")).name("name")
val queryWithValidationMethod =
geoPolygon(TestDocument.stringField, Chunk("40, -70", "30, -80", "20, -90")).validationMethod(
IgnoreMalformed
)
val queryWithAllParams = geoPolygon(TestDocument.stringField, Chunk("40, -70", "30, -80", "20, -90"))
.validationMethod(IgnoreMalformed)
.name("name")

assert(query)(
equalTo(
GeoPolygon[Any](
field = "testField",
points = Chunk("40, -70", "30, -80", "20, -90"),
queryName = None,
validationMethod = None
)
)
) && assert(queryTs)(
equalTo(
GeoPolygon[TestDocument](
field = "stringField",
points = Chunk("drm3btev3e86", "drm3btev3e87"),
queryName = None,
validationMethod = None
)
)
) && assert(queryWithName)(
equalTo(
GeoPolygon[TestDocument](
field = "stringField",
points = Chunk("40, -70", "30, -80", "20, -90"),
queryName = Some("name"),
validationMethod = None
)
)
) && assert(queryWithValidationMethod)(
equalTo(
GeoPolygon[TestDocument](
field = "stringField",
points = Chunk("40, -70", "30, -80", "20, -90"),
queryName = None,
validationMethod = Some(IgnoreMalformed)
)
)
) && assert(queryWithAllParams)(
equalTo(
GeoPolygon[TestDocument](
field = "stringField",
points = Chunk("40, -70", "30, -80", "20, -90"),
queryName = Some("name"),
validationMethod = Some(IgnoreMalformed)
)
)
)
},
test("hasChild") {
val query = hasChild("child", matchAll)
val queryWithIgnoreUnmapped = hasChild("child", matchAll).ignoreUnmappedTrue
Expand Down Expand Up @@ -2910,6 +2972,87 @@ object ElasticQuerySpec extends ZIOSpecDefault {
assert(queryWithValidationMethod.toJson(fieldPath = None))(equalTo(expectedWithValidationMethod.toJson)) &&
assert(queryWithAllParams.toJson(fieldPath = None))(equalTo(expectedWithAllParams.toJson))
},
test("geoPolygon") {
val query =
geoPolygon("testField", Chunk("40, -70", "30, -80", "20, -90"))
val queryTs =
geoPolygon(TestDocument.stringField, Chunk("drm3btev3e86", "drm3btev3e87"))
val queryWithName =
geoPolygon(TestDocument.stringField, Chunk("40, -70", "30, -80", "20, -90")).name("name")
val queryWithValidationMethod =
geoPolygon(TestDocument.stringField, Chunk("40, -70", "30, -80", "20, -90")).validationMethod(
IgnoreMalformed
)
val queryWithAllParams =
geoPolygon(TestDocument.stringField, Chunk("40, -70", "30, -80", "20, -90"))
.validationMethod(IgnoreMalformed)
.name("name")

val expected =
"""
|{
| "geo_polygon": {
| "testField": {
| "points": ["40, -70", "30, -80", "20, -90"]
| }
| }
|}
|""".stripMargin

val expectedTs =
"""
|{
| "geo_polygon": {
| "stringField": {
| "points": ["drm3btev3e86", "drm3btev3e87"]
| }
| }
|}
|""".stripMargin

val expectedWithName =
"""
|{
| "geo_polygon": {
| "_name": "name",
| "stringField": {
| "points": ["40, -70", "30, -80", "20, -90"]
| }
| }
|}
|""".stripMargin

val expectedWithValidationMethod =
"""
|{
| "geo_polygon": {
| "validation_method": "IGNORE_MALFORMED",
| "stringField": {
| "points": ["40, -70", "30, -80", "20, -90"]
| }
| }
|}
|""".stripMargin

val expectedWithAllParams =
"""
|{
| "geo_polygon": {
| "validation_method": "IGNORE_MALFORMED",
| "_name": "name",
| "stringField": {
| "points": ["40, -70", "30, -80", "20, -90"]
| }
| }
|}
|""".stripMargin

assert(query.toJson(fieldPath = None))(equalTo(expected.toJson)) &&
assert(queryTs.toJson(fieldPath = None))(equalTo(expectedTs.toJson)) &&
assert(queryWithName.toJson(fieldPath = None))(equalTo(expectedWithName.toJson)) &&
assert(queryWithValidationMethod.toJson(fieldPath = None))(equalTo(expectedWithValidationMethod.toJson)) &&
assert(queryWithAllParams.toJson(fieldPath = None))(equalTo(expectedWithAllParams.toJson))
},
test("hasChild") {
val query = hasChild("child", matches(TestDocument.stringField, "test"))
val queryWithIgnoreUnmapped = hasChild("child", matches("field", "value")).ignoreUnmappedTrue
Expand Down
1 change: 1 addition & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module.exports = {
'overview/queries/elastic_query_function_score',
'overview/queries/elastic_query_fuzzy',
'overview/queries/elastic_query_geo_distance',
'overview/queries/elastic_query_geo_polygon',
'overview/queries/elastic_query_has_child',
'overview/queries/elastic_query_has_parent',
'overview/queries/elastic_query_match',
Expand Down

0 comments on commit f1a33dd

Please sign in to comment.