I'm facing a problem with elasticsearch and I don't know how it is possible to solve it.
Here is my document :
{
"company": "XXX",
"InBusinessSince": "YYYY-MM-DD",
"...": "...",
"location": {
"lat": 14.01234,
"lon": 60.1234
},
"maxDistanceInKm": 50
}
I want to be able to query my index by passing another location as a parameter and only have in return the documents where the distance between the location of the document and the location passed in parameter is below maxDistanceInKm value.
Bonus Question : In fact if it is possible I'd like to be able to provide a location as a parameter and also an alternateMaxDistance, and only have documents that matches distance is lower than the minimum between maxDistanceInKm of the document and alternateMaxDistance
Note : of course it is a simplified version of my index it contains lots of other fields onto which I also apply various conditions but I have no problem for them.
Thanks a lot for your help !
Ps : I'm looking at painless script to solve this but I've never used them and I don't know if it is possible to use them for such situation.
Answering the first question for now:
{
"query": {
"bool": {
"filter": {
"script": {
"script": {
"source": """
doc['location'].arcDistance(
params.location.lat,
params.location.lon
) * 0.001 <= doc['maxDistanceInKm'].value
""",
"params": {
"location": {
"lat": 15.5,
"lon": 60
}
}
}
}
}
}
}
}
Assuming the location
field is of geo_point
type, you can use the arcDistance()
method to calculate, in meters(!), the distance to another point specified by lat/lon coordinates.
(Note that I used triple quotes to improve legibility; this is not valid JSON syntax, and to try it, you'll have to replace them with double quotes and keep the script source on one line.)
To include an alternateMaxDistance
parameter, just use the Math.min()
method:
{
"query": {
"bool": {
"filter": {
"script": {
"script": {
"source": """
doc['location'].arcDistance(
params.location.lat,
params.location.lon
) * 0.001 <= Math.min(
doc['maxDistanceInKm'].value,
params.alternateMaxDistance
)
""",
"params": {
"location": {
"lat": 15.5,
"lon": 60
},
"alternateMaxDistance": 170
}
}
}
}
}
}
}
For completion, here's how you'd use this query from Spring Data Elasticsearch.
Assuming a POJO/entity like:
@Document(createIndex = false, indexName = "so77843318")
class CompanyDetails {
@Id
@Field(type = FieldType.Keyword)
private String company;
// GeoPointField and GeoPoint under org.springframework.data.elasticsearch.*
@GeoPointField
private GeoPoint location;
@Field(type = FieldType.Half_Float)
private Float maxDistanceInKm;
//... accessors, etc.
}
and a Spring-based setup with an ElasticsearchRestTemplate
injected in your service (or repository, etc.), one basic way to execute the query is:
List<SearchHit<CompanyDetails>> queryAround(
org.elasticsearch.common.geo.GeoPoint location,
Float alternateMaxDistance
) {
final var filterBuilder = boolQuery().filter(
scriptQuery(
new Script(
ScriptType.INLINE,
"painless",
"""
doc['location'].arcDistance(
params.location.lat,
params.location.lon
) * 0.001 <= Math.min(
doc['maxDistanceInKm'].value,
params.alternateMaxDistance
)
""",
Map.of("location", location, "alternateMaxDistance", alternateMaxDistance)
)
)
);
final var queryBuilder = new NativeSearchQueryBuilder()
.withTrackTotalHits(true) // if we need the total hits number, ES 7+
.withQuery(filterBuilder);
return elasticsearch.search(
queryBuilder.build(),
CompanyDetails.class,
IndexCoordinates.of("so77843318")
).getSearchHits();
}
Note that:
CompanyDetails
instances via this method, and you might want to keep the index name used in IndexCoordinates.of("so77843318")
in a property, etc.NativeSearchQuery
, which is quite flexible.