Skip to content

Commit

Permalink
Support GeoJSON for geo_point
Browse files Browse the repository at this point in the history
  • Loading branch information
craigtaverner committed Mar 18, 2022
1 parent b9f7e45 commit bbc61bd
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public GeoPoint resetFromString(String value) {
}

public GeoPoint resetFromString(String value, final boolean ignoreZValue, EffectivePoint effectivePoint) {
// TODO: Support GeoJSON
if (value.toLowerCase(Locale.ROOT).contains("point")) {
return resetFromWKT(value, ignoreZValue);
} else if (value.contains(",")) {
Expand Down
35 changes: 35 additions & 0 deletions server/src/main/java/org/elasticsearch/common/geo/GeoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import org.elasticsearch.xcontent.support.MapXContentParser;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Locale;

public class GeoUtils {

Expand All @@ -42,6 +44,8 @@ public class GeoUtils {
public static final String LATITUDE = "lat";
public static final String LONGITUDE = "lon";
public static final String GEOHASH = "geohash";
public static final String COORDINATES = "coordinates";
public static final String TYPE = "type";

/** Earth ellipsoid major axis defined by WGS 84 in meters */
public static final double EARTH_SEMI_MAJOR_AXIS = 6378137.0; // meters (WGS 84)
Expand Down Expand Up @@ -434,9 +438,12 @@ public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point, fina
*/
public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point, final boolean ignoreZValue, EffectivePoint effectivePoint)
throws IOException, ElasticsearchParseException {
// TODO: Consider merging with GeoJSON parser
double lat = Double.NaN;
double lon = Double.NaN;
String geohash = null;
String geojsonType = null;
ArrayList<Double> coordinates = null;
NumberFormatException numberFormatException = null;

if (parser.currentToken() == Token.START_OBJECT) {
Expand Down Expand Up @@ -478,6 +485,26 @@ public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point, fina
} else {
throw new ElasticsearchParseException("geohash must be a string");
}
} else if (COORDINATES.equals(field)) {
subParser.nextToken();
if (subParser.currentToken() == Token.START_ARRAY) {
coordinates = new ArrayList<>();
subParser.nextToken();
while (subParser.currentToken() != Token.END_ARRAY) {
switch (subParser.currentToken()) {
case VALUE_NUMBER -> coordinates.add(subParser.doubleValue());
case VALUE_STRING -> coordinates.add(subParser.doubleValue(true));
default -> throw new ElasticsearchParseException("GeoJSON coordinate values must be numbers");
}
subParser.nextToken();
}
}
} else if (TYPE.equals(field)) {
if (subParser.nextToken() == Token.VALUE_STRING) {
geojsonType = subParser.text();
} else {
throw new ElasticsearchParseException("GeoJSON type must be a string");
}
} else {
throw new ElasticsearchParseException("field must be either [{}], [{}] or [{}]", LATITUDE, LONGITUDE, GEOHASH);
}
Expand All @@ -492,6 +519,14 @@ public static GeoPoint parseGeoPoint(XContentParser parser, GeoPoint point, fina
} else {
return point.parseGeoHash(geohash, effectivePoint);
}
} else if (coordinates != null) {
if (geojsonType == null || geojsonType.toLowerCase(Locale.ROOT).equals("point") == false) {
throw new ElasticsearchParseException("GeoJSON type for geo_point can only be 'point'");
}
if (coordinates.size() > 2) {
GeoPoint.assertZValue(ignoreZValue, coordinates.get(2));
}
return point.reset(coordinates.get(1), coordinates.get(0));
} else if (numberFormatException != null) {
throw new ElasticsearchParseException(
"[{}] and [{}] must be valid double values",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@

import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.geo.ShapeRelation;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.geo.GeometryTestUtils;
import org.elasticsearch.geometry.Point;
import org.elasticsearch.geometry.utils.WellKnownText;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentFactory;

import java.io.IOException;
import java.util.HashMap;

import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
import static org.elasticsearch.index.query.QueryBuilders.geoShapeQuery;
Expand Down Expand Up @@ -67,4 +71,59 @@ public void testFieldAlias() throws IOException {
SearchResponse response = client().prepareSearch(defaultIndexName).setQuery(geoShapeQuery("alias", point)).get();
assertEquals(1, response.getHits().getTotalHits().value);
}

public void testQueryPointFromMultiPointFormats() throws Exception {
createMapping(defaultIndexName, defaultGeoFieldName);
ensureGreen();

double[] geojsonDoubles = new double[] { 45.0, 35.0 };
HashMap<String, Object> geojson = new HashMap<>();
geojson.put("type", "Point");
geojson.put("coordinates", geojsonDoubles);
double[] pointDoubles = new double[] { 35.0, 25.0 };
Object[] points = new Object[] { "-35, -45", "POINT(-35 -25)", pointDoubles, geojson };
client().prepareIndex(defaultIndexName)
.setId("1")
.setSource(jsonBuilder().startObject().field(defaultGeoFieldName, points).endObject())
.setRefreshPolicy(IMMEDIATE)
.get();

Point pointA = new Point(-45, -35);
Point pointB = new Point(-35, -25);
Point pointC = new Point(35, 25);
Point pointD = new Point(45, 35);
Point pointInvalid = new Point(-35, -35);
for (Point point : new Point[] { pointA, pointB, pointC, pointD, pointInvalid }) {
int expectedDocs = point.equals(pointInvalid) ? 0 : 1;
int disjointDocs = point.equals(pointInvalid) ? 1 : 0;
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point))
.get();
SearchHits searchHits = response.getHits();
assertEquals("Doc matches %s" + point, expectedDocs, searchHits.getTotalHits().value);
}
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point).relation(ShapeRelation.WITHIN))
.get();
SearchHits searchHits = response.getHits();
assertEquals("Doc WITHIN %s" + point, 0, searchHits.getTotalHits().value);
}
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point).relation(ShapeRelation.CONTAINS))
.get();
SearchHits searchHits = response.getHits();
assertEquals("Doc CONTAINS %s" + point, expectedDocs, searchHits.getTotalHits().value);
}
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point).relation(ShapeRelation.DISJOINT))
.get();
SearchHits searchHits = response.getHits();
assertEquals("Doc DISJOINT with %s" + point, disjointDocs, searchHits.getTotalHits().value);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package org.elasticsearch.search.geo;

import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchAction;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.SearchRequestBuilder;
Expand Down Expand Up @@ -38,6 +39,7 @@

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
Expand Down Expand Up @@ -583,4 +585,101 @@ public void testQueryMultiPoint() throws Exception {
assertEquals(0, searchHits.getTotalHits().value);
}
}

public void testQueryPointFromGeoJSON() throws Exception {
createMapping(defaultIndexName, defaultGeoFieldName);
ensureGreen();

String doc1 = """
{
"geo": {
"coordinates": [ -35, -25.0 ],
"type": "Point"
}
}""";
client().index(new IndexRequest(defaultIndexName).id("1").source(doc1, XContentType.JSON).setRefreshPolicy(IMMEDIATE)).actionGet();

Point point = new Point(-35, -25);
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point))
.get();
SearchHits searchHits = response.getHits();
assertEquals(1, searchHits.getTotalHits().value);
}
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point).relation(ShapeRelation.WITHIN))
.get();
SearchHits searchHits = response.getHits();
assertEquals(1, searchHits.getTotalHits().value);
}
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point).relation(ShapeRelation.CONTAINS))
.get();
SearchHits searchHits = response.getHits();
assertEquals(1, searchHits.getTotalHits().value);
}
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point).relation(ShapeRelation.DISJOINT))
.get();
SearchHits searchHits = response.getHits();
assertEquals(0, searchHits.getTotalHits().value);
}
}

public void testQueryPointFromMultiPoint() throws Exception {
createMapping(defaultIndexName, defaultGeoFieldName);
ensureGreen();

double[] pointDoubles = new double[] { 35.0, 25.0 };
HashMap<String, Object> geojson = new HashMap<>();
geojson.put("type", "Point");
geojson.put("coordinates", pointDoubles);
Object[] points = new Object[] { "POINT(-45 -35)", "POINT(-35 -25)", geojson };
client().prepareIndex(defaultIndexName)
.setId("1")
.setSource(jsonBuilder().startObject().field(defaultGeoFieldName, points).endObject())
.setRefreshPolicy(IMMEDIATE)
.get();

Point pointA = new Point(-45, -35);
Point pointB = new Point(-35, -25);
Point pointC = new Point(35, 25);
Point pointInvalid = new Point(-35, -35);
for (Point point : new Point[] { pointA, pointB, pointC, pointInvalid }) {
int expectedDocs = point.equals(pointInvalid) ? 0 : 1;
int disjointDocs = point.equals(pointInvalid) ? 1 : 0;
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point))
.get();
SearchHits searchHits = response.getHits();
assertEquals("Doc matches %s" + point, expectedDocs, searchHits.getTotalHits().value);
}
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point).relation(ShapeRelation.WITHIN))
.get();
SearchHits searchHits = response.getHits();
assertEquals("Doc WITHIN %s" + point, 0, searchHits.getTotalHits().value);
}
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point).relation(ShapeRelation.CONTAINS))
.get();
SearchHits searchHits = response.getHits();
assertEquals("Doc CONTAINS %s" + point, expectedDocs, searchHits.getTotalHits().value);
}
{
SearchResponse response = client().prepareSearch(defaultIndexName)
.setQuery(QueryBuilders.geoShapeQuery(defaultGeoFieldName, point).relation(ShapeRelation.DISJOINT))
.get();
SearchHits searchHits = response.getHits();
assertEquals("Doc DISJOINT with %s" + point, disjointDocs, searchHits.getTotalHits().value);
}
}
}
}

0 comments on commit bbc61bd

Please sign in to comment.