Revert "[Geo] Integrate Lucene's LatLonShape (BKD Backed GeoShapes) as default `geo_shape` indexing approach (#35320)"
This reverts commit 5bc7822562
.
This commit is contained in:
parent
2f5300e3a6
commit
96d279ed83
|
@ -21,59 +21,48 @@ type.
|
||||||
|=======================================================================
|
|=======================================================================
|
||||||
|Option |Description| Default
|
|Option |Description| Default
|
||||||
|
|
||||||
|`tree |deprecated[6.6, PrefixTrees no longer used] Name of the PrefixTree
|
|`tree` |Name of the PrefixTree implementation to be used: `geohash` for
|
||||||
implementation to be used: `geohash` for GeohashPrefixTree and `quadtree`
|
GeohashPrefixTree and `quadtree` for QuadPrefixTree.
|
||||||
for QuadPrefixTree. Note: This parameter is only relevant for `term` and
|
| `geohash`
|
||||||
`recursive` strategies.
|
|
||||||
| `quadtree`
|
|
||||||
|
|
||||||
|`precision` |deprecated[6.6, PrefixTrees no longer used] This parameter may
|
|`precision` |This parameter may be used instead of `tree_levels` to set
|
||||||
be used instead of `tree_levels` to set an appropriate value for the
|
an appropriate value for the `tree_levels` parameter. The value
|
||||||
`tree_levels` parameter. The value specifies the desired precision and
|
specifies the desired precision and Elasticsearch will calculate the
|
||||||
Elasticsearch will calculate the best tree_levels value to honor this
|
best tree_levels value to honor this precision. The value should be a
|
||||||
precision. The value should be a number followed by an optional distance
|
number followed by an optional distance unit. Valid distance units
|
||||||
unit. Valid distance units include: `in`, `inch`, `yd`, `yard`, `mi`,
|
include: `in`, `inch`, `yd`, `yard`, `mi`, `miles`, `km`, `kilometers`,
|
||||||
`miles`, `km`, `kilometers`, `m`,`meters`, `cm`,`centimeters`, `mm`,
|
`m`,`meters`, `cm`,`centimeters`, `mm`, `millimeters`.
|
||||||
`millimeters`. Note: This parameter is only relevant for `term` and
|
|
||||||
`recursive` strategies.
|
|
||||||
| `50m`
|
| `50m`
|
||||||
|
|
||||||
|`tree_levels` |deprecated[6.6, PrefixTrees no longer used] Maximum number
|
|`tree_levels` |Maximum number of layers to be used by the PrefixTree.
|
||||||
of layers to be used by the PrefixTree. This can be used to control the
|
This can be used to control the precision of shape representations and
|
||||||
precision of shape representations andtherefore how many terms are
|
therefore how many terms are indexed. Defaults to the default value of
|
||||||
indexed. Defaults to the default value of the chosen PrefixTree
|
the chosen PrefixTree implementation. Since this parameter requires a
|
||||||
implementation. Since this parameter requires a certain level of
|
certain level of understanding of the underlying implementation, users
|
||||||
understanding of the underlying implementation, users may use the
|
may use the `precision` parameter instead. However, Elasticsearch only
|
||||||
`precision` parameter instead. However, Elasticsearch only uses the
|
uses the tree_levels parameter internally and this is what is returned
|
||||||
tree_levels parameter internally and this is what is returned via the
|
via the mapping API even if you use the precision parameter.
|
||||||
mapping API even if you use the precision parameter. Note: This parameter
|
|
||||||
is only relevant for `term` and `recursive` strategies.
|
|
||||||
| various
|
| various
|
||||||
|
|
||||||
|`strategy` |deprecated[6.6, PrefixTrees no longer used] The strategy
|
|`strategy` |The strategy parameter defines the approach for how to
|
||||||
parameter defines the approach for how to represent shapes at indexing
|
represent shapes at indexing and search time. It also influences the
|
||||||
and search time. It also influences the capabilities available so it
|
capabilities available so it is recommended to let Elasticsearch set
|
||||||
is recommended to let Elasticsearch set this parameter automatically.
|
this parameter automatically. There are two strategies available:
|
||||||
There are two strategies available: `recursive`, and `term`.
|
`recursive` and `term`. Term strategy supports point types only (the
|
||||||
Recursive and Term strategies are deprecated and will be removed in a
|
`points_only` parameter will be automatically set to true) while
|
||||||
future version. While they are still available, the Term strategy
|
Recursive strategy supports all shape types. (IMPORTANT: see
|
||||||
supports point types only (the `points_only` parameter will be
|
<<prefix-trees, Prefix trees>> for more detailed information)
|
||||||
automatically set to true) while Recursive strategy supports all
|
|
||||||
shape types. (IMPORTANT: see <<prefix-trees, Prefix trees>> for more
|
|
||||||
detailed information about these strategies)
|
|
||||||
| `recursive`
|
| `recursive`
|
||||||
|
|
||||||
|`distance_error_pct` |deprecated[6.6, PrefixTrees no longer used] Used as a
|
|`distance_error_pct` |Used as a hint to the PrefixTree about how
|
||||||
hint to the PrefixTree about how precise it should be. Defaults to 0.025 (2.5%)
|
precise it should be. Defaults to 0.025 (2.5%) with 0.5 as the maximum
|
||||||
with 0.5 as the maximum supported value. PERFORMANCE NOTE: This value will
|
supported value. PERFORMANCE NOTE: This value will default to 0 if a `precision` or
|
||||||
default to 0 if a `precision` or `tree_level` definition is explicitly defined.
|
`tree_level` definition is explicitly defined. This guarantees spatial precision
|
||||||
This guarantees spatial precision at the level defined in the mapping. This can
|
at the level defined in the mapping. This can lead to significant memory usage
|
||||||
lead to significant memory usage for high resolution shapes with low error
|
for high resolution shapes with low error (e.g., large shapes at 1m with < 0.001 error).
|
||||||
(e.g., large shapes at 1m with < 0.001 error). To improve indexing performance
|
To improve indexing performance (at the cost of query accuracy) explicitly define
|
||||||
(at the cost of query accuracy) explicitly define `tree_level` or `precision`
|
`tree_level` or `precision` along with a reasonable `distance_error_pct`, noting
|
||||||
along with a reasonable `distance_error_pct`, noting that large shapes will have
|
that large shapes will have greater false positives.
|
||||||
greater false positives. Note: This parameter is only relevant for `term` and
|
|
||||||
`recursive` strategies.
|
|
||||||
| `0.025`
|
| `0.025`
|
||||||
|
|
||||||
|`orientation` |Optionally define how to interpret vertex order for
|
|`orientation` |Optionally define how to interpret vertex order for
|
||||||
|
@ -88,13 +77,13 @@ sets vertex order for the coordinate list of a geo_shape field but can be
|
||||||
overridden in each individual GeoJSON or WKT document.
|
overridden in each individual GeoJSON or WKT document.
|
||||||
| `ccw`
|
| `ccw`
|
||||||
|
|
||||||
|`points_only` |deprecated[6.6, PrefixTrees no longer used] Setting this option to
|
|`points_only` |Setting this option to `true` (defaults to `false`) configures
|
||||||
`true` (defaults to `false`) configures the `geo_shape` field type for point
|
the `geo_shape` field type for point shapes only (NOTE: Multi-Points are not
|
||||||
shapes only (NOTE: Multi-Points are not yet supported). This optimizes index and
|
yet supported). This optimizes index and search performance for the `geohash` and
|
||||||
search performance for the `geohash` and `quadtree` when it is known that only points
|
`quadtree` when it is known that only points will be indexed. At present geo_shape
|
||||||
will be indexed. At present geo_shape queries can not be executed on `geo_point`
|
queries can not be executed on `geo_point` field types. This option bridges the gap
|
||||||
field types. This option bridges the gap by improving point performance on a
|
by improving point performance on a `geo_shape` field so that `geo_shape` queries are
|
||||||
`geo_shape` field so that `geo_shape` queries are optimal on a point only field.
|
optimal on a point only field.
|
||||||
| `false`
|
| `false`
|
||||||
|
|
||||||
|`ignore_malformed` |If true, malformed GeoJSON or WKT shapes are ignored. If
|
|`ignore_malformed` |If true, malformed GeoJSON or WKT shapes are ignored. If
|
||||||
|
@ -111,35 +100,16 @@ and reject the whole document.
|
||||||
|
|
||||||
|=======================================================================
|
|=======================================================================
|
||||||
|
|
||||||
|
|
||||||
[[geoshape-indexing-approach]]
|
|
||||||
[float]
|
|
||||||
==== Indexing approach
|
|
||||||
GeoShape types are indexed by decomposing the shape into a triangular mesh and
|
|
||||||
indexing each triangle as a 7 dimension point in a BKD tree. This provides
|
|
||||||
near perfect spatial resolution (down to 1e-7 decimal degree precision) since all
|
|
||||||
spatial relations are computed using an encoded vector representation of the
|
|
||||||
original shape instead of a raster-grid representation as used by the
|
|
||||||
<<prefix-trees>> indexing approach. Performance of the tessellator primarily
|
|
||||||
depends on the number of vertices that define the polygon/multi-polyogn. While
|
|
||||||
this is the default indexing technique prefix trees can still be used by setting
|
|
||||||
the `tree` or `strategy` parameters according to the appropriate
|
|
||||||
<<geo-shape-mapping-options>>. Note that these parameters are now deprecated
|
|
||||||
and will be removed in a future version.
|
|
||||||
|
|
||||||
[[prefix-trees]]
|
[[prefix-trees]]
|
||||||
[float]
|
[float]
|
||||||
==== Prefix trees
|
==== Prefix trees
|
||||||
|
|
||||||
deprecated[6.6, PrefixTrees no longer used] To efficiently represent shapes in
|
To efficiently represent shapes in the index, Shapes are converted into
|
||||||
an inverted index, Shapes are converted into a series of hashes representing
|
a series of hashes representing grid squares (commonly referred to as "rasters")
|
||||||
grid squares (commonly referred to as "rasters") using implementations of a
|
using implementations of a PrefixTree. The tree notion comes from the fact that
|
||||||
PrefixTree. The tree notion comes from the fact that the PrefixTree uses multiple
|
the PrefixTree uses multiple grid layers, each with an increasing level of
|
||||||
grid layers, each with an increasing level of precision to represent the Earth.
|
precision to represent the Earth. This can be thought of as increasing the level
|
||||||
This can be thought of as increasing the level of detail of a map or image at higher
|
of detail of a map or image at higher zoom levels.
|
||||||
zoom levels. Since this approach causes precision issues with indexed shape, it has
|
|
||||||
been deprecated in favor of a vector indexing approach that indexes the shapes as a
|
|
||||||
triangular mesh (see <<geoshape-indexing-approach>>).
|
|
||||||
|
|
||||||
Multiple PrefixTree implementations are provided:
|
Multiple PrefixTree implementations are provided:
|
||||||
|
|
||||||
|
@ -161,10 +131,9 @@ number of levels for the quad trees in Elasticsearch is 29; the default is 21.
|
||||||
[[spatial-strategy]]
|
[[spatial-strategy]]
|
||||||
[float]
|
[float]
|
||||||
===== Spatial strategies
|
===== Spatial strategies
|
||||||
deprecated[6.6, PrefixTrees no longer used] The indexing implementation
|
The PrefixTree implementations rely on a SpatialStrategy for decomposing
|
||||||
selected relies on a SpatialStrategy for choosing how to decompose the shapes
|
the provided Shape(s) into approximated grid squares. Each strategy answers
|
||||||
(either as grid squares or a tessellated triangular mesh). Each strategy
|
the following:
|
||||||
answers the following:
|
|
||||||
|
|
||||||
* What type of Shapes can be indexed?
|
* What type of Shapes can be indexed?
|
||||||
* What types of Query Operations and Shapes can be used?
|
* What types of Query Operations and Shapes can be used?
|
||||||
|
@ -177,7 +146,7 @@ are provided:
|
||||||
|=======================================================================
|
|=======================================================================
|
||||||
|Strategy |Supported Shapes |Supported Queries |Multiple Shapes
|
|Strategy |Supported Shapes |Supported Queries |Multiple Shapes
|
||||||
|
|
||||||
|`recursive` |<<input-structure, All>> |`INTERSECTS`, `DISJOINT`, `WITHIN`, `CONTAINS` |Yes
|
|`recursive` |<<input-structure, All>> |`INTERSECTS`, `DISJOINT`, `WITHIN`, `CONTAINS` |Yes
|
||||||
|`term` |<<point, Points>> |`INTERSECTS` |Yes
|
|`term` |<<point, Points>> |`INTERSECTS` |Yes
|
||||||
|
|
||||||
|=======================================================================
|
|=======================================================================
|
||||||
|
@ -185,13 +154,13 @@ are provided:
|
||||||
[float]
|
[float]
|
||||||
===== Accuracy
|
===== Accuracy
|
||||||
|
|
||||||
`Recursive` and `Term` strategies do not provide 100% accuracy and depending on
|
Geo_shape does not provide 100% accuracy and depending on how it is configured
|
||||||
how they are configured it may return some false positives for `INTERSECTS`,
|
it may return some false positives for `INTERSECTS`, `WITHIN` and `CONTAINS`
|
||||||
`WITHIN` and `CONTAINS` queries, and some false negatives for `DISJOINT` queries.
|
queries, and some false negatives for `DISJOINT` queries. To mitigate this, it
|
||||||
To mitigate this, it is important to select an appropriate value for the tree_levels
|
is important to select an appropriate value for the tree_levels parameter and
|
||||||
parameter and to adjust expectations accordingly. For example, a point may be near
|
to adjust expectations accordingly. For example, a point may be near the border
|
||||||
the border of a particular grid cell and may thus not match a query that only matches
|
of a particular grid cell and may thus not match a query that only matches the
|
||||||
the cell right next to it -- even though the shape is very close to the point.
|
cell right next to it -- even though the shape is very close to the point.
|
||||||
|
|
||||||
[float]
|
[float]
|
||||||
===== Example
|
===== Example
|
||||||
|
@ -204,7 +173,9 @@ PUT /example
|
||||||
"doc": {
|
"doc": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"location": {
|
"location": {
|
||||||
"type": "geo_shape"
|
"type": "geo_shape",
|
||||||
|
"tree": "quadtree",
|
||||||
|
"precision": "100m"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,23 +185,22 @@ PUT /example
|
||||||
// CONSOLE
|
// CONSOLE
|
||||||
// TESTSETUP
|
// TESTSETUP
|
||||||
|
|
||||||
This mapping definition maps the location field to the geo_shape
|
This mapping maps the location field to the geo_shape type using the
|
||||||
type using the default vector implementation. It provides
|
quad_tree implementation and a precision of 100m. Elasticsearch translates
|
||||||
approximately 1e-7 decimal degree precision.
|
this into a tree_levels setting of 20.
|
||||||
|
|
||||||
[float]
|
[float]
|
||||||
===== Performance considerations with Prefix Trees
|
===== Performance considerations
|
||||||
|
|
||||||
deprecated[6.6, PrefixTrees no longer used] With prefix trees,
|
Elasticsearch uses the paths in the prefix tree as terms in the index
|
||||||
Elasticsearch uses the paths in the tree as terms in the inverted index
|
and in queries. The higher the level is (and thus the precision), the
|
||||||
and in queries. The higher the level (and thus the precision), the more
|
more terms are generated. Of course, calculating the terms, keeping them in
|
||||||
terms are generated. Of course, calculating the terms, keeping them in
|
|
||||||
memory, and storing them on disk all have a price. Especially with higher
|
memory, and storing them on disk all have a price. Especially with higher
|
||||||
tree levels, indices can become extremely large even with a modest amount
|
tree levels, indices can become extremely large even with a modest
|
||||||
of data. Additionally, the size of the features also matters. Big, complex
|
amount of data. Additionally, the size of the features also matters.
|
||||||
polygons can take up a lot of space at higher tree levels. Which setting
|
Big, complex polygons can take up a lot of space at higher tree levels.
|
||||||
is right depends on the use case. Generally one trades off accuracy against
|
Which setting is right depends on the use case. Generally one trades off
|
||||||
index size and query performance.
|
accuracy against index size and query performance.
|
||||||
|
|
||||||
The defaults in Elasticsearch for both implementations are a compromise
|
The defaults in Elasticsearch for both implementations are a compromise
|
||||||
between index size and a reasonable level of precision of 50m at the
|
between index size and a reasonable level of precision of 50m at the
|
||||||
|
@ -628,10 +598,7 @@ POST /example/doc
|
||||||
===== Circle
|
===== Circle
|
||||||
|
|
||||||
Elasticsearch supports a `circle` type, which consists of a center
|
Elasticsearch supports a `circle` type, which consists of a center
|
||||||
point with a radius. Note that this circle representation can only
|
point with a radius:
|
||||||
be indexed when using the `recursive` Prefix Tree strategy. For
|
|
||||||
the default <<geoshape-indexing-approach>> circles should be approximated using
|
|
||||||
a `POLYGON`.
|
|
||||||
|
|
||||||
[source,js]
|
[source,js]
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
@ -645,7 +612,6 @@ POST /example/doc
|
||||||
}
|
}
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
// CONSOLE
|
// CONSOLE
|
||||||
// TEST[skip:not supported in default]
|
|
||||||
|
|
||||||
Note: The inner `radius` field is required. If not specified, then
|
Note: The inner `radius` field is required. If not specified, then
|
||||||
the units of the `radius` will default to `METERS`.
|
the units of the `radius` will default to `METERS`.
|
||||||
|
|
|
@ -52,19 +52,3 @@ as a better alternative.
|
||||||
|
|
||||||
An error will now be thrown when unknown configuration options are provided
|
An error will now be thrown when unknown configuration options are provided
|
||||||
to similarities. Such unknown parameters were ignored before.
|
to similarities. Such unknown parameters were ignored before.
|
||||||
|
|
||||||
[float]
|
|
||||||
==== deprecated `geo_shape` Prefix Tree indexing
|
|
||||||
|
|
||||||
`geo_shape` types now default to using a vector indexing approach based on Lucene's new
|
|
||||||
`LatLonShape` field type. This indexes shapes as a triangular mesh instead of decomposing
|
|
||||||
them into individual grid cells. To index using legacy prefix trees `recursive` or `term`
|
|
||||||
strategy must be explicitly defined. Note that these strategies are now deprecated and will
|
|
||||||
be removed in a future version.
|
|
||||||
|
|
||||||
[float]
|
|
||||||
==== deprecated `geo_shape` parameters
|
|
||||||
|
|
||||||
The following type parameters are deprecated for the `geo_shape` field type: `tree`,
|
|
||||||
`precision`, `tree_levels`, `distance_error_pct`, `points_only`, and `strategy`. They
|
|
||||||
will be removed in a future version.
|
|
|
@ -7,7 +7,7 @@ Requires the <<geo-shape,`geo_shape` Mapping>>.
|
||||||
|
|
||||||
The `geo_shape` query uses the same grid square representation as the
|
The `geo_shape` query uses the same grid square representation as the
|
||||||
`geo_shape` mapping to find documents that have a shape that intersects
|
`geo_shape` mapping to find documents that have a shape that intersects
|
||||||
with the query shape. It will also use the same Prefix Tree configuration
|
with the query shape. It will also use the same PrefixTree configuration
|
||||||
as defined for the field mapping.
|
as defined for the field mapping.
|
||||||
|
|
||||||
The query supports two ways of defining the query shape, either by
|
The query supports two ways of defining the query shape, either by
|
||||||
|
@ -157,8 +157,7 @@ has nothing in common with the query geometry.
|
||||||
* `WITHIN` - Return all documents whose `geo_shape` field
|
* `WITHIN` - Return all documents whose `geo_shape` field
|
||||||
is within the query geometry.
|
is within the query geometry.
|
||||||
* `CONTAINS` - Return all documents whose `geo_shape` field
|
* `CONTAINS` - Return all documents whose `geo_shape` field
|
||||||
contains the query geometry. Note: this is only supported using the
|
contains the query geometry.
|
||||||
`recursive` Prefix Tree Strategy deprecated[6.6]
|
|
||||||
|
|
||||||
[float]
|
[float]
|
||||||
==== Ignore Unmapped
|
==== Ignore Unmapped
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
|
|
||||||
package org.elasticsearch.common.geo;
|
package org.elasticsearch.common.geo;
|
||||||
|
|
||||||
import org.apache.lucene.document.LatLonShape.QueryRelation;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.io.stream.Writeable;
|
import org.elasticsearch.common.io.stream.Writeable;
|
||||||
|
@ -63,17 +62,6 @@ public enum ShapeRelation implements Writeable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Maps ShapeRelation to Lucene's LatLonShapeRelation */
|
|
||||||
public QueryRelation getLuceneRelation() {
|
|
||||||
switch (this) {
|
|
||||||
case INTERSECTS: return QueryRelation.INTERSECTS;
|
|
||||||
case DISJOINT: return QueryRelation.DISJOINT;
|
|
||||||
case WITHIN: return QueryRelation.WITHIN;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("ShapeRelation [" + this + "] not supported");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRelationName() {
|
public String getRelationName() {
|
||||||
return relationName;
|
return relationName;
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,6 +197,9 @@ public class GeometryCollectionBuilder extends ShapeBuilder<Shape, GeometryColle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shapes.size() == 1) {
|
||||||
|
return shapes.get(0);
|
||||||
|
}
|
||||||
return shapes.toArray(new Object[shapes.size()]);
|
return shapes.toArray(new Object[shapes.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,10 @@ import org.elasticsearch.common.geo.GeoShapeType;
|
||||||
import org.elasticsearch.common.geo.builders.CircleBuilder;
|
import org.elasticsearch.common.geo.builders.CircleBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
|
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
|
|
||||||
import org.elasticsearch.common.unit.DistanceUnit;
|
import org.elasticsearch.common.unit.DistanceUnit;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentSubParser;
|
import org.elasticsearch.common.xcontent.XContentSubParser;
|
||||||
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||||
import org.locationtech.jts.geom.Coordinate;
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -42,22 +41,17 @@ import java.util.List;
|
||||||
* complies with geojson specification: https://tools.ietf.org/html/rfc7946
|
* complies with geojson specification: https://tools.ietf.org/html/rfc7946
|
||||||
*/
|
*/
|
||||||
abstract class GeoJsonParser {
|
abstract class GeoJsonParser {
|
||||||
protected static ShapeBuilder parse(XContentParser parser, BaseGeoShapeFieldMapper shapeMapper)
|
protected static ShapeBuilder parse(XContentParser parser, GeoShapeFieldMapper shapeMapper)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
GeoShapeType shapeType = null;
|
GeoShapeType shapeType = null;
|
||||||
DistanceUnit.Distance radius = null;
|
DistanceUnit.Distance radius = null;
|
||||||
CoordinateNode coordinateNode = null;
|
CoordinateNode coordinateNode = null;
|
||||||
GeometryCollectionBuilder geometryCollections = null;
|
GeometryCollectionBuilder geometryCollections = null;
|
||||||
|
|
||||||
Orientation orientation = (shapeMapper == null)
|
ShapeBuilder.Orientation requestedOrientation =
|
||||||
? BaseGeoShapeFieldMapper.Defaults.ORIENTATION.value()
|
(shapeMapper == null) ? ShapeBuilder.Orientation.RIGHT : shapeMapper.fieldType().orientation();
|
||||||
: shapeMapper.orientation();
|
Explicit<Boolean> coerce = (shapeMapper == null) ? GeoShapeFieldMapper.Defaults.COERCE : shapeMapper.coerce();
|
||||||
Explicit<Boolean> coerce = (shapeMapper == null)
|
Explicit<Boolean> ignoreZValue = (shapeMapper == null) ? GeoShapeFieldMapper.Defaults.IGNORE_Z_VALUE : shapeMapper.ignoreZValue();
|
||||||
? BaseGeoShapeFieldMapper.Defaults.COERCE
|
|
||||||
: shapeMapper.coerce();
|
|
||||||
Explicit<Boolean> ignoreZValue = (shapeMapper == null)
|
|
||||||
? BaseGeoShapeFieldMapper.Defaults.IGNORE_Z_VALUE
|
|
||||||
: shapeMapper.ignoreZValue();
|
|
||||||
|
|
||||||
String malformedException = null;
|
String malformedException = null;
|
||||||
|
|
||||||
|
@ -108,7 +102,7 @@ abstract class GeoJsonParser {
|
||||||
malformedException = "cannot have [" + ShapeParser.FIELD_ORIENTATION + "] with type set to [" + shapeType + "]";
|
malformedException = "cannot have [" + ShapeParser.FIELD_ORIENTATION + "] with type set to [" + shapeType + "]";
|
||||||
}
|
}
|
||||||
subParser.nextToken();
|
subParser.nextToken();
|
||||||
orientation = ShapeBuilder.Orientation.fromString(subParser.text());
|
requestedOrientation = ShapeBuilder.Orientation.fromString(subParser.text());
|
||||||
} else {
|
} else {
|
||||||
subParser.nextToken();
|
subParser.nextToken();
|
||||||
subParser.skipChildren();
|
subParser.skipChildren();
|
||||||
|
@ -134,7 +128,7 @@ abstract class GeoJsonParser {
|
||||||
return geometryCollections;
|
return geometryCollections;
|
||||||
}
|
}
|
||||||
|
|
||||||
return shapeType.getBuilder(coordinateNode, radius, orientation, coerce.value());
|
return shapeType.getBuilder(coordinateNode, radius, requestedOrientation, coerce.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,7 +202,7 @@ abstract class GeoJsonParser {
|
||||||
* @return Geometry[] geometries of the GeometryCollection
|
* @return Geometry[] geometries of the GeometryCollection
|
||||||
* @throws IOException Thrown if an error occurs while reading from the XContentParser
|
* @throws IOException Thrown if an error occurs while reading from the XContentParser
|
||||||
*/
|
*/
|
||||||
static GeometryCollectionBuilder parseGeometries(XContentParser parser, BaseGeoShapeFieldMapper mapper) throws
|
static GeometryCollectionBuilder parseGeometries(XContentParser parser, GeoShapeFieldMapper mapper) throws
|
||||||
IOException {
|
IOException {
|
||||||
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
|
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
|
||||||
throw new ElasticsearchParseException("geometries must be an array of geojson objects");
|
throw new ElasticsearchParseException("geometries must be an array of geojson objects");
|
||||||
|
|
|
@ -34,7 +34,7 @@ import org.elasticsearch.common.geo.builders.PolygonBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.common.logging.Loggers;
|
import org.elasticsearch.common.logging.Loggers;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||||
import org.locationtech.jts.geom.Coordinate;
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -63,7 +63,7 @@ public class GeoWKTParser {
|
||||||
// no instance
|
// no instance
|
||||||
private GeoWKTParser() {}
|
private GeoWKTParser() {}
|
||||||
|
|
||||||
public static ShapeBuilder parse(XContentParser parser, final BaseGeoShapeFieldMapper shapeMapper)
|
public static ShapeBuilder parse(XContentParser parser, final GeoShapeFieldMapper shapeMapper)
|
||||||
throws IOException, ElasticsearchParseException {
|
throws IOException, ElasticsearchParseException {
|
||||||
return parseExpectedType(parser, null, shapeMapper);
|
return parseExpectedType(parser, null, shapeMapper);
|
||||||
}
|
}
|
||||||
|
@ -75,12 +75,12 @@ public class GeoWKTParser {
|
||||||
|
|
||||||
/** throws an exception if the parsed geometry type does not match the expected shape type */
|
/** throws an exception if the parsed geometry type does not match the expected shape type */
|
||||||
public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType,
|
public static ShapeBuilder parseExpectedType(XContentParser parser, final GeoShapeType shapeType,
|
||||||
final BaseGeoShapeFieldMapper shapeMapper)
|
final GeoShapeFieldMapper shapeMapper)
|
||||||
throws IOException, ElasticsearchParseException {
|
throws IOException, ElasticsearchParseException {
|
||||||
try (StringReader reader = new StringReader(parser.text())) {
|
try (StringReader reader = new StringReader(parser.text())) {
|
||||||
Explicit<Boolean> ignoreZValue = (shapeMapper == null) ? BaseGeoShapeFieldMapper.Defaults.IGNORE_Z_VALUE :
|
Explicit<Boolean> ignoreZValue = (shapeMapper == null) ? GeoShapeFieldMapper.Defaults.IGNORE_Z_VALUE :
|
||||||
shapeMapper.ignoreZValue();
|
shapeMapper.ignoreZValue();
|
||||||
Explicit<Boolean> coerce = (shapeMapper == null) ? BaseGeoShapeFieldMapper.Defaults.COERCE : shapeMapper.coerce();
|
Explicit<Boolean> coerce = (shapeMapper == null) ? GeoShapeFieldMapper.Defaults.COERCE : shapeMapper.coerce();
|
||||||
// setup the tokenizer; configured to read words w/o numbers
|
// setup the tokenizer; configured to read words w/o numbers
|
||||||
StreamTokenizer tokenizer = new StreamTokenizer(reader);
|
StreamTokenizer tokenizer = new StreamTokenizer(reader);
|
||||||
tokenizer.resetSyntax();
|
tokenizer.resetSyntax();
|
||||||
|
@ -257,8 +257,7 @@ public class GeoWKTParser {
|
||||||
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
|
if (nextEmptyOrOpen(stream).equals(EMPTY)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
PolygonBuilder builder = new PolygonBuilder(parseLinearRing(stream, ignoreZValue, coerce),
|
PolygonBuilder builder = new PolygonBuilder(parseLinearRing(stream, ignoreZValue, coerce), ShapeBuilder.Orientation.RIGHT);
|
||||||
BaseGeoShapeFieldMapper.Defaults.ORIENTATION.value());
|
|
||||||
while (nextCloserOrComma(stream).equals(COMMA)) {
|
while (nextCloserOrComma(stream).equals(COMMA)) {
|
||||||
builder.hole(parseLinearRing(stream, ignoreZValue, coerce));
|
builder.hole(parseLinearRing(stream, ignoreZValue, coerce));
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import org.elasticsearch.common.ParseField;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContent;
|
import org.elasticsearch.common.xcontent.XContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ public interface ShapeParser {
|
||||||
* if the parsers current token has been <code>null</code>
|
* if the parsers current token has been <code>null</code>
|
||||||
* @throws IOException if the input could not be read
|
* @throws IOException if the input could not be read
|
||||||
*/
|
*/
|
||||||
static ShapeBuilder parse(XContentParser parser, BaseGeoShapeFieldMapper shapeMapper) throws IOException {
|
static ShapeBuilder parse(XContentParser parser, GeoShapeFieldMapper shapeMapper) throws IOException {
|
||||||
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
|
if (parser.currentToken() == XContentParser.Token.VALUE_NULL) {
|
||||||
return null;
|
return null;
|
||||||
} if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
|
} if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
|
||||||
|
|
|
@ -1,336 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch under one or more contributor
|
|
||||||
* license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright
|
|
||||||
* ownership. Elasticsearch licenses this file to you under
|
|
||||||
* the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
* not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.index.mapper;
|
|
||||||
|
|
||||||
import org.apache.lucene.index.IndexOptions;
|
|
||||||
import org.apache.lucene.index.IndexableField;
|
|
||||||
import org.apache.lucene.index.Term;
|
|
||||||
import org.apache.lucene.search.Query;
|
|
||||||
import org.apache.lucene.search.TermQuery;
|
|
||||||
import org.elasticsearch.Version;
|
|
||||||
import org.elasticsearch.common.Explicit;
|
|
||||||
import org.elasticsearch.common.ParseField;
|
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
|
||||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper.DeprecatedParameters;
|
|
||||||
import org.elasticsearch.index.query.QueryShardContext;
|
|
||||||
import org.elasticsearch.index.query.QueryShardException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_MALFORMED;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for {@link GeoShapeFieldMapper} and {@link LegacyGeoShapeFieldMapper}
|
|
||||||
*/
|
|
||||||
public abstract class BaseGeoShapeFieldMapper extends FieldMapper {
|
|
||||||
public static final String CONTENT_TYPE = "geo_shape";
|
|
||||||
|
|
||||||
public static class Names {
|
|
||||||
public static final ParseField ORIENTATION = new ParseField("orientation");
|
|
||||||
public static final ParseField COERCE = new ParseField("coerce");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Defaults {
|
|
||||||
public static final Explicit<Orientation> ORIENTATION = new Explicit<>(Orientation.RIGHT, false);
|
|
||||||
public static final Explicit<Boolean> COERCE = new Explicit<>(false, false);
|
|
||||||
public static final Explicit<Boolean> IGNORE_MALFORMED = new Explicit<>(false, false);
|
|
||||||
public static final Explicit<Boolean> IGNORE_Z_VALUE = new Explicit<>(true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract static class Builder<T extends Builder, Y extends BaseGeoShapeFieldMapper>
|
|
||||||
extends FieldMapper.Builder<T, Y> {
|
|
||||||
protected Boolean coerce;
|
|
||||||
protected Boolean ignoreMalformed;
|
|
||||||
protected Boolean ignoreZValue;
|
|
||||||
protected Orientation orientation;
|
|
||||||
|
|
||||||
/** default builder - used for external mapper*/
|
|
||||||
public Builder(String name, MappedFieldType fieldType, MappedFieldType defaultFieldType) {
|
|
||||||
super(name, fieldType, defaultFieldType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder(String name, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
|
||||||
boolean coerce, boolean ignoreMalformed, Orientation orientation, boolean ignoreZ) {
|
|
||||||
super(name, fieldType, defaultFieldType);
|
|
||||||
this.coerce = coerce;
|
|
||||||
this.ignoreMalformed = ignoreMalformed;
|
|
||||||
this.orientation = orientation;
|
|
||||||
this.ignoreZValue = ignoreZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder coerce(boolean coerce) {
|
|
||||||
this.coerce = coerce;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Explicit<Boolean> coerce(BuilderContext context) {
|
|
||||||
if (coerce != null) {
|
|
||||||
return new Explicit<>(coerce, true);
|
|
||||||
}
|
|
||||||
if (context.indexSettings() != null) {
|
|
||||||
return new Explicit<>(COERCE_SETTING.get(context.indexSettings()), false);
|
|
||||||
}
|
|
||||||
return Defaults.COERCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder orientation(Orientation orientation) {
|
|
||||||
this.orientation = orientation;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Explicit<Orientation> orientation() {
|
|
||||||
if (orientation != null) {
|
|
||||||
return new Explicit<>(orientation, true);
|
|
||||||
}
|
|
||||||
return Defaults.ORIENTATION;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean defaultDocValues(Version indexCreated) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ignoreMalformed(boolean ignoreMalformed) {
|
|
||||||
this.ignoreMalformed = ignoreMalformed;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Explicit<Boolean> ignoreMalformed(BuilderContext context) {
|
|
||||||
if (ignoreMalformed != null) {
|
|
||||||
return new Explicit<>(ignoreMalformed, true);
|
|
||||||
}
|
|
||||||
if (context.indexSettings() != null) {
|
|
||||||
return new Explicit<>(IGNORE_MALFORMED_SETTING.get(context.indexSettings()), false);
|
|
||||||
}
|
|
||||||
return Defaults.IGNORE_MALFORMED;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Explicit<Boolean> ignoreZValue() {
|
|
||||||
if (ignoreZValue != null) {
|
|
||||||
return new Explicit<>(ignoreZValue, true);
|
|
||||||
}
|
|
||||||
return Defaults.IGNORE_Z_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder ignoreZValue(final boolean ignoreZValue) {
|
|
||||||
this.ignoreZValue = ignoreZValue;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setupFieldType(BuilderContext context) {
|
|
||||||
super.setupFieldType(context);
|
|
||||||
|
|
||||||
// field mapper handles this at build time
|
|
||||||
// but prefix tree strategies require a name, so throw a similar exception
|
|
||||||
if (name().isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("name cannot be empty string");
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseGeoShapeFieldType ft = (BaseGeoShapeFieldType)fieldType();
|
|
||||||
ft.setOrientation(orientation().value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TypeParser implements Mapper.TypeParser {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
|
|
||||||
boolean coerce = Defaults.COERCE.value();
|
|
||||||
boolean ignoreZ = Defaults.IGNORE_Z_VALUE.value();
|
|
||||||
boolean ignoreMalformed = Defaults.IGNORE_MALFORMED.value();
|
|
||||||
Orientation orientation = Defaults.ORIENTATION.value();
|
|
||||||
DeprecatedParameters deprecatedParameters = new DeprecatedParameters();
|
|
||||||
boolean parsedDeprecatedParams = false;
|
|
||||||
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
|
|
||||||
Map.Entry<String, Object> entry = iterator.next();
|
|
||||||
String fieldName = entry.getKey();
|
|
||||||
Object fieldNode = entry.getValue();
|
|
||||||
if (DeprecatedParameters.parse(name, fieldName, fieldNode, deprecatedParameters)) {
|
|
||||||
parsedDeprecatedParams = true;
|
|
||||||
iterator.remove();
|
|
||||||
} else if (Names.ORIENTATION.match(fieldName, LoggingDeprecationHandler.INSTANCE)) {
|
|
||||||
orientation = ShapeBuilder.Orientation.fromString(fieldNode.toString());
|
|
||||||
iterator.remove();
|
|
||||||
} else if (IGNORE_MALFORMED.equals(fieldName)) {
|
|
||||||
ignoreMalformed = XContentMapValues.nodeBooleanValue(fieldNode, name + ".ignore_malformed");
|
|
||||||
iterator.remove();
|
|
||||||
} else if (Names.COERCE.match(fieldName, LoggingDeprecationHandler.INSTANCE)) {
|
|
||||||
coerce = XContentMapValues.nodeBooleanValue(fieldNode, name + "." + Names.COERCE.getPreferredName());
|
|
||||||
iterator.remove();
|
|
||||||
} else if (GeoPointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName().equals(fieldName)) {
|
|
||||||
ignoreZ = XContentMapValues.nodeBooleanValue(fieldNode,
|
|
||||||
name + "." + GeoPointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName());
|
|
||||||
iterator.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getBuilder(name, coerce, ignoreMalformed, orientation, ignoreZ, parsedDeprecatedParams ? deprecatedParameters : null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder getBuilder(String name, boolean coerce, boolean ignoreMalformed, Orientation orientation,
|
|
||||||
boolean ignoreZ, DeprecatedParameters deprecatedParameters) {
|
|
||||||
if (deprecatedParameters != null) {
|
|
||||||
return getLegacyBuilder(name, coerce, ignoreMalformed, orientation, ignoreZ, deprecatedParameters);
|
|
||||||
}
|
|
||||||
return new GeoShapeFieldMapper.Builder(name, coerce, ignoreMalformed, orientation, ignoreZ);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder getLegacyBuilder(String name, boolean coerce, boolean ignoreMalformed, Orientation orientation,
|
|
||||||
boolean ignoreZ, DeprecatedParameters deprecatedParameters) {
|
|
||||||
return new LegacyGeoShapeFieldMapper.Builder(name, coerce, ignoreMalformed, orientation, ignoreZ, deprecatedParameters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract static class BaseGeoShapeFieldType extends MappedFieldType {
|
|
||||||
protected Orientation orientation = Defaults.ORIENTATION.value();
|
|
||||||
|
|
||||||
protected BaseGeoShapeFieldType() {
|
|
||||||
setIndexOptions(IndexOptions.DOCS);
|
|
||||||
setTokenized(false);
|
|
||||||
setStored(false);
|
|
||||||
setStoreTermVectors(false);
|
|
||||||
setOmitNorms(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BaseGeoShapeFieldType(BaseGeoShapeFieldType ref) {
|
|
||||||
super(ref);
|
|
||||||
this.orientation = ref.orientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (!super.equals(o)) return false;
|
|
||||||
BaseGeoShapeFieldType that = (BaseGeoShapeFieldType) o;
|
|
||||||
return orientation == that.orientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(super.hashCode(), orientation);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String typeName() {
|
|
||||||
return CONTENT_TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkCompatibility(MappedFieldType fieldType, List<String> conflicts) {
|
|
||||||
super.checkCompatibility(fieldType, conflicts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Orientation orientation() { return this.orientation; }
|
|
||||||
|
|
||||||
public void setOrientation(Orientation orientation) {
|
|
||||||
checkIfFrozen();
|
|
||||||
this.orientation = orientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Query existsQuery(QueryShardContext context) {
|
|
||||||
return new TermQuery(new Term(FieldNamesFieldMapper.NAME, name()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Query termQuery(Object value, QueryShardContext context) {
|
|
||||||
throw new QueryShardException(context, "Geo fields do not support exact searching, use dedicated geo queries instead");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Explicit<Boolean> coerce;
|
|
||||||
protected Explicit<Boolean> ignoreMalformed;
|
|
||||||
protected Explicit<Boolean> ignoreZValue;
|
|
||||||
|
|
||||||
protected BaseGeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
|
||||||
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
|
|
||||||
Explicit<Boolean> ignoreZValue, Settings indexSettings,
|
|
||||||
MultiFields multiFields, CopyTo copyTo) {
|
|
||||||
super(simpleName, fieldType, defaultFieldType, indexSettings, multiFields, copyTo);
|
|
||||||
this.coerce = coerce;
|
|
||||||
this.ignoreMalformed = ignoreMalformed;
|
|
||||||
this.ignoreZValue = ignoreZValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doMerge(Mapper mergeWith) {
|
|
||||||
super.doMerge(mergeWith);
|
|
||||||
BaseGeoShapeFieldMapper gsfm = (BaseGeoShapeFieldMapper)mergeWith;
|
|
||||||
if (gsfm.coerce.explicit()) {
|
|
||||||
this.coerce = gsfm.coerce;
|
|
||||||
}
|
|
||||||
if (gsfm.ignoreMalformed.explicit()) {
|
|
||||||
this.ignoreMalformed = gsfm.ignoreMalformed;
|
|
||||||
}
|
|
||||||
if (gsfm.ignoreZValue.explicit()) {
|
|
||||||
this.ignoreZValue = gsfm.ignoreZValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void parseCreateField(ParseContext context, List<IndexableField> fields) throws IOException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
|
|
||||||
builder.field("type", contentType());
|
|
||||||
BaseGeoShapeFieldType ft = (BaseGeoShapeFieldType)fieldType();
|
|
||||||
if (includeDefaults || ft.orientation() != Defaults.ORIENTATION.value()) {
|
|
||||||
builder.field(Names.ORIENTATION.getPreferredName(), ft.orientation());
|
|
||||||
}
|
|
||||||
if (includeDefaults || coerce.explicit()) {
|
|
||||||
builder.field(Names.COERCE.getPreferredName(), coerce.value());
|
|
||||||
}
|
|
||||||
if (includeDefaults || ignoreMalformed.explicit()) {
|
|
||||||
builder.field(IGNORE_MALFORMED, ignoreMalformed.value());
|
|
||||||
}
|
|
||||||
if (includeDefaults || ignoreZValue.explicit()) {
|
|
||||||
builder.field(GeoPointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName(), ignoreZValue.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Explicit<Boolean> coerce() {
|
|
||||||
return coerce;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Explicit<Boolean> ignoreMalformed() {
|
|
||||||
return ignoreMalformed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Explicit<Boolean> ignoreZValue() {
|
|
||||||
return ignoreZValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Orientation orientation() {
|
|
||||||
return ((BaseGeoShapeFieldType)fieldType).orientation();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String contentType() {
|
|
||||||
return CONTENT_TYPE;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,24 +18,48 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.index.mapper;
|
package org.elasticsearch.index.mapper;
|
||||||
|
|
||||||
import org.apache.lucene.document.Field;
|
import org.apache.lucene.index.IndexOptions;
|
||||||
import org.apache.lucene.document.LatLonShape;
|
|
||||||
import org.apache.lucene.geo.Line;
|
|
||||||
import org.apache.lucene.geo.Polygon;
|
|
||||||
import org.apache.lucene.geo.Rectangle;
|
|
||||||
import org.apache.lucene.index.IndexableField;
|
import org.apache.lucene.index.IndexableField;
|
||||||
|
import org.apache.lucene.index.Term;
|
||||||
|
import org.apache.lucene.search.Query;
|
||||||
|
import org.apache.lucene.search.TermQuery;
|
||||||
|
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.PackedQuadPrefixTree;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
|
||||||
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.common.Explicit;
|
import org.elasticsearch.common.Explicit;
|
||||||
import org.elasticsearch.common.geo.GeoPoint;
|
import org.elasticsearch.common.geo.GeoUtils;
|
||||||
|
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||||
|
import org.elasticsearch.common.geo.XShapeCollection;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
|
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
|
||||||
import org.elasticsearch.common.geo.parsers.ShapeParser;
|
import org.elasticsearch.common.geo.parsers.ShapeParser;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.unit.DistanceUnit;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
||||||
|
import org.elasticsearch.index.query.QueryShardContext;
|
||||||
|
import org.elasticsearch.index.query.QueryShardException;
|
||||||
|
import org.locationtech.spatial4j.shape.Point;
|
||||||
|
import org.locationtech.spatial4j.shape.Shape;
|
||||||
|
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_MALFORMED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FieldMapper for indexing {@link org.apache.lucene.document.LatLonShape}s.
|
* FieldMapper for indexing {@link org.locationtech.spatial4j.shape.Shape}s.
|
||||||
* <p>
|
* <p>
|
||||||
* Currently Shapes can only be indexed and can only be queried using
|
* Currently Shapes can only be indexed and can only be queried using
|
||||||
* {@link org.elasticsearch.index.query.GeoShapeQueryBuilder}, consequently
|
* {@link org.elasticsearch.index.query.GeoShapeQueryBuilder}, consequently
|
||||||
|
@ -49,128 +73,554 @@ import java.util.Arrays;
|
||||||
* [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
|
* [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
|
||||||
* ]
|
* ]
|
||||||
* }
|
* }
|
||||||
* <p>
|
|
||||||
* or:
|
|
||||||
* <p>
|
|
||||||
* "field" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))
|
|
||||||
*/
|
*/
|
||||||
public class GeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
public class GeoShapeFieldMapper extends FieldMapper {
|
||||||
|
|
||||||
|
public static final String CONTENT_TYPE = "geo_shape";
|
||||||
|
|
||||||
|
public static class Names {
|
||||||
|
public static final String TREE = "tree";
|
||||||
|
public static final String TREE_GEOHASH = "geohash";
|
||||||
|
public static final String TREE_QUADTREE = "quadtree";
|
||||||
|
public static final String TREE_LEVELS = "tree_levels";
|
||||||
|
public static final String TREE_PRESISION = "precision";
|
||||||
|
public static final String DISTANCE_ERROR_PCT = "distance_error_pct";
|
||||||
|
public static final String ORIENTATION = "orientation";
|
||||||
|
public static final String STRATEGY = "strategy";
|
||||||
|
public static final String STRATEGY_POINTS_ONLY = "points_only";
|
||||||
|
public static final String COERCE = "coerce";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Defaults {
|
||||||
|
public static final String TREE = Names.TREE_GEOHASH;
|
||||||
|
public static final String STRATEGY = SpatialStrategy.RECURSIVE.getStrategyName();
|
||||||
|
public static final boolean POINTS_ONLY = false;
|
||||||
|
public static final int GEOHASH_LEVELS = GeoUtils.geoHashLevelsForPrecision("50m");
|
||||||
|
public static final int QUADTREE_LEVELS = GeoUtils.quadTreeLevelsForPrecision("50m");
|
||||||
|
public static final Orientation ORIENTATION = Orientation.RIGHT;
|
||||||
|
public static final double LEGACY_DISTANCE_ERROR_PCT = 0.025d;
|
||||||
|
public static final Explicit<Boolean> COERCE = new Explicit<>(false, false);
|
||||||
|
public static final Explicit<Boolean> IGNORE_MALFORMED = new Explicit<>(false, false);
|
||||||
|
public static final Explicit<Boolean> IGNORE_Z_VALUE = new Explicit<>(true, false);
|
||||||
|
|
||||||
|
public static final MappedFieldType FIELD_TYPE = new GeoShapeFieldType();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// setting name here is a hack so freeze can be called...instead all these options should be
|
||||||
|
// moved to the default ctor for GeoShapeFieldType, and defaultFieldType() should be removed from mappers...
|
||||||
|
FIELD_TYPE.setName("DoesNotExist");
|
||||||
|
FIELD_TYPE.setIndexOptions(IndexOptions.DOCS);
|
||||||
|
FIELD_TYPE.setTokenized(false);
|
||||||
|
FIELD_TYPE.setStored(false);
|
||||||
|
FIELD_TYPE.setStoreTermVectors(false);
|
||||||
|
FIELD_TYPE.setOmitNorms(true);
|
||||||
|
FIELD_TYPE.freeze();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder extends FieldMapper.Builder<Builder, GeoShapeFieldMapper> {
|
||||||
|
|
||||||
|
private Boolean coerce;
|
||||||
|
private Boolean ignoreMalformed;
|
||||||
|
private Boolean ignoreZValue;
|
||||||
|
|
||||||
public static class Builder extends BaseGeoShapeFieldMapper.Builder<BaseGeoShapeFieldMapper.Builder, GeoShapeFieldMapper> {
|
|
||||||
public Builder(String name) {
|
public Builder(String name) {
|
||||||
super (name, new GeoShapeFieldType(), new GeoShapeFieldType());
|
super(name, Defaults.FIELD_TYPE, Defaults.FIELD_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder(String name, boolean coerce, boolean ignoreMalformed, ShapeBuilder.Orientation orientation,
|
@Override
|
||||||
boolean ignoreZ) {
|
public GeoShapeFieldType fieldType() {
|
||||||
super(name, new GeoShapeFieldType(), new GeoShapeFieldType(), coerce, ignoreMalformed, orientation, ignoreZ);
|
return (GeoShapeFieldType)fieldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder coerce(boolean coerce) {
|
||||||
|
this.coerce = coerce;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean defaultDocValues(Version indexCreated) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Explicit<Boolean> coerce(BuilderContext context) {
|
||||||
|
if (coerce != null) {
|
||||||
|
return new Explicit<>(coerce, true);
|
||||||
|
}
|
||||||
|
if (context.indexSettings() != null) {
|
||||||
|
return new Explicit<>(COERCE_SETTING.get(context.indexSettings()), false);
|
||||||
|
}
|
||||||
|
return Defaults.COERCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder ignoreMalformed(boolean ignoreMalformed) {
|
||||||
|
this.ignoreMalformed = ignoreMalformed;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Explicit<Boolean> ignoreMalformed(BuilderContext context) {
|
||||||
|
if (ignoreMalformed != null) {
|
||||||
|
return new Explicit<>(ignoreMalformed, true);
|
||||||
|
}
|
||||||
|
if (context.indexSettings() != null) {
|
||||||
|
return new Explicit<>(IGNORE_MALFORMED_SETTING.get(context.indexSettings()), false);
|
||||||
|
}
|
||||||
|
return Defaults.IGNORE_MALFORMED;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Explicit<Boolean> ignoreZValue(BuilderContext context) {
|
||||||
|
if (ignoreZValue != null) {
|
||||||
|
return new Explicit<>(ignoreZValue, true);
|
||||||
|
}
|
||||||
|
return Defaults.IGNORE_Z_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder ignoreZValue(final boolean ignoreZValue) {
|
||||||
|
this.ignoreZValue = ignoreZValue;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GeoShapeFieldMapper build(BuilderContext context) {
|
public GeoShapeFieldMapper build(BuilderContext context) {
|
||||||
|
GeoShapeFieldType geoShapeFieldType = (GeoShapeFieldType)fieldType;
|
||||||
|
|
||||||
|
if (geoShapeFieldType.treeLevels() == 0 && geoShapeFieldType.precisionInMeters() < 0) {
|
||||||
|
geoShapeFieldType.setDefaultDistanceErrorPct(Defaults.LEGACY_DISTANCE_ERROR_PCT);
|
||||||
|
}
|
||||||
setupFieldType(context);
|
setupFieldType(context);
|
||||||
return new GeoShapeFieldMapper(name, fieldType, defaultFieldType, ignoreMalformed(context), coerce(context),
|
|
||||||
ignoreZValue(), context.indexSettings(), multiFieldsBuilder.build(this, context), copyTo);
|
return new GeoShapeFieldMapper(name, fieldType, ignoreMalformed(context), coerce(context), ignoreZValue(context),
|
||||||
|
context.indexSettings(), multiFieldsBuilder.build(this, context), copyTo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class GeoShapeFieldType extends BaseGeoShapeFieldType {
|
public static class TypeParser implements Mapper.TypeParser {
|
||||||
public GeoShapeFieldType() {
|
|
||||||
super();
|
@Override
|
||||||
|
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
|
||||||
|
Builder builder = new Builder(name);
|
||||||
|
Boolean pointsOnly = null;
|
||||||
|
for (Iterator<Map.Entry<String, Object>> iterator = node.entrySet().iterator(); iterator.hasNext();) {
|
||||||
|
Map.Entry<String, Object> entry = iterator.next();
|
||||||
|
String fieldName = entry.getKey();
|
||||||
|
Object fieldNode = entry.getValue();
|
||||||
|
if (Names.TREE.equals(fieldName)) {
|
||||||
|
builder.fieldType().setTree(fieldNode.toString());
|
||||||
|
iterator.remove();
|
||||||
|
} else if (Names.TREE_LEVELS.equals(fieldName)) {
|
||||||
|
builder.fieldType().setTreeLevels(Integer.parseInt(fieldNode.toString()));
|
||||||
|
iterator.remove();
|
||||||
|
} else if (Names.TREE_PRESISION.equals(fieldName)) {
|
||||||
|
builder.fieldType().setPrecisionInMeters(DistanceUnit.parse(fieldNode.toString(),
|
||||||
|
DistanceUnit.DEFAULT, DistanceUnit.DEFAULT));
|
||||||
|
iterator.remove();
|
||||||
|
} else if (Names.DISTANCE_ERROR_PCT.equals(fieldName)) {
|
||||||
|
builder.fieldType().setDistanceErrorPct(Double.parseDouble(fieldNode.toString()));
|
||||||
|
iterator.remove();
|
||||||
|
} else if (Names.ORIENTATION.equals(fieldName)) {
|
||||||
|
builder.fieldType().setOrientation(ShapeBuilder.Orientation.fromString(fieldNode.toString()));
|
||||||
|
iterator.remove();
|
||||||
|
} else if (Names.STRATEGY.equals(fieldName)) {
|
||||||
|
builder.fieldType().setStrategyName(fieldNode.toString());
|
||||||
|
iterator.remove();
|
||||||
|
} else if (IGNORE_MALFORMED.equals(fieldName)) {
|
||||||
|
builder.ignoreMalformed(XContentMapValues.nodeBooleanValue(fieldNode, name + ".ignore_malformed"));
|
||||||
|
iterator.remove();
|
||||||
|
} else if (Names.COERCE.equals(fieldName)) {
|
||||||
|
builder.coerce(XContentMapValues.nodeBooleanValue(fieldNode, name + "." + Names.COERCE));
|
||||||
|
iterator.remove();
|
||||||
|
} else if (GeoPointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName().equals(fieldName)) {
|
||||||
|
builder.ignoreZValue(XContentMapValues.nodeBooleanValue(fieldNode,
|
||||||
|
name + "." + GeoPointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName()));
|
||||||
|
iterator.remove();
|
||||||
|
} else if (Names.STRATEGY_POINTS_ONLY.equals(fieldName)) {
|
||||||
|
pointsOnly = XContentMapValues.nodeBooleanValue(fieldNode, name + "." + Names.STRATEGY_POINTS_ONLY);
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pointsOnly != null) {
|
||||||
|
if (builder.fieldType().strategyName.equals(SpatialStrategy.TERM.getStrategyName()) && pointsOnly == false) {
|
||||||
|
throw new IllegalArgumentException("points_only cannot be set to false for term strategy");
|
||||||
|
} else {
|
||||||
|
builder.fieldType().setPointsOnly(pointsOnly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class GeoShapeFieldType extends MappedFieldType {
|
||||||
|
|
||||||
|
private String tree = Defaults.TREE;
|
||||||
|
private String strategyName = Defaults.STRATEGY;
|
||||||
|
private boolean pointsOnly = Defaults.POINTS_ONLY;
|
||||||
|
private int treeLevels = 0;
|
||||||
|
private double precisionInMeters = -1;
|
||||||
|
private Double distanceErrorPct;
|
||||||
|
private double defaultDistanceErrorPct = 0.0;
|
||||||
|
private Orientation orientation = Defaults.ORIENTATION;
|
||||||
|
|
||||||
|
// these are built when the field type is frozen
|
||||||
|
private PrefixTreeStrategy defaultStrategy;
|
||||||
|
private RecursivePrefixTreeStrategy recursiveStrategy;
|
||||||
|
private TermQueryPrefixTreeStrategy termStrategy;
|
||||||
|
|
||||||
|
public GeoShapeFieldType() {}
|
||||||
|
|
||||||
protected GeoShapeFieldType(GeoShapeFieldType ref) {
|
protected GeoShapeFieldType(GeoShapeFieldType ref) {
|
||||||
super(ref);
|
super(ref);
|
||||||
|
this.tree = ref.tree;
|
||||||
|
this.strategyName = ref.strategyName;
|
||||||
|
this.pointsOnly = ref.pointsOnly;
|
||||||
|
this.treeLevels = ref.treeLevels;
|
||||||
|
this.precisionInMeters = ref.precisionInMeters;
|
||||||
|
this.distanceErrorPct = ref.distanceErrorPct;
|
||||||
|
this.defaultDistanceErrorPct = ref.defaultDistanceErrorPct;
|
||||||
|
this.orientation = ref.orientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GeoShapeFieldType clone() {
|
public GeoShapeFieldType clone() {
|
||||||
return new GeoShapeFieldType(this);
|
return new GeoShapeFieldType(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!super.equals(o)) return false;
|
||||||
|
GeoShapeFieldType that = (GeoShapeFieldType) o;
|
||||||
|
return treeLevels == that.treeLevels &&
|
||||||
|
precisionInMeters == that.precisionInMeters &&
|
||||||
|
defaultDistanceErrorPct == that.defaultDistanceErrorPct &&
|
||||||
|
Objects.equals(tree, that.tree) &&
|
||||||
|
Objects.equals(strategyName, that.strategyName) &&
|
||||||
|
pointsOnly == that.pointsOnly &&
|
||||||
|
Objects.equals(distanceErrorPct, that.distanceErrorPct) &&
|
||||||
|
orientation == that.orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(super.hashCode(), tree, strategyName, pointsOnly, treeLevels, precisionInMeters, distanceErrorPct,
|
||||||
|
defaultDistanceErrorPct, orientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String typeName() {
|
||||||
|
return CONTENT_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void freeze() {
|
||||||
|
super.freeze();
|
||||||
|
// This is a bit hackish: we need to setup the spatial tree and strategies once the field name is set, which
|
||||||
|
// must be by the time freeze is called.
|
||||||
|
SpatialPrefixTree prefixTree;
|
||||||
|
if ("geohash".equals(tree)) {
|
||||||
|
prefixTree = new GeohashPrefixTree(ShapeBuilder.SPATIAL_CONTEXT,
|
||||||
|
getLevels(treeLevels, precisionInMeters, Defaults.GEOHASH_LEVELS, true));
|
||||||
|
} else if ("legacyquadtree".equals(tree)) {
|
||||||
|
prefixTree = new QuadPrefixTree(ShapeBuilder.SPATIAL_CONTEXT,
|
||||||
|
getLevels(treeLevels, precisionInMeters, Defaults.QUADTREE_LEVELS, false));
|
||||||
|
} else if ("quadtree".equals(tree)) {
|
||||||
|
prefixTree = new PackedQuadPrefixTree(ShapeBuilder.SPATIAL_CONTEXT,
|
||||||
|
getLevels(treeLevels, precisionInMeters, Defaults.QUADTREE_LEVELS, false));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown prefix tree type [" + tree + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
recursiveStrategy = new RecursivePrefixTreeStrategy(prefixTree, name());
|
||||||
|
recursiveStrategy.setDistErrPct(distanceErrorPct());
|
||||||
|
recursiveStrategy.setPruneLeafyBranches(false);
|
||||||
|
termStrategy = new TermQueryPrefixTreeStrategy(prefixTree, name());
|
||||||
|
termStrategy.setDistErrPct(distanceErrorPct());
|
||||||
|
defaultStrategy = resolveStrategy(strategyName);
|
||||||
|
defaultStrategy.setPointsOnly(pointsOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkCompatibility(MappedFieldType fieldType, List<String> conflicts) {
|
||||||
|
super.checkCompatibility(fieldType, conflicts);
|
||||||
|
GeoShapeFieldType other = (GeoShapeFieldType)fieldType;
|
||||||
|
// prevent user from changing strategies
|
||||||
|
if (strategyName().equals(other.strategyName()) == false) {
|
||||||
|
conflicts.add("mapper [" + name() + "] has different [strategy]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent user from changing trees (changes encoding)
|
||||||
|
if (tree().equals(other.tree()) == false) {
|
||||||
|
conflicts.add("mapper [" + name() + "] has different [tree]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((pointsOnly() != other.pointsOnly())) {
|
||||||
|
conflicts.add("mapper [" + name() + "] has different points_only");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO we should allow this, but at the moment levels is used to build bookkeeping variables
|
||||||
|
// in lucene's SpatialPrefixTree implementations, need a patch to correct that first
|
||||||
|
if (treeLevels() != other.treeLevels()) {
|
||||||
|
conflicts.add("mapper [" + name() + "] has different [tree_levels]");
|
||||||
|
}
|
||||||
|
if (precisionInMeters() != other.precisionInMeters()) {
|
||||||
|
conflicts.add("mapper [" + name() + "] has different [precision]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getLevels(int treeLevels, double precisionInMeters, int defaultLevels, boolean geoHash) {
|
||||||
|
if (treeLevels > 0 || precisionInMeters >= 0) {
|
||||||
|
return Math.max(treeLevels, precisionInMeters >= 0 ? (geoHash ? GeoUtils.geoHashLevelsForPrecision(precisionInMeters)
|
||||||
|
: GeoUtils.quadTreeLevelsForPrecision(precisionInMeters)) : 0);
|
||||||
|
}
|
||||||
|
return defaultLevels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String tree() {
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTree(String tree) {
|
||||||
|
checkIfFrozen();
|
||||||
|
this.tree = tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String strategyName() {
|
||||||
|
return strategyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStrategyName(String strategyName) {
|
||||||
|
checkIfFrozen();
|
||||||
|
this.strategyName = strategyName;
|
||||||
|
if (this.strategyName.equals(SpatialStrategy.TERM.getStrategyName())) {
|
||||||
|
this.pointsOnly = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean pointsOnly() {
|
||||||
|
return pointsOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPointsOnly(boolean pointsOnly) {
|
||||||
|
checkIfFrozen();
|
||||||
|
this.pointsOnly = pointsOnly;
|
||||||
|
}
|
||||||
|
public int treeLevels() {
|
||||||
|
return treeLevels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTreeLevels(int treeLevels) {
|
||||||
|
checkIfFrozen();
|
||||||
|
this.treeLevels = treeLevels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double precisionInMeters() {
|
||||||
|
return precisionInMeters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrecisionInMeters(double precisionInMeters) {
|
||||||
|
checkIfFrozen();
|
||||||
|
this.precisionInMeters = precisionInMeters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double distanceErrorPct() {
|
||||||
|
return distanceErrorPct == null ? defaultDistanceErrorPct : distanceErrorPct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDistanceErrorPct(double distanceErrorPct) {
|
||||||
|
checkIfFrozen();
|
||||||
|
this.distanceErrorPct = distanceErrorPct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultDistanceErrorPct(double defaultDistanceErrorPct) {
|
||||||
|
checkIfFrozen();
|
||||||
|
this.defaultDistanceErrorPct = defaultDistanceErrorPct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Orientation orientation() { return this.orientation; }
|
||||||
|
|
||||||
|
public void setOrientation(Orientation orientation) {
|
||||||
|
checkIfFrozen();
|
||||||
|
this.orientation = orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrefixTreeStrategy defaultStrategy() {
|
||||||
|
return this.defaultStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrefixTreeStrategy resolveStrategy(SpatialStrategy strategy) {
|
||||||
|
return resolveStrategy(strategy.getStrategyName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrefixTreeStrategy resolveStrategy(String strategyName) {
|
||||||
|
if (SpatialStrategy.RECURSIVE.getStrategyName().equals(strategyName)) {
|
||||||
|
return recursiveStrategy;
|
||||||
|
}
|
||||||
|
if (SpatialStrategy.TERM.getStrategyName().equals(strategyName)) {
|
||||||
|
return termStrategy;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Unknown prefix tree strategy [" + strategyName + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query existsQuery(QueryShardContext context) {
|
||||||
|
return new TermQuery(new Term(FieldNamesFieldMapper.NAME, name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Query termQuery(Object value, QueryShardContext context) {
|
||||||
|
throw new QueryShardException(context, "Geo fields do not support exact searching, use dedicated geo queries instead");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
protected Explicit<Boolean> coerce;
|
||||||
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce,
|
protected Explicit<Boolean> ignoreMalformed;
|
||||||
Explicit<Boolean> ignoreZValue, Settings indexSettings,
|
protected Explicit<Boolean> ignoreZValue;
|
||||||
|
|
||||||
|
public GeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, Explicit<Boolean> ignoreMalformed,
|
||||||
|
Explicit<Boolean> coerce, Explicit<Boolean> ignoreZValue, Settings indexSettings,
|
||||||
MultiFields multiFields, CopyTo copyTo) {
|
MultiFields multiFields, CopyTo copyTo) {
|
||||||
super(simpleName, fieldType, defaultFieldType, ignoreMalformed, coerce, ignoreZValue, indexSettings,
|
super(simpleName, fieldType, Defaults.FIELD_TYPE, indexSettings, multiFields, copyTo);
|
||||||
multiFields, copyTo);
|
this.coerce = coerce;
|
||||||
|
this.ignoreMalformed = ignoreMalformed;
|
||||||
|
this.ignoreZValue = ignoreZValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GeoShapeFieldType fieldType() {
|
public GeoShapeFieldType fieldType() {
|
||||||
return (GeoShapeFieldType) super.fieldType();
|
return (GeoShapeFieldType) super.fieldType();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** parsing logic for {@link LatLonShape} indexing */
|
|
||||||
@Override
|
@Override
|
||||||
public void parse(ParseContext context) throws IOException {
|
public void parse(ParseContext context) throws IOException {
|
||||||
try {
|
try {
|
||||||
Object shape = context.parseExternalValue(Object.class);
|
Shape shape = context.parseExternalValue(Shape.class);
|
||||||
if (shape == null) {
|
if (shape == null) {
|
||||||
ShapeBuilder shapeBuilder = ShapeParser.parse(context.parser(), this);
|
ShapeBuilder shapeBuilder = ShapeParser.parse(context.parser(), this);
|
||||||
if (shapeBuilder == null) {
|
if (shapeBuilder == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
shape = shapeBuilder.buildLucene();
|
shape = shapeBuilder.buildS4J();
|
||||||
|
}
|
||||||
|
if (fieldType().pointsOnly() == true) {
|
||||||
|
// index configured for pointsOnly
|
||||||
|
if (shape instanceof XShapeCollection && XShapeCollection.class.cast(shape).pointsOnly()) {
|
||||||
|
// MULTIPOINT data: index each point separately
|
||||||
|
List<Shape> shapes = ((XShapeCollection) shape).getShapes();
|
||||||
|
for (Shape s : shapes) {
|
||||||
|
indexShape(context, s);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (shape instanceof Point == false) {
|
||||||
|
throw new MapperParsingException("[{" + fieldType().name() + "}] is configured for points only but a "
|
||||||
|
+ ((shape instanceof JtsGeometry) ? ((JtsGeometry)shape).getGeom().getGeometryType() : shape.getClass())
|
||||||
|
+ " was found");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
indexShape(context, shape);
|
indexShape(context, shape);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (ignoreMalformed.value() == false) {
|
if (ignoreMalformed.value() == false) {
|
||||||
throw new MapperParsingException("failed to parse field [{}] of type [{}]", e, fieldType().name(),
|
throw new MapperParsingException("failed to parse field [{}] of type [{}]", e, fieldType().name(),
|
||||||
fieldType().typeName());
|
fieldType().typeName());
|
||||||
}
|
}
|
||||||
context.addIgnoredField(fieldType().name());
|
context.addIgnoredField(fieldType.name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void indexShape(ParseContext context, Object luceneShape) {
|
private void indexShape(ParseContext context, Shape shape) {
|
||||||
if (luceneShape instanceof GeoPoint) {
|
List<IndexableField> fields = new ArrayList<>(Arrays.asList(fieldType().defaultStrategy().createIndexableFields(shape)));
|
||||||
GeoPoint pt = (GeoPoint) luceneShape;
|
createFieldNamesField(context, fields);
|
||||||
indexFields(context, LatLonShape.createIndexableFields(name(), pt.lat(), pt.lon()));
|
for (IndexableField field : fields) {
|
||||||
} else if (luceneShape instanceof double[]) {
|
context.doc().add(field);
|
||||||
double[] pt = (double[]) luceneShape;
|
}
|
||||||
indexFields(context, LatLonShape.createIndexableFields(name(), pt[1], pt[0]));
|
}
|
||||||
} else if (luceneShape instanceof Line) {
|
|
||||||
indexFields(context, LatLonShape.createIndexableFields(name(), (Line)luceneShape));
|
@Override
|
||||||
} else if (luceneShape instanceof Polygon) {
|
protected void parseCreateField(ParseContext context, List<IndexableField> fields) throws IOException {
|
||||||
indexFields(context, LatLonShape.createIndexableFields(name(), (Polygon) luceneShape));
|
}
|
||||||
} else if (luceneShape instanceof double[][]) {
|
|
||||||
double[][] pts = (double[][])luceneShape;
|
@Override
|
||||||
for (int i = 0; i < pts.length; ++i) {
|
protected void doMerge(Mapper mergeWith) {
|
||||||
indexFields(context, LatLonShape.createIndexableFields(name(), pts[i][1], pts[i][0]));
|
super.doMerge(mergeWith);
|
||||||
|
|
||||||
|
GeoShapeFieldMapper gsfm = (GeoShapeFieldMapper)mergeWith;
|
||||||
|
if (gsfm.coerce.explicit()) {
|
||||||
|
this.coerce = gsfm.coerce;
|
||||||
|
}
|
||||||
|
if (gsfm.ignoreMalformed.explicit()) {
|
||||||
|
this.ignoreMalformed = gsfm.ignoreMalformed;
|
||||||
|
}
|
||||||
|
if (gsfm.ignoreZValue.explicit()) {
|
||||||
|
this.ignoreZValue = gsfm.ignoreZValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
|
||||||
|
builder.field("type", contentType());
|
||||||
|
|
||||||
|
if (includeDefaults || fieldType().tree().equals(Defaults.TREE) == false) {
|
||||||
|
builder.field(Names.TREE, fieldType().tree());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldType().treeLevels() != 0) {
|
||||||
|
builder.field(Names.TREE_LEVELS, fieldType().treeLevels());
|
||||||
|
} else if(includeDefaults && fieldType().precisionInMeters() == -1) { // defaults only make sense if precision is not specified
|
||||||
|
if ("geohash".equals(fieldType().tree())) {
|
||||||
|
builder.field(Names.TREE_LEVELS, Defaults.GEOHASH_LEVELS);
|
||||||
|
} else if ("legacyquadtree".equals(fieldType().tree())) {
|
||||||
|
builder.field(Names.TREE_LEVELS, Defaults.QUADTREE_LEVELS);
|
||||||
|
} else if ("quadtree".equals(fieldType().tree())) {
|
||||||
|
builder.field(Names.TREE_LEVELS, Defaults.QUADTREE_LEVELS);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown prefix tree type [" + fieldType().tree() + "]");
|
||||||
}
|
}
|
||||||
} else if (luceneShape instanceof Line[]) {
|
}
|
||||||
Line[] lines = (Line[]) luceneShape;
|
if (fieldType().precisionInMeters() != -1) {
|
||||||
for (int i = 0; i < lines.length; ++i) {
|
builder.field(Names.TREE_PRESISION, DistanceUnit.METERS.toString(fieldType().precisionInMeters()));
|
||||||
indexFields(context, LatLonShape.createIndexableFields(name(), lines[i]));
|
} else if (includeDefaults && fieldType().treeLevels() == 0) { // defaults only make sense if tree levels are not specified
|
||||||
}
|
builder.field(Names.TREE_PRESISION, DistanceUnit.METERS.toString(50));
|
||||||
} else if (luceneShape instanceof Polygon[]) {
|
}
|
||||||
Polygon[] polys = (Polygon[]) luceneShape;
|
if (includeDefaults || fieldType().strategyName().equals(Defaults.STRATEGY) == false) {
|
||||||
for (int i = 0; i < polys.length; ++i) {
|
builder.field(Names.STRATEGY, fieldType().strategyName());
|
||||||
indexFields(context, LatLonShape.createIndexableFields(name(), polys[i]));
|
}
|
||||||
}
|
if (includeDefaults || fieldType().distanceErrorPct() != fieldType().defaultDistanceErrorPct) {
|
||||||
} else if (luceneShape instanceof Rectangle) {
|
builder.field(Names.DISTANCE_ERROR_PCT, fieldType().distanceErrorPct());
|
||||||
// index rectangle as a polygon
|
}
|
||||||
Rectangle r = (Rectangle) luceneShape;
|
if (includeDefaults || fieldType().orientation() != Defaults.ORIENTATION) {
|
||||||
Polygon p = new Polygon(new double[]{r.minLat, r.minLat, r.maxLat, r.maxLat, r.minLat},
|
builder.field(Names.ORIENTATION, fieldType().orientation());
|
||||||
new double[]{r.minLon, r.maxLon, r.maxLon, r.minLon, r.minLon});
|
}
|
||||||
indexFields(context, LatLonShape.createIndexableFields(name(), p));
|
if (fieldType().strategyName().equals(SpatialStrategy.TERM.getStrategyName())) {
|
||||||
} else if (luceneShape instanceof Object[]) {
|
// For TERMs strategy the defaults for points only change to true
|
||||||
// recurse to index geometry collection
|
if (includeDefaults || fieldType().pointsOnly() != true) {
|
||||||
for (Object o : (Object[])luceneShape) {
|
builder.field(Names.STRATEGY_POINTS_ONLY, fieldType().pointsOnly());
|
||||||
indexShape(context, o);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("invalid shape type found [" + luceneShape.getClass() + "] while indexing shape");
|
if (includeDefaults || fieldType().pointsOnly() != GeoShapeFieldMapper.Defaults.POINTS_ONLY) {
|
||||||
|
builder.field(Names.STRATEGY_POINTS_ONLY, fieldType().pointsOnly());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (includeDefaults || coerce.explicit()) {
|
||||||
|
builder.field(Names.COERCE, coerce.value());
|
||||||
|
}
|
||||||
|
if (includeDefaults || ignoreMalformed.explicit()) {
|
||||||
|
builder.field(IGNORE_MALFORMED, ignoreMalformed.value());
|
||||||
|
}
|
||||||
|
if (includeDefaults || ignoreZValue.explicit()) {
|
||||||
|
builder.field(GeoPointFieldMapper.Names.IGNORE_Z_VALUE.getPreferredName(), ignoreZValue.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void indexFields(ParseContext context, Field[] fields) {
|
public Explicit<Boolean> coerce() {
|
||||||
ArrayList<IndexableField> flist = new ArrayList<>(Arrays.asList(fields));
|
return coerce;
|
||||||
createFieldNamesField(context, flist);
|
}
|
||||||
for (IndexableField f : flist) {
|
|
||||||
context.doc().add(f);
|
public Explicit<Boolean> ignoreMalformed() {
|
||||||
}
|
return ignoreMalformed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Explicit<Boolean> ignoreZValue() {
|
||||||
|
return ignoreZValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String contentType() {
|
||||||
|
return CONTENT_TYPE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,596 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch under one or more contributor
|
|
||||||
* license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright
|
|
||||||
* ownership. Elasticsearch licenses this file to you under
|
|
||||||
* the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
* not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.index.mapper;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.apache.lucene.index.IndexOptions;
|
|
||||||
import org.apache.lucene.index.IndexableField;
|
|
||||||
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
|
||||||
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
|
|
||||||
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
|
|
||||||
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
|
||||||
import org.apache.lucene.spatial.prefix.tree.PackedQuadPrefixTree;
|
|
||||||
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
|
|
||||||
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
|
|
||||||
import org.elasticsearch.ElasticsearchParseException;
|
|
||||||
import org.elasticsearch.common.Explicit;
|
|
||||||
import org.elasticsearch.common.ParseField;
|
|
||||||
import org.elasticsearch.common.geo.GeoUtils;
|
|
||||||
import org.elasticsearch.common.geo.ShapesAvailability;
|
|
||||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
|
||||||
import org.elasticsearch.common.geo.XShapeCollection;
|
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder.Orientation;
|
|
||||||
import org.elasticsearch.common.geo.parsers.ShapeParser;
|
|
||||||
import org.elasticsearch.common.logging.DeprecationLogger;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.unit.DistanceUnit;
|
|
||||||
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.support.XContentMapValues;
|
|
||||||
import org.locationtech.spatial4j.shape.Point;
|
|
||||||
import org.locationtech.spatial4j.shape.Shape;
|
|
||||||
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FieldMapper for indexing {@link org.locationtech.spatial4j.shape.Shape}s.
|
|
||||||
* <p>
|
|
||||||
* Currently Shapes can only be indexed and can only be queried using
|
|
||||||
* {@link org.elasticsearch.index.query.GeoShapeQueryBuilder}, consequently
|
|
||||||
* a lot of behavior in this Mapper is disabled.
|
|
||||||
* <p>
|
|
||||||
* Format supported:
|
|
||||||
* <p>
|
|
||||||
* "field" : {
|
|
||||||
* "type" : "polygon",
|
|
||||||
* "coordinates" : [
|
|
||||||
* [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ]
|
|
||||||
* ]
|
|
||||||
* }
|
|
||||||
* <p>
|
|
||||||
* or:
|
|
||||||
* <p>
|
|
||||||
* "field" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))
|
|
||||||
*
|
|
||||||
* @deprecated use {@link GeoShapeFieldMapper}
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public class LegacyGeoShapeFieldMapper extends BaseGeoShapeFieldMapper {
|
|
||||||
|
|
||||||
public static final String CONTENT_TYPE = "geo_shape";
|
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
public static class DeprecatedParameters {
|
|
||||||
public static class Names {
|
|
||||||
public static final ParseField STRATEGY = new ParseField("strategy");
|
|
||||||
public static final ParseField TREE = new ParseField("tree");
|
|
||||||
public static final ParseField TREE_LEVELS = new ParseField("tree_levels");
|
|
||||||
public static final ParseField PRECISION = new ParseField("precision");
|
|
||||||
public static final ParseField DISTANCE_ERROR_PCT = new ParseField("distance_error_pct");
|
|
||||||
public static final ParseField POINTS_ONLY = new ParseField("points_only");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PrefixTrees {
|
|
||||||
public static final String LEGACY_QUADTREE = "legacyquadtree";
|
|
||||||
public static final String QUADTREE = "quadtree";
|
|
||||||
public static final String GEOHASH = "geohash";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Defaults {
|
|
||||||
public static final SpatialStrategy STRATEGY = SpatialStrategy.RECURSIVE;
|
|
||||||
public static final String TREE = "quadtree";
|
|
||||||
public static final String PRECISION = "50m";
|
|
||||||
public static final int QUADTREE_LEVELS = GeoUtils.quadTreeLevelsForPrecision(PRECISION);
|
|
||||||
public static final int GEOHASH_TREE_LEVELS = GeoUtils.geoHashLevelsForPrecision(PRECISION);
|
|
||||||
public static final boolean POINTS_ONLY = false;
|
|
||||||
public static final double DISTANCE_ERROR_PCT = 0.025d;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpatialStrategy strategy = null;
|
|
||||||
public String tree = null;
|
|
||||||
public int treeLevels = Integer.MIN_VALUE;
|
|
||||||
public String precision = null;
|
|
||||||
public Boolean pointsOnly = null;
|
|
||||||
public double distanceErrorPct = Double.NaN;
|
|
||||||
|
|
||||||
public void setSpatialStrategy(SpatialStrategy strategy) {
|
|
||||||
this.strategy = strategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTree(String prefixTree) {
|
|
||||||
this.tree = prefixTree;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTreeLevels(int treeLevels) {
|
|
||||||
this.treeLevels = treeLevels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrecision(String precision) {
|
|
||||||
this.precision = precision;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPointsOnly(boolean pointsOnly) {
|
|
||||||
if (this.strategy == SpatialStrategy.TERM && pointsOnly == false) {
|
|
||||||
throw new ElasticsearchParseException("points_only cannot be set to false for term strategy");
|
|
||||||
}
|
|
||||||
this.pointsOnly = pointsOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDistanceErrorPct(double distanceErrorPct) {
|
|
||||||
this.distanceErrorPct = distanceErrorPct;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setup() {
|
|
||||||
if (strategy == null) {
|
|
||||||
strategy = Defaults.STRATEGY;
|
|
||||||
}
|
|
||||||
if (tree == null) {
|
|
||||||
tree = Defaults.TREE;
|
|
||||||
}
|
|
||||||
if (Double.isNaN(distanceErrorPct)) {
|
|
||||||
if (precision != null || treeLevels != Integer.MIN_VALUE) {
|
|
||||||
distanceErrorPct = 0d;
|
|
||||||
} else {
|
|
||||||
distanceErrorPct = Defaults.DISTANCE_ERROR_PCT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (treeLevels == Integer.MIN_VALUE && precision == null) {
|
|
||||||
// set default precision if treeLevels is not explicitly set
|
|
||||||
precision = Defaults.PRECISION;
|
|
||||||
}
|
|
||||||
if (treeLevels == Integer.MIN_VALUE) {
|
|
||||||
if (precision.equals(Defaults.PRECISION)) {
|
|
||||||
treeLevels = tree.equals(Defaults.TREE)
|
|
||||||
? Defaults.QUADTREE_LEVELS
|
|
||||||
: Defaults.GEOHASH_TREE_LEVELS;
|
|
||||||
} else {
|
|
||||||
treeLevels = tree == Defaults.TREE
|
|
||||||
? GeoUtils.quadTreeLevelsForPrecision(precision)
|
|
||||||
: GeoUtils.geoHashLevelsForPrecision(precision);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pointsOnly == null) {
|
|
||||||
if (strategy == SpatialStrategy.TERM) {
|
|
||||||
pointsOnly = true;
|
|
||||||
} else {
|
|
||||||
pointsOnly = Defaults.POINTS_ONLY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean parse(String name, String fieldName, Object fieldNode, DeprecatedParameters deprecatedParameters) {
|
|
||||||
if (Names.STRATEGY.match(fieldName, LoggingDeprecationHandler.INSTANCE)) {
|
|
||||||
checkPrefixTreeSupport(fieldName);
|
|
||||||
deprecatedParameters.setSpatialStrategy(SpatialStrategy.fromString(fieldNode.toString()));
|
|
||||||
} else if (Names.TREE.match(fieldName, LoggingDeprecationHandler.INSTANCE)) {
|
|
||||||
checkPrefixTreeSupport(fieldName);
|
|
||||||
deprecatedParameters.setTree(fieldNode.toString());
|
|
||||||
} else if (Names.TREE_LEVELS.match(fieldName, LoggingDeprecationHandler.INSTANCE)) {
|
|
||||||
checkPrefixTreeSupport(fieldName);
|
|
||||||
deprecatedParameters.setTreeLevels(Integer.parseInt(fieldNode.toString()));
|
|
||||||
} else if (Names.PRECISION.match(fieldName, LoggingDeprecationHandler.INSTANCE)) {
|
|
||||||
checkPrefixTreeSupport(fieldName);
|
|
||||||
deprecatedParameters.setPrecision(fieldNode.toString());
|
|
||||||
} else if (Names.DISTANCE_ERROR_PCT.match(fieldName, LoggingDeprecationHandler.INSTANCE)) {
|
|
||||||
checkPrefixTreeSupport(fieldName);
|
|
||||||
deprecatedParameters.setDistanceErrorPct(Double.parseDouble(fieldNode.toString()));
|
|
||||||
} else if (Names.POINTS_ONLY.match(fieldName, LoggingDeprecationHandler.INSTANCE)) {
|
|
||||||
checkPrefixTreeSupport(fieldName);
|
|
||||||
deprecatedParameters.setPointsOnly(
|
|
||||||
XContentMapValues.nodeBooleanValue(fieldNode, name + "." + DeprecatedParameters.Names.POINTS_ONLY));
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkPrefixTreeSupport(String fieldName) {
|
|
||||||
if (ShapesAvailability.JTS_AVAILABLE == false || ShapesAvailability.SPATIAL4J_AVAILABLE == false) {
|
|
||||||
throw new ElasticsearchParseException("Field parameter [{}] is not supported for [{}] field type",
|
|
||||||
fieldName, CONTENT_TYPE);
|
|
||||||
}
|
|
||||||
DEPRECATION_LOGGER.deprecated("Field parameter [{}] is deprecated and will be removed in a future version.",
|
|
||||||
fieldName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Logger logger = LogManager.getLogger(LegacyGeoShapeFieldMapper.class);
|
|
||||||
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(logger);
|
|
||||||
|
|
||||||
public static class Builder extends BaseGeoShapeFieldMapper.Builder<BaseGeoShapeFieldMapper.Builder, LegacyGeoShapeFieldMapper> {
|
|
||||||
|
|
||||||
DeprecatedParameters deprecatedParameters;
|
|
||||||
|
|
||||||
public Builder(String name) {
|
|
||||||
super(name, new GeoShapeFieldType(), new GeoShapeFieldType());
|
|
||||||
this.deprecatedParameters = new DeprecatedParameters();
|
|
||||||
this.deprecatedParameters.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder(String name, boolean coerce, boolean ignoreMalformed, Orientation orientation,
|
|
||||||
boolean ignoreZ, DeprecatedParameters deprecatedParameters) {
|
|
||||||
super(name, new GeoShapeFieldType(), new GeoShapeFieldType(), coerce, ignoreMalformed, orientation, ignoreZ);
|
|
||||||
this.deprecatedParameters = deprecatedParameters;
|
|
||||||
this.deprecatedParameters.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public GeoShapeFieldType fieldType() {
|
|
||||||
return (GeoShapeFieldType)fieldType;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupFieldTypeDeprecatedParameters() {
|
|
||||||
GeoShapeFieldType ft = fieldType();
|
|
||||||
ft.setStrategy(deprecatedParameters.strategy);
|
|
||||||
ft.setTree(deprecatedParameters.tree);
|
|
||||||
ft.setTreeLevels(deprecatedParameters.treeLevels);
|
|
||||||
if (deprecatedParameters.precision != null) {
|
|
||||||
// precision is only set iff: a. treeLevel is not explicitly set, b. its explicitly set
|
|
||||||
ft.setPrecisionInMeters(DistanceUnit.parse(deprecatedParameters.precision,
|
|
||||||
DistanceUnit.DEFAULT, DistanceUnit.DEFAULT));
|
|
||||||
}
|
|
||||||
ft.setDistanceErrorPct(deprecatedParameters.distanceErrorPct);
|
|
||||||
ft.setPointsOnly(deprecatedParameters.pointsOnly);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupPrefixTrees() {
|
|
||||||
GeoShapeFieldType ft = fieldType();
|
|
||||||
SpatialPrefixTree prefixTree;
|
|
||||||
if (ft.tree().equals(DeprecatedParameters.PrefixTrees.GEOHASH)) {
|
|
||||||
prefixTree = new GeohashPrefixTree(ShapeBuilder.SPATIAL_CONTEXT,
|
|
||||||
getLevels(ft.treeLevels(), ft.precisionInMeters(), DeprecatedParameters.Defaults.GEOHASH_TREE_LEVELS, true));
|
|
||||||
} else if (ft.tree().equals(DeprecatedParameters.PrefixTrees.LEGACY_QUADTREE)) {
|
|
||||||
prefixTree = new QuadPrefixTree(ShapeBuilder.SPATIAL_CONTEXT,
|
|
||||||
getLevels(ft.treeLevels(), ft.precisionInMeters(), DeprecatedParameters.Defaults.QUADTREE_LEVELS, false));
|
|
||||||
} else if (ft.tree().equals(DeprecatedParameters.PrefixTrees.QUADTREE)) {
|
|
||||||
prefixTree = new PackedQuadPrefixTree(ShapeBuilder.SPATIAL_CONTEXT,
|
|
||||||
getLevels(ft.treeLevels(), ft.precisionInMeters(), DeprecatedParameters.Defaults.QUADTREE_LEVELS, false));
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Unknown prefix tree type [" + ft.tree() + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup prefix trees regardless of strategy (this is used for the QueryBuilder)
|
|
||||||
// recursive:
|
|
||||||
RecursivePrefixTreeStrategy rpts = new RecursivePrefixTreeStrategy(prefixTree, ft.name());
|
|
||||||
rpts.setDistErrPct(ft.distanceErrorPct());
|
|
||||||
rpts.setPruneLeafyBranches(false);
|
|
||||||
ft.recursiveStrategy = rpts;
|
|
||||||
|
|
||||||
// term:
|
|
||||||
TermQueryPrefixTreeStrategy termStrategy = new TermQueryPrefixTreeStrategy(prefixTree, ft.name());
|
|
||||||
termStrategy.setDistErrPct(ft.distanceErrorPct());
|
|
||||||
ft.termStrategy = termStrategy;
|
|
||||||
|
|
||||||
// set default (based on strategy):
|
|
||||||
ft.defaultPrefixTreeStrategy = ft.resolvePrefixTreeStrategy(ft.strategy());
|
|
||||||
ft.defaultPrefixTreeStrategy.setPointsOnly(ft.pointsOnly());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setupFieldType(BuilderContext context) {
|
|
||||||
super.setupFieldType(context);
|
|
||||||
|
|
||||||
// field mapper handles this at build time
|
|
||||||
// but prefix tree strategies require a name, so throw a similar exception
|
|
||||||
if (fieldType().name().isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("name cannot be empty string");
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup the deprecated parameters and the prefix tree configuration
|
|
||||||
setupFieldTypeDeprecatedParameters();
|
|
||||||
setupPrefixTrees();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getLevels(int treeLevels, double precisionInMeters, int defaultLevels, boolean geoHash) {
|
|
||||||
if (treeLevels > 0 || precisionInMeters >= 0) {
|
|
||||||
return Math.max(treeLevels, precisionInMeters >= 0 ? (geoHash ? GeoUtils.geoHashLevelsForPrecision(precisionInMeters)
|
|
||||||
: GeoUtils.quadTreeLevelsForPrecision(precisionInMeters)) : 0);
|
|
||||||
}
|
|
||||||
return defaultLevels;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LegacyGeoShapeFieldMapper build(BuilderContext context) {
|
|
||||||
setupFieldType(context);
|
|
||||||
|
|
||||||
return new LegacyGeoShapeFieldMapper(name, fieldType, defaultFieldType, ignoreMalformed(context),
|
|
||||||
coerce(context), orientation(), ignoreZValue(), context.indexSettings(),
|
|
||||||
multiFieldsBuilder.build(this, context), copyTo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class GeoShapeFieldType extends BaseGeoShapeFieldType {
|
|
||||||
|
|
||||||
private String tree = DeprecatedParameters.Defaults.TREE;
|
|
||||||
private SpatialStrategy strategy = DeprecatedParameters.Defaults.STRATEGY;
|
|
||||||
private boolean pointsOnly = DeprecatedParameters.Defaults.POINTS_ONLY;
|
|
||||||
private int treeLevels = 0;
|
|
||||||
private double precisionInMeters = -1;
|
|
||||||
private Double distanceErrorPct;
|
|
||||||
private double defaultDistanceErrorPct = 0.0;
|
|
||||||
|
|
||||||
// these are built when the field type is frozen
|
|
||||||
private PrefixTreeStrategy defaultPrefixTreeStrategy;
|
|
||||||
private RecursivePrefixTreeStrategy recursiveStrategy;
|
|
||||||
private TermQueryPrefixTreeStrategy termStrategy;
|
|
||||||
|
|
||||||
public GeoShapeFieldType() {
|
|
||||||
setIndexOptions(IndexOptions.DOCS);
|
|
||||||
setTokenized(false);
|
|
||||||
setStored(false);
|
|
||||||
setStoreTermVectors(false);
|
|
||||||
setOmitNorms(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected GeoShapeFieldType(GeoShapeFieldType ref) {
|
|
||||||
super(ref);
|
|
||||||
this.tree = ref.tree;
|
|
||||||
this.strategy = ref.strategy;
|
|
||||||
this.pointsOnly = ref.pointsOnly;
|
|
||||||
this.treeLevels = ref.treeLevels;
|
|
||||||
this.precisionInMeters = ref.precisionInMeters;
|
|
||||||
this.distanceErrorPct = ref.distanceErrorPct;
|
|
||||||
this.defaultDistanceErrorPct = ref.defaultDistanceErrorPct;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public GeoShapeFieldType clone() {
|
|
||||||
return new GeoShapeFieldType(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (!super.equals(o)) return false;
|
|
||||||
GeoShapeFieldType that = (GeoShapeFieldType) o;
|
|
||||||
return treeLevels == that.treeLevels &&
|
|
||||||
precisionInMeters == that.precisionInMeters &&
|
|
||||||
defaultDistanceErrorPct == that.defaultDistanceErrorPct &&
|
|
||||||
Objects.equals(tree, that.tree) &&
|
|
||||||
Objects.equals(strategy, that.strategy) &&
|
|
||||||
pointsOnly == that.pointsOnly &&
|
|
||||||
Objects.equals(distanceErrorPct, that.distanceErrorPct);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(super.hashCode(), tree, strategy, pointsOnly, treeLevels, precisionInMeters, distanceErrorPct,
|
|
||||||
defaultDistanceErrorPct);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkCompatibility(MappedFieldType fieldType, List<String> conflicts) {
|
|
||||||
super.checkCompatibility(fieldType, conflicts);
|
|
||||||
GeoShapeFieldType other = (GeoShapeFieldType)fieldType;
|
|
||||||
// prevent user from changing strategies
|
|
||||||
if (strategy() != other.strategy()) {
|
|
||||||
conflicts.add("mapper [" + name() + "] has different [strategy]");
|
|
||||||
}
|
|
||||||
|
|
||||||
// prevent user from changing trees (changes encoding)
|
|
||||||
if (tree().equals(other.tree()) == false) {
|
|
||||||
conflicts.add("mapper [" + name() + "] has different [tree]");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((pointsOnly() != other.pointsOnly())) {
|
|
||||||
conflicts.add("mapper [" + name() + "] has different points_only");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO we should allow this, but at the moment levels is used to build bookkeeping variables
|
|
||||||
// in lucene's SpatialPrefixTree implementations, need a patch to correct that first
|
|
||||||
if (treeLevels() != other.treeLevels()) {
|
|
||||||
conflicts.add("mapper [" + name() + "] has different [tree_levels]");
|
|
||||||
}
|
|
||||||
if (precisionInMeters() != other.precisionInMeters()) {
|
|
||||||
conflicts.add("mapper [" + name() + "] has different [precision]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String tree() {
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTree(String tree) {
|
|
||||||
checkIfFrozen();
|
|
||||||
this.tree = tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpatialStrategy strategy() {
|
|
||||||
return strategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStrategy(SpatialStrategy strategy) {
|
|
||||||
checkIfFrozen();
|
|
||||||
this.strategy = strategy;
|
|
||||||
if (this.strategy.equals(SpatialStrategy.TERM)) {
|
|
||||||
this.pointsOnly = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean pointsOnly() {
|
|
||||||
return pointsOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPointsOnly(boolean pointsOnly) {
|
|
||||||
checkIfFrozen();
|
|
||||||
this.pointsOnly = pointsOnly;
|
|
||||||
}
|
|
||||||
public int treeLevels() {
|
|
||||||
return treeLevels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTreeLevels(int treeLevels) {
|
|
||||||
checkIfFrozen();
|
|
||||||
this.treeLevels = treeLevels;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double precisionInMeters() {
|
|
||||||
return precisionInMeters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPrecisionInMeters(double precisionInMeters) {
|
|
||||||
checkIfFrozen();
|
|
||||||
this.precisionInMeters = precisionInMeters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double distanceErrorPct() {
|
|
||||||
return distanceErrorPct == null ? defaultDistanceErrorPct : distanceErrorPct;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDistanceErrorPct(double distanceErrorPct) {
|
|
||||||
checkIfFrozen();
|
|
||||||
this.distanceErrorPct = distanceErrorPct;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDefaultDistanceErrorPct(double defaultDistanceErrorPct) {
|
|
||||||
checkIfFrozen();
|
|
||||||
this.defaultDistanceErrorPct = defaultDistanceErrorPct;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrefixTreeStrategy defaultPrefixTreeStrategy() {
|
|
||||||
return this.defaultPrefixTreeStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrefixTreeStrategy resolvePrefixTreeStrategy(SpatialStrategy strategy) {
|
|
||||||
return resolvePrefixTreeStrategy(strategy.getStrategyName());
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrefixTreeStrategy resolvePrefixTreeStrategy(String strategyName) {
|
|
||||||
if (SpatialStrategy.RECURSIVE.getStrategyName().equals(strategyName)) {
|
|
||||||
return recursiveStrategy;
|
|
||||||
}
|
|
||||||
if (SpatialStrategy.TERM.getStrategyName().equals(strategyName)) {
|
|
||||||
return termStrategy;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Unknown prefix tree strategy [" + strategyName + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public LegacyGeoShapeFieldMapper(String simpleName, MappedFieldType fieldType, MappedFieldType defaultFieldType,
|
|
||||||
Explicit<Boolean> ignoreMalformed, Explicit<Boolean> coerce, Explicit<Orientation> orientation,
|
|
||||||
Explicit<Boolean> ignoreZValue, Settings indexSettings,
|
|
||||||
MultiFields multiFields, CopyTo copyTo) {
|
|
||||||
super(simpleName, fieldType, defaultFieldType, ignoreMalformed, coerce, ignoreZValue, indexSettings,
|
|
||||||
multiFields, copyTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public GeoShapeFieldType fieldType() {
|
|
||||||
return (GeoShapeFieldType) super.fieldType();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void parse(ParseContext context) throws IOException {
|
|
||||||
try {
|
|
||||||
Shape shape = context.parseExternalValue(Shape.class);
|
|
||||||
if (shape == null) {
|
|
||||||
ShapeBuilder shapeBuilder = ShapeParser.parse(context.parser(), this);
|
|
||||||
if (shapeBuilder == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
shape = shapeBuilder.buildS4J();
|
|
||||||
}
|
|
||||||
if (fieldType().pointsOnly() == true) {
|
|
||||||
// index configured for pointsOnly
|
|
||||||
if (shape instanceof XShapeCollection && XShapeCollection.class.cast(shape).pointsOnly()) {
|
|
||||||
// MULTIPOINT data: index each point separately
|
|
||||||
List<Shape> shapes = ((XShapeCollection) shape).getShapes();
|
|
||||||
for (Shape s : shapes) {
|
|
||||||
indexShape(context, s);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else if (shape instanceof Point == false) {
|
|
||||||
throw new MapperParsingException("[{" + fieldType().name() + "}] is configured for points only but a "
|
|
||||||
+ ((shape instanceof JtsGeometry) ? ((JtsGeometry)shape).getGeom().getGeometryType() : shape.getClass())
|
|
||||||
+ " was found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
indexShape(context, shape);
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (ignoreMalformed.value() == false) {
|
|
||||||
throw new MapperParsingException("failed to parse field [{}] of type [{}]", e, fieldType().name(),
|
|
||||||
fieldType().typeName());
|
|
||||||
}
|
|
||||||
context.addIgnoredField(fieldType.name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void indexShape(ParseContext context, Shape shape) {
|
|
||||||
List<IndexableField> fields = new ArrayList<>(Arrays.asList(fieldType().defaultPrefixTreeStrategy().createIndexableFields(shape)));
|
|
||||||
createFieldNamesField(context, fields);
|
|
||||||
for (IndexableField field : fields) {
|
|
||||||
context.doc().add(field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doXContentBody(XContentBuilder builder, boolean includeDefaults, Params params) throws IOException {
|
|
||||||
super.doXContentBody(builder, includeDefaults, params);
|
|
||||||
|
|
||||||
if (includeDefaults || fieldType().tree().equals(DeprecatedParameters.Defaults.TREE) == false) {
|
|
||||||
builder.field(DeprecatedParameters.Names.TREE.getPreferredName(), fieldType().tree());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldType().treeLevels() != 0) {
|
|
||||||
builder.field(DeprecatedParameters.Names.TREE_LEVELS.getPreferredName(), fieldType().treeLevels());
|
|
||||||
} else if(includeDefaults && fieldType().precisionInMeters() == -1) { // defaults only make sense if precision is not specified
|
|
||||||
if (DeprecatedParameters.PrefixTrees.GEOHASH.equals(fieldType().tree())) {
|
|
||||||
builder.field(DeprecatedParameters.Names.TREE_LEVELS.getPreferredName(),
|
|
||||||
DeprecatedParameters.Defaults.GEOHASH_TREE_LEVELS);
|
|
||||||
} else if (DeprecatedParameters.PrefixTrees.LEGACY_QUADTREE.equals(fieldType().tree())) {
|
|
||||||
builder.field(DeprecatedParameters.Names.TREE_LEVELS.getPreferredName(),
|
|
||||||
DeprecatedParameters.Defaults.QUADTREE_LEVELS);
|
|
||||||
} else if (DeprecatedParameters.PrefixTrees.QUADTREE.equals(fieldType().tree())) {
|
|
||||||
builder.field(DeprecatedParameters.Names.TREE_LEVELS.getPreferredName(),
|
|
||||||
DeprecatedParameters.Defaults.QUADTREE_LEVELS);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Unknown prefix tree type [" + fieldType().tree() + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fieldType().precisionInMeters() != -1) {
|
|
||||||
builder.field(DeprecatedParameters.Names.PRECISION.getPreferredName(),
|
|
||||||
DistanceUnit.METERS.toString(fieldType().precisionInMeters()));
|
|
||||||
} else if (includeDefaults && fieldType().treeLevels() == 0) { // defaults only make sense if tree levels are not specified
|
|
||||||
builder.field(DeprecatedParameters.Names.PRECISION.getPreferredName(),
|
|
||||||
DistanceUnit.METERS.toString(50));
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.field(DeprecatedParameters.Names.STRATEGY.getPreferredName(), fieldType().strategy().getStrategyName());
|
|
||||||
|
|
||||||
if (includeDefaults || fieldType().distanceErrorPct() != fieldType().defaultDistanceErrorPct) {
|
|
||||||
builder.field(DeprecatedParameters.Names.DISTANCE_ERROR_PCT.getPreferredName(), fieldType().distanceErrorPct());
|
|
||||||
}
|
|
||||||
if (fieldType().strategy() == SpatialStrategy.TERM) {
|
|
||||||
// For TERMs strategy the defaults for points only change to true
|
|
||||||
if (includeDefaults || fieldType().pointsOnly() != true) {
|
|
||||||
builder.field(DeprecatedParameters.Names.POINTS_ONLY.getPreferredName(), fieldType().pointsOnly());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (includeDefaults || fieldType().pointsOnly() != DeprecatedParameters.Defaults.POINTS_ONLY) {
|
|
||||||
builder.field(DeprecatedParameters.Names.POINTS_ONLY.getPreferredName(), fieldType().pointsOnly());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,10 +19,6 @@
|
||||||
|
|
||||||
package org.elasticsearch.index.query;
|
package org.elasticsearch.index.query;
|
||||||
|
|
||||||
import org.apache.lucene.document.LatLonShape;
|
|
||||||
import org.apache.lucene.geo.Line;
|
|
||||||
import org.apache.lucene.geo.Polygon;
|
|
||||||
import org.apache.lucene.geo.Rectangle;
|
|
||||||
import org.apache.lucene.search.BooleanClause;
|
import org.apache.lucene.search.BooleanClause;
|
||||||
import org.apache.lucene.search.BooleanQuery;
|
import org.apache.lucene.search.BooleanQuery;
|
||||||
import org.apache.lucene.search.ConstantScoreQuery;
|
import org.apache.lucene.search.ConstantScoreQuery;
|
||||||
|
@ -40,9 +36,8 @@ import org.elasticsearch.action.get.GetResponse;
|
||||||
import org.elasticsearch.client.Client;
|
import org.elasticsearch.client.Client;
|
||||||
import org.elasticsearch.common.ParseField;
|
import org.elasticsearch.common.ParseField;
|
||||||
import org.elasticsearch.common.ParsingException;
|
import org.elasticsearch.common.ParsingException;
|
||||||
import org.elasticsearch.common.geo.GeoPoint;
|
|
||||||
import org.elasticsearch.common.geo.GeoShapeType;
|
|
||||||
import org.elasticsearch.common.geo.ShapeRelation;
|
import org.elasticsearch.common.geo.ShapeRelation;
|
||||||
|
import org.elasticsearch.common.geo.ShapesAvailability;
|
||||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.common.geo.parsers.ShapeParser;
|
import org.elasticsearch.common.geo.parsers.ShapeParser;
|
||||||
|
@ -53,8 +48,7 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
|
||||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
import org.elasticsearch.index.mapper.MappedFieldType;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -335,9 +329,9 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
|
||||||
if (relation == null) {
|
if (relation == null) {
|
||||||
throw new IllegalArgumentException("No Shape Relation defined");
|
throw new IllegalArgumentException("No Shape Relation defined");
|
||||||
}
|
}
|
||||||
if (SpatialStrategy.TERM.equals(strategy) && relation != ShapeRelation.INTERSECTS) {
|
if (strategy != null && strategy == SpatialStrategy.TERM && relation != ShapeRelation.INTERSECTS) {
|
||||||
throw new IllegalArgumentException("current strategy [" + strategy.getStrategyName() + "] only supports relation ["
|
throw new IllegalArgumentException("current strategy [" + strategy.getStrategyName() + "] only supports relation ["
|
||||||
+ ShapeRelation.INTERSECTS.getRelationName() + "] found relation [" + relation.getRelationName() + "]");
|
+ ShapeRelation.INTERSECTS.getRelationName() + "] found relation [" + relation.getRelationName() + "]");
|
||||||
}
|
}
|
||||||
this.relation = relation;
|
this.relation = relation;
|
||||||
return this;
|
return this;
|
||||||
|
@ -382,98 +376,34 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
|
||||||
} else {
|
} else {
|
||||||
throw new QueryShardException(context, "failed to find geo_shape field [" + fieldName + "]");
|
throw new QueryShardException(context, "failed to find geo_shape field [" + fieldName + "]");
|
||||||
}
|
}
|
||||||
} else if (fieldType.typeName().equals(BaseGeoShapeFieldMapper.CONTENT_TYPE) == false) {
|
} else if (fieldType.typeName().equals(GeoShapeFieldMapper.CONTENT_TYPE) == false) {
|
||||||
throw new QueryShardException(context,
|
throw new QueryShardException(context,
|
||||||
"Field [" + fieldName + "] is not of type [geo_shape] but of type [" + fieldType.typeName() + "]");
|
"Field [" + fieldName + "] is not of type [geo_shape] but of type [" + fieldType.typeName() + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
final BaseGeoShapeFieldMapper.BaseGeoShapeFieldType ft = (BaseGeoShapeFieldMapper.BaseGeoShapeFieldType) fieldType;
|
final GeoShapeFieldMapper.GeoShapeFieldType shapeFieldType = (GeoShapeFieldMapper.GeoShapeFieldType) fieldType;
|
||||||
|
|
||||||
|
PrefixTreeStrategy strategy = shapeFieldType.defaultStrategy();
|
||||||
|
if (this.strategy != null) {
|
||||||
|
strategy = shapeFieldType.resolveStrategy(this.strategy);
|
||||||
|
}
|
||||||
Query query;
|
Query query;
|
||||||
if (strategy != null || ft instanceof LegacyGeoShapeFieldMapper.GeoShapeFieldType) {
|
if (strategy instanceof RecursivePrefixTreeStrategy && relation == ShapeRelation.DISJOINT) {
|
||||||
LegacyGeoShapeFieldMapper.GeoShapeFieldType shapeFieldType = (LegacyGeoShapeFieldMapper.GeoShapeFieldType) ft;
|
// this strategy doesn't support disjoint anymore: but it did
|
||||||
SpatialStrategy spatialStrategy = shapeFieldType.strategy();
|
// before, including creating lucene fieldcache (!)
|
||||||
if (this.strategy != null) {
|
// in this case, execute disjoint as exists && !intersects
|
||||||
spatialStrategy = this.strategy;
|
BooleanQuery.Builder bool = new BooleanQuery.Builder();
|
||||||
}
|
Query exists = ExistsQueryBuilder.newFilter(context, fieldName);
|
||||||
PrefixTreeStrategy prefixTreeStrategy = shapeFieldType.resolvePrefixTreeStrategy(spatialStrategy);
|
Query intersects = strategy.makeQuery(getArgs(shapeToQuery, ShapeRelation.INTERSECTS));
|
||||||
if (prefixTreeStrategy instanceof RecursivePrefixTreeStrategy && relation == ShapeRelation.DISJOINT) {
|
bool.add(exists, BooleanClause.Occur.MUST);
|
||||||
// this strategy doesn't support disjoint anymore: but it did
|
bool.add(intersects, BooleanClause.Occur.MUST_NOT);
|
||||||
// before, including creating lucene fieldcache (!)
|
query = new ConstantScoreQuery(bool.build());
|
||||||
// in this case, execute disjoint as exists && !intersects
|
|
||||||
BooleanQuery.Builder bool = new BooleanQuery.Builder();
|
|
||||||
Query exists = ExistsQueryBuilder.newFilter(context, fieldName);
|
|
||||||
Query intersects = prefixTreeStrategy.makeQuery(getArgs(shapeToQuery, ShapeRelation.INTERSECTS));
|
|
||||||
bool.add(exists, BooleanClause.Occur.MUST);
|
|
||||||
bool.add(intersects, BooleanClause.Occur.MUST_NOT);
|
|
||||||
query = new ConstantScoreQuery(bool.build());
|
|
||||||
} else {
|
|
||||||
query = new ConstantScoreQuery(prefixTreeStrategy.makeQuery(getArgs(shapeToQuery, relation)));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
query = new ConstantScoreQuery(getVectorQuery(context, shapeToQuery));
|
query = new ConstantScoreQuery(strategy.makeQuery(getArgs(shapeToQuery, relation)));
|
||||||
}
|
}
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Query getVectorQuery(QueryShardContext context, ShapeBuilder queryShapeBuilder) {
|
|
||||||
// CONTAINS queries are not yet supported by VECTOR strategy
|
|
||||||
if (relation == ShapeRelation.CONTAINS) {
|
|
||||||
throw new QueryShardException(context,
|
|
||||||
ShapeRelation.CONTAINS + " query relation not supported for Field [" + fieldName + "]");
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrap geoQuery as a ConstantScoreQuery
|
|
||||||
return getVectorQueryFromShape(context, queryShapeBuilder.buildLucene());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Query getVectorQueryFromShape(QueryShardContext context, Object queryShape) {
|
|
||||||
Query geoQuery;
|
|
||||||
if (queryShape instanceof Line[]) {
|
|
||||||
geoQuery = LatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), (Line[]) queryShape);
|
|
||||||
} else if (queryShape instanceof Polygon[]) {
|
|
||||||
geoQuery = LatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), (Polygon[]) queryShape);
|
|
||||||
} else if (queryShape instanceof Line) {
|
|
||||||
geoQuery = LatLonShape.newLineQuery(fieldName(), relation.getLuceneRelation(), (Line) queryShape);
|
|
||||||
} else if (queryShape instanceof Polygon) {
|
|
||||||
geoQuery = LatLonShape.newPolygonQuery(fieldName(), relation.getLuceneRelation(), (Polygon) queryShape);
|
|
||||||
} else if (queryShape instanceof Rectangle) {
|
|
||||||
Rectangle r = (Rectangle) queryShape;
|
|
||||||
geoQuery = LatLonShape.newBoxQuery(fieldName(), relation.getLuceneRelation(),
|
|
||||||
r.minLat, r.maxLat, r.minLon, r.maxLon);
|
|
||||||
} else if (queryShape instanceof double[][]) {
|
|
||||||
// note: we decompose point queries into a bounding box query with min values == max values
|
|
||||||
// to do this for multipoint we would have to create a BooleanQuery for each point
|
|
||||||
// this is *way* too costly. So we do not allow multipoint queries
|
|
||||||
throw new QueryShardException(context, "Field [" + fieldName + "] does not support " + GeoShapeType.MULTIPOINT + " queries");
|
|
||||||
} else if (queryShape instanceof double[] || queryShape instanceof GeoPoint) {
|
|
||||||
// for now just create a single bounding box query with min values == max values
|
|
||||||
double[] pt;
|
|
||||||
if (queryShape instanceof GeoPoint) {
|
|
||||||
pt = new double[] {((GeoPoint)queryShape).lon(), ((GeoPoint)queryShape).lat()};
|
|
||||||
} else {
|
|
||||||
pt = (double[])queryShape;
|
|
||||||
if (pt.length != 2) {
|
|
||||||
throw new QueryShardException(context, "Expected double array of length 2. "
|
|
||||||
+ "But found length " + pt.length + " for field [" + fieldName + "]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return LatLonShape.newBoxQuery(fieldName, relation.getLuceneRelation(), pt[1], pt[1], pt[0], pt[0]);
|
|
||||||
} else if (queryShape instanceof Object[]) {
|
|
||||||
geoQuery = createGeometryCollectionQuery(context, (Object[]) queryShape);
|
|
||||||
} else {
|
|
||||||
throw new QueryShardException(context, "Field [" + fieldName + "] found and unknown shape");
|
|
||||||
}
|
|
||||||
return geoQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Query createGeometryCollectionQuery(QueryShardContext context, Object... shapes) {
|
|
||||||
BooleanQuery.Builder bqb = new BooleanQuery.Builder();
|
|
||||||
for (Object shape : shapes) {
|
|
||||||
bqb.add(getVectorQueryFromShape(context, shape), BooleanClause.Occur.SHOULD);
|
|
||||||
}
|
|
||||||
return bqb.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the Shape with the given ID in the given type and index.
|
* Fetches the Shape with the given ID in the given type and index.
|
||||||
*
|
*
|
||||||
|
@ -484,6 +414,9 @@ public class GeoShapeQueryBuilder extends AbstractQueryBuilder<GeoShapeQueryBuil
|
||||||
* Shape itself is located
|
* Shape itself is located
|
||||||
*/
|
*/
|
||||||
private void fetch(Client client, GetRequest getRequest, String path, ActionListener<ShapeBuilder> listener) {
|
private void fetch(Client client, GetRequest getRequest, String path, ActionListener<ShapeBuilder> listener) {
|
||||||
|
if (ShapesAvailability.JTS_AVAILABLE == false) {
|
||||||
|
throw new IllegalStateException("JTS not available");
|
||||||
|
}
|
||||||
getRequest.preference("_local");
|
getRequest.preference("_local");
|
||||||
client.get(getRequest, new ActionListener<GetResponse>(){
|
client.get(getRequest, new ActionListener<GetResponse>(){
|
||||||
|
|
||||||
|
|
|
@ -25,13 +25,13 @@ import org.elasticsearch.action.admin.indices.rollover.MaxDocsCondition;
|
||||||
import org.elasticsearch.action.admin.indices.rollover.MaxSizeCondition;
|
import org.elasticsearch.action.admin.indices.rollover.MaxSizeCondition;
|
||||||
import org.elasticsearch.action.resync.TransportResyncReplicationAction;
|
import org.elasticsearch.action.resync.TransportResyncReplicationAction;
|
||||||
import org.elasticsearch.common.ParseField;
|
import org.elasticsearch.common.ParseField;
|
||||||
|
import org.elasticsearch.common.geo.ShapesAvailability;
|
||||||
import org.elasticsearch.common.inject.AbstractModule;
|
import org.elasticsearch.common.inject.AbstractModule;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||||
import org.elasticsearch.index.IndexSettings;
|
import org.elasticsearch.index.IndexSettings;
|
||||||
import org.elasticsearch.index.engine.EngineFactory;
|
import org.elasticsearch.index.engine.EngineFactory;
|
||||||
import org.elasticsearch.index.mapper.BaseGeoShapeFieldMapper;
|
|
||||||
import org.elasticsearch.index.mapper.BinaryFieldMapper;
|
import org.elasticsearch.index.mapper.BinaryFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.BooleanFieldMapper;
|
import org.elasticsearch.index.mapper.BooleanFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.CompletionFieldMapper;
|
import org.elasticsearch.index.mapper.CompletionFieldMapper;
|
||||||
|
@ -39,6 +39,7 @@ import org.elasticsearch.index.mapper.DateFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.FieldAliasMapper;
|
import org.elasticsearch.index.mapper.FieldAliasMapper;
|
||||||
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
|
import org.elasticsearch.index.mapper.FieldNamesFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
|
import org.elasticsearch.index.mapper.GeoPointFieldMapper;
|
||||||
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.IdFieldMapper;
|
import org.elasticsearch.index.mapper.IdFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.IgnoredFieldMapper;
|
import org.elasticsearch.index.mapper.IgnoredFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.IndexFieldMapper;
|
import org.elasticsearch.index.mapper.IndexFieldMapper;
|
||||||
|
@ -131,7 +132,10 @@ public class IndicesModule extends AbstractModule {
|
||||||
mappers.put(CompletionFieldMapper.CONTENT_TYPE, new CompletionFieldMapper.TypeParser());
|
mappers.put(CompletionFieldMapper.CONTENT_TYPE, new CompletionFieldMapper.TypeParser());
|
||||||
mappers.put(FieldAliasMapper.CONTENT_TYPE, new FieldAliasMapper.TypeParser());
|
mappers.put(FieldAliasMapper.CONTENT_TYPE, new FieldAliasMapper.TypeParser());
|
||||||
mappers.put(GeoPointFieldMapper.CONTENT_TYPE, new GeoPointFieldMapper.TypeParser());
|
mappers.put(GeoPointFieldMapper.CONTENT_TYPE, new GeoPointFieldMapper.TypeParser());
|
||||||
mappers.put(BaseGeoShapeFieldMapper.CONTENT_TYPE, new BaseGeoShapeFieldMapper.TypeParser());
|
|
||||||
|
if (ShapesAvailability.JTS_AVAILABLE && ShapesAvailability.SPATIAL4J_AVAILABLE) {
|
||||||
|
mappers.put(GeoShapeFieldMapper.CONTENT_TYPE, new GeoShapeFieldMapper.TypeParser());
|
||||||
|
}
|
||||||
|
|
||||||
for (MapperPlugin mapperPlugin : mapperPlugins) {
|
for (MapperPlugin mapperPlugin : mapperPlugins) {
|
||||||
for (Map.Entry<String, Mapper.TypeParser> entry : mapperPlugin.getMappers().entrySet()) {
|
for (Map.Entry<String, Mapper.TypeParser> entry : mapperPlugin.getMappers().entrySet()) {
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
import org.elasticsearch.index.mapper.ContentPath;
|
import org.elasticsearch.index.mapper.ContentPath;
|
||||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.Mapper;
|
import org.elasticsearch.index.mapper.Mapper;
|
||||||
import org.elasticsearch.test.VersionUtils;
|
import org.elasticsearch.test.VersionUtils;
|
||||||
import org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions;
|
import org.elasticsearch.test.hamcrest.ElasticsearchGeoAssertions;
|
||||||
|
@ -296,8 +296,7 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
|
||||||
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
|
LinearRing shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
|
||||||
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null);
|
Polygon expected = GEOMETRY_FACTORY.createPolygon(shell, null);
|
||||||
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
|
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
|
||||||
final LegacyGeoShapeFieldMapper mapperBuilder =
|
final GeoShapeFieldMapper mapperBuilder = new GeoShapeFieldMapper.Builder("test").ignoreZValue(true).build(mockBuilderContext);
|
||||||
(LegacyGeoShapeFieldMapper) (new LegacyGeoShapeFieldMapper.Builder("test").ignoreZValue(true).build(mockBuilderContext));
|
|
||||||
try (XContentParser parser = createParser(polygonGeoJson)) {
|
try (XContentParser parser = createParser(polygonGeoJson)) {
|
||||||
parser.nextToken();
|
parser.nextToken();
|
||||||
ElasticsearchGeoAssertions.assertEquals(jtsGeom(expected), ShapeParser.parse(parser, mapperBuilder).buildS4J());
|
ElasticsearchGeoAssertions.assertEquals(jtsGeom(expected), ShapeParser.parse(parser, mapperBuilder).buildS4J());
|
||||||
|
@ -897,6 +896,7 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
|
||||||
.startArray().value(101.0).value(1.0).endArray()
|
.startArray().value(101.0).value(1.0).endArray()
|
||||||
.endArray()
|
.endArray()
|
||||||
.endObject();
|
.endObject();
|
||||||
|
|
||||||
ShapeCollection<?> expected = shapeCollection(
|
ShapeCollection<?> expected = shapeCollection(
|
||||||
SPATIAL_CONTEXT.makePoint(100, 0),
|
SPATIAL_CONTEXT.makePoint(100, 0),
|
||||||
SPATIAL_CONTEXT.makePoint(101, 1.0));
|
SPATIAL_CONTEXT.makePoint(101, 1.0));
|
||||||
|
@ -968,6 +968,7 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
|
||||||
shellCoordinates.add(new Coordinate(102, 2));
|
shellCoordinates.add(new Coordinate(102, 2));
|
||||||
shellCoordinates.add(new Coordinate(102, 3));
|
shellCoordinates.add(new Coordinate(102, 3));
|
||||||
|
|
||||||
|
|
||||||
shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
|
shell = GEOMETRY_FACTORY.createLinearRing(shellCoordinates.toArray(new Coordinate[shellCoordinates.size()]));
|
||||||
Polygon withoutHoles = GEOMETRY_FACTORY.createPolygon(shell, null);
|
Polygon withoutHoles = GEOMETRY_FACTORY.createPolygon(shell, null);
|
||||||
|
|
||||||
|
@ -1148,6 +1149,7 @@ public class GeoJsonShapeParserTests extends BaseGeoParsingTestCase {
|
||||||
.startObject("nested").startArray("coordinates").value(200.0).value(0.0).endArray().endObject()
|
.startObject("nested").startArray("coordinates").value(200.0).value(0.0).endArray().endObject()
|
||||||
.startObject("lala").field("type", "NotAPoint").endObject()
|
.startObject("lala").field("type", "NotAPoint").endObject()
|
||||||
.endObject();
|
.endObject();
|
||||||
|
|
||||||
Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
|
Point expected = GEOMETRY_FACTORY.createPoint(new Coordinate(100.0, 0.0));
|
||||||
assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson, true);
|
assertGeometryEquals(new JtsPoint(expected, SPATIAL_CONTEXT), pointGeoJson, true);
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,6 @@ import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.index.mapper.ContentPath;
|
import org.elasticsearch.index.mapper.ContentPath;
|
||||||
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper;
|
||||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
|
||||||
import org.elasticsearch.index.mapper.Mapper;
|
import org.elasticsearch.index.mapper.Mapper;
|
||||||
import org.elasticsearch.test.geo.RandomShapeGenerator;
|
import org.elasticsearch.test.geo.RandomShapeGenerator;
|
||||||
import org.locationtech.jts.geom.Coordinate;
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
|
@ -147,6 +146,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
|
||||||
@Override
|
@Override
|
||||||
public void testParseLineString() throws IOException {
|
public void testParseLineString() throws IOException {
|
||||||
List<Coordinate> coordinates = randomLineStringCoords();
|
List<Coordinate> coordinates = randomLineStringCoords();
|
||||||
|
|
||||||
LineString expected = GEOMETRY_FACTORY.createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
|
LineString expected = GEOMETRY_FACTORY.createLineString(coordinates.toArray(new Coordinate[coordinates.size()]));
|
||||||
assertExpected(jtsGeom(expected), new LineStringBuilder(coordinates), true);
|
assertExpected(jtsGeom(expected), new LineStringBuilder(coordinates), true);
|
||||||
|
|
||||||
|
@ -279,14 +279,13 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
|
||||||
parser.nextToken();
|
parser.nextToken();
|
||||||
|
|
||||||
Settings indexSettings = Settings.builder()
|
Settings indexSettings = Settings.builder()
|
||||||
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_7_0_0)
|
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.V_6_3_0)
|
||||||
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
|
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
|
||||||
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
|
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
|
||||||
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
|
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
|
||||||
|
|
||||||
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
|
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
|
||||||
final GeoShapeFieldMapper mapperBuilder =
|
final GeoShapeFieldMapper mapperBuilder = new GeoShapeFieldMapper.Builder("test").ignoreZValue(false).build(mockBuilderContext);
|
||||||
(GeoShapeFieldMapper) (new GeoShapeFieldMapper.Builder("test").ignoreZValue(false).build(mockBuilderContext));
|
|
||||||
|
|
||||||
// test store z disabled
|
// test store z disabled
|
||||||
ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class,
|
ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class,
|
||||||
|
@ -324,8 +323,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
|
||||||
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
|
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
|
||||||
|
|
||||||
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
|
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
|
||||||
final LegacyGeoShapeFieldMapper mapperBuilder =
|
final GeoShapeFieldMapper mapperBuilder = new GeoShapeFieldMapper.Builder("test").ignoreZValue(true).build(mockBuilderContext);
|
||||||
(LegacyGeoShapeFieldMapper)(new LegacyGeoShapeFieldMapper.Builder("test").ignoreZValue(true).build(mockBuilderContext));
|
|
||||||
|
|
||||||
// test store z disabled
|
// test store z disabled
|
||||||
ElasticsearchException e = expectThrows(ElasticsearchException.class,
|
ElasticsearchException e = expectThrows(ElasticsearchException.class,
|
||||||
|
@ -354,8 +352,7 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
|
||||||
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
|
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
|
||||||
|
|
||||||
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
|
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
|
||||||
final LegacyGeoShapeFieldMapper mapperBuilder =
|
final GeoShapeFieldMapper mapperBuilder = new GeoShapeFieldMapper.Builder("test").ignoreZValue(true).build(mockBuilderContext);
|
||||||
(LegacyGeoShapeFieldMapper)(new LegacyGeoShapeFieldMapper.Builder("test").ignoreZValue(true).build(mockBuilderContext));
|
|
||||||
|
|
||||||
ShapeBuilder<?, ?> shapeBuilder = ShapeParser.parse(parser, mapperBuilder);
|
ShapeBuilder<?, ?> shapeBuilder = ShapeParser.parse(parser, mapperBuilder);
|
||||||
assertEquals(shapeBuilder.numDimensions(), 3);
|
assertEquals(shapeBuilder.numDimensions(), 3);
|
||||||
|
@ -375,14 +372,12 @@ public class GeoWKTShapeParserTests extends BaseGeoParsingTestCase {
|
||||||
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
|
.put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID()).build();
|
||||||
|
|
||||||
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
|
Mapper.BuilderContext mockBuilderContext = new Mapper.BuilderContext(indexSettings, new ContentPath());
|
||||||
final LegacyGeoShapeFieldMapper defaultMapperBuilder =
|
final GeoShapeFieldMapper defaultMapperBuilder = new GeoShapeFieldMapper.Builder("test").coerce(false).build(mockBuilderContext);
|
||||||
(LegacyGeoShapeFieldMapper)(new LegacyGeoShapeFieldMapper.Builder("test").coerce(false).build(mockBuilderContext));
|
|
||||||
ElasticsearchParseException exception = expectThrows(ElasticsearchParseException.class,
|
ElasticsearchParseException exception = expectThrows(ElasticsearchParseException.class,
|
||||||
() -> ShapeParser.parse(parser, defaultMapperBuilder));
|
() -> ShapeParser.parse(parser, defaultMapperBuilder));
|
||||||
assertEquals("invalid LinearRing found (coordinates are not closed)", exception.getMessage());
|
assertEquals("invalid LinearRing found (coordinates are not closed)", exception.getMessage());
|
||||||
|
|
||||||
final LegacyGeoShapeFieldMapper coercingMapperBuilder =
|
final GeoShapeFieldMapper coercingMapperBuilder = new GeoShapeFieldMapper.Builder("test").coerce(true).build(mockBuilderContext);
|
||||||
(LegacyGeoShapeFieldMapper)(new LegacyGeoShapeFieldMapper.Builder("test").coerce(true).build(mockBuilderContext));
|
|
||||||
ShapeBuilder<?, ?> shapeBuilder = ShapeParser.parse(parser, coercingMapperBuilder);
|
ShapeBuilder<?, ?> shapeBuilder = ShapeParser.parse(parser, coercingMapperBuilder);
|
||||||
assertNotNull(shapeBuilder);
|
assertNotNull(shapeBuilder);
|
||||||
assertEquals("polygon ((100.0 5.0, 100.0 10.0, 90.0 10.0, 90.0 5.0, 100.0 5.0))", shapeBuilder.toWKT());
|
assertEquals("polygon ((100.0 5.0, 100.0 10.0, 90.0 10.0, 90.0 5.0, 100.0 5.0))", shapeBuilder.toWKT());
|
||||||
|
|
|
@ -24,8 +24,8 @@ import org.apache.lucene.index.Term;
|
||||||
import org.apache.lucene.search.DocValuesFieldExistsQuery;
|
import org.apache.lucene.search.DocValuesFieldExistsQuery;
|
||||||
import org.apache.lucene.search.Query;
|
import org.apache.lucene.search.Query;
|
||||||
import org.apache.lucene.search.TermQuery;
|
import org.apache.lucene.search.TermQuery;
|
||||||
import org.elasticsearch.Version;
|
|
||||||
import org.elasticsearch.common.geo.builders.PointBuilder;
|
import org.elasticsearch.common.geo.builders.PointBuilder;
|
||||||
|
import org.locationtech.spatial4j.shape.Point;
|
||||||
import org.elasticsearch.common.collect.Iterators;
|
import org.elasticsearch.common.collect.Iterators;
|
||||||
import org.elasticsearch.common.geo.GeoPoint;
|
import org.elasticsearch.common.geo.GeoPoint;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
@ -63,7 +63,6 @@ public class ExternalMapper extends FieldMapper {
|
||||||
private BooleanFieldMapper.Builder boolBuilder = new BooleanFieldMapper.Builder(Names.FIELD_BOOL);
|
private BooleanFieldMapper.Builder boolBuilder = new BooleanFieldMapper.Builder(Names.FIELD_BOOL);
|
||||||
private GeoPointFieldMapper.Builder latLonPointBuilder = new GeoPointFieldMapper.Builder(Names.FIELD_POINT);
|
private GeoPointFieldMapper.Builder latLonPointBuilder = new GeoPointFieldMapper.Builder(Names.FIELD_POINT);
|
||||||
private GeoShapeFieldMapper.Builder shapeBuilder = new GeoShapeFieldMapper.Builder(Names.FIELD_SHAPE);
|
private GeoShapeFieldMapper.Builder shapeBuilder = new GeoShapeFieldMapper.Builder(Names.FIELD_SHAPE);
|
||||||
private LegacyGeoShapeFieldMapper.Builder legacyShapeBuilder = new LegacyGeoShapeFieldMapper.Builder(Names.FIELD_SHAPE);
|
|
||||||
private Mapper.Builder stringBuilder;
|
private Mapper.Builder stringBuilder;
|
||||||
private String generatedValue;
|
private String generatedValue;
|
||||||
private String mapperName;
|
private String mapperName;
|
||||||
|
@ -87,9 +86,7 @@ public class ExternalMapper extends FieldMapper {
|
||||||
BinaryFieldMapper binMapper = binBuilder.build(context);
|
BinaryFieldMapper binMapper = binBuilder.build(context);
|
||||||
BooleanFieldMapper boolMapper = boolBuilder.build(context);
|
BooleanFieldMapper boolMapper = boolBuilder.build(context);
|
||||||
GeoPointFieldMapper pointMapper = latLonPointBuilder.build(context);
|
GeoPointFieldMapper pointMapper = latLonPointBuilder.build(context);
|
||||||
BaseGeoShapeFieldMapper shapeMapper = (context.indexCreatedVersion().before(Version.V_6_6_0))
|
GeoShapeFieldMapper shapeMapper = shapeBuilder.build(context);
|
||||||
? legacyShapeBuilder.build(context)
|
|
||||||
: shapeBuilder.build(context);
|
|
||||||
FieldMapper stringMapper = (FieldMapper)stringBuilder.build(context);
|
FieldMapper stringMapper = (FieldMapper)stringBuilder.build(context);
|
||||||
context.path().remove();
|
context.path().remove();
|
||||||
|
|
||||||
|
@ -153,13 +150,13 @@ public class ExternalMapper extends FieldMapper {
|
||||||
private BinaryFieldMapper binMapper;
|
private BinaryFieldMapper binMapper;
|
||||||
private BooleanFieldMapper boolMapper;
|
private BooleanFieldMapper boolMapper;
|
||||||
private GeoPointFieldMapper pointMapper;
|
private GeoPointFieldMapper pointMapper;
|
||||||
private BaseGeoShapeFieldMapper shapeMapper;
|
private GeoShapeFieldMapper shapeMapper;
|
||||||
private FieldMapper stringMapper;
|
private FieldMapper stringMapper;
|
||||||
|
|
||||||
public ExternalMapper(String simpleName, MappedFieldType fieldType,
|
public ExternalMapper(String simpleName, MappedFieldType fieldType,
|
||||||
String generatedValue, String mapperName,
|
String generatedValue, String mapperName,
|
||||||
BinaryFieldMapper binMapper, BooleanFieldMapper boolMapper, GeoPointFieldMapper pointMapper,
|
BinaryFieldMapper binMapper, BooleanFieldMapper boolMapper, GeoPointFieldMapper pointMapper,
|
||||||
BaseGeoShapeFieldMapper shapeMapper, FieldMapper stringMapper, Settings indexSettings,
|
GeoShapeFieldMapper shapeMapper, FieldMapper stringMapper, Settings indexSettings,
|
||||||
MultiFields multiFields, CopyTo copyTo) {
|
MultiFields multiFields, CopyTo copyTo) {
|
||||||
super(simpleName, fieldType, new ExternalFieldType(), indexSettings, multiFields, copyTo);
|
super(simpleName, fieldType, new ExternalFieldType(), indexSettings, multiFields, copyTo);
|
||||||
this.generatedValue = generatedValue;
|
this.generatedValue = generatedValue;
|
||||||
|
@ -185,12 +182,8 @@ public class ExternalMapper extends FieldMapper {
|
||||||
pointMapper.parse(context.createExternalValueContext(point));
|
pointMapper.parse(context.createExternalValueContext(point));
|
||||||
|
|
||||||
// Let's add a Dummy Shape
|
// Let's add a Dummy Shape
|
||||||
PointBuilder pb = new PointBuilder(-100, 45);
|
Point shape = new PointBuilder(-100, 45).buildS4J();
|
||||||
if (shapeMapper instanceof GeoShapeFieldMapper) {
|
shapeMapper.parse(context.createExternalValueContext(shape));
|
||||||
shapeMapper.parse(context.createExternalValueContext(pb.buildLucene()));
|
|
||||||
} else {
|
|
||||||
shapeMapper.parse(context.createExternalValueContext(pb.buildS4J()));
|
|
||||||
}
|
|
||||||
|
|
||||||
context = context.createExternalValueContext(generatedValue);
|
context = context.createExternalValueContext(generatedValue);
|
||||||
|
|
||||||
|
@ -217,7 +210,7 @@ public class ExternalMapper extends FieldMapper {
|
||||||
BinaryFieldMapper binMapperUpdate = (BinaryFieldMapper) binMapper.updateFieldType(fullNameToFieldType);
|
BinaryFieldMapper binMapperUpdate = (BinaryFieldMapper) binMapper.updateFieldType(fullNameToFieldType);
|
||||||
BooleanFieldMapper boolMapperUpdate = (BooleanFieldMapper) boolMapper.updateFieldType(fullNameToFieldType);
|
BooleanFieldMapper boolMapperUpdate = (BooleanFieldMapper) boolMapper.updateFieldType(fullNameToFieldType);
|
||||||
GeoPointFieldMapper pointMapperUpdate = (GeoPointFieldMapper) pointMapper.updateFieldType(fullNameToFieldType);
|
GeoPointFieldMapper pointMapperUpdate = (GeoPointFieldMapper) pointMapper.updateFieldType(fullNameToFieldType);
|
||||||
BaseGeoShapeFieldMapper shapeMapperUpdate = (BaseGeoShapeFieldMapper) shapeMapper.updateFieldType(fullNameToFieldType);
|
GeoShapeFieldMapper shapeMapperUpdate = (GeoShapeFieldMapper) shapeMapper.updateFieldType(fullNameToFieldType);
|
||||||
TextFieldMapper stringMapperUpdate = (TextFieldMapper) stringMapper.updateFieldType(fullNameToFieldType);
|
TextFieldMapper stringMapperUpdate = (TextFieldMapper) stringMapper.updateFieldType(fullNameToFieldType);
|
||||||
if (update == this
|
if (update == this
|
||||||
&& multiFieldsUpdate == multiFields
|
&& multiFieldsUpdate == multiFields
|
||||||
|
|
|
@ -21,13 +21,12 @@ package org.elasticsearch.index.mapper;
|
||||||
|
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
import org.elasticsearch.common.geo.ShapeRelation;
|
import org.elasticsearch.common.geo.ShapeRelation;
|
||||||
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
|
import org.elasticsearch.common.geo.builders.PointBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
import org.elasticsearch.index.query.QueryBuilders;
|
import org.elasticsearch.index.query.QueryBuilders;
|
||||||
import org.elasticsearch.plugins.Plugin;
|
import org.elasticsearch.plugins.Plugin;
|
||||||
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
|
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
|
||||||
import org.elasticsearch.test.ESIntegTestCase;
|
import org.elasticsearch.test.ESIntegTestCase;
|
||||||
import org.locationtech.jts.geom.Coordinate;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -119,8 +118,7 @@ public class ExternalValuesMapperIntegrationIT extends ESIntegTestCase {
|
||||||
assertThat(response.getHits().getTotalHits().value, equalTo((long) 1));
|
assertThat(response.getHits().getTotalHits().value, equalTo((long) 1));
|
||||||
|
|
||||||
response = client().prepareSearch("test-idx")
|
response = client().prepareSearch("test-idx")
|
||||||
.setPostFilter(QueryBuilders.geoShapeQuery("field.shape",
|
.setPostFilter(QueryBuilders.geoShapeQuery("field.shape", new PointBuilder(-100, 45)).relation(ShapeRelation.WITHIN))
|
||||||
new EnvelopeBuilder(new Coordinate(-101, 46), new Coordinate(-99, 44))).relation(ShapeRelation.WITHIN))
|
|
||||||
.execute().actionGet();
|
.execute().actionGet();
|
||||||
|
|
||||||
assertThat(response.getHits().getTotalHits().value, equalTo((long) 1));
|
assertThat(response.getHits().getTotalHits().value, equalTo((long) 1));
|
||||||
|
|
|
@ -18,9 +18,14 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.index.mapper;
|
package org.elasticsearch.index.mapper;
|
||||||
|
|
||||||
|
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
||||||
|
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
|
||||||
import org.elasticsearch.common.Explicit;
|
import org.elasticsearch.common.Explicit;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.compress.CompressedXContent;
|
import org.elasticsearch.common.compress.CompressedXContent;
|
||||||
|
import org.elasticsearch.common.geo.GeoUtils;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
@ -37,6 +42,7 @@ import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
|
||||||
public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
|
|
||||||
|
@ -47,10 +53,10 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
|
|
||||||
public void testDefaultConfiguration() throws IOException {
|
public void testDefaultConfiguration() throws IOException {
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
.startObject("properties").startObject("location")
|
.startObject("properties").startObject("location")
|
||||||
.field("type", "geo_shape")
|
.field("type", "geo_shape")
|
||||||
.endObject().endObject()
|
.endObject().endObject()
|
||||||
.endObject().endObject());
|
.endObject().endObject());
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
.parse("type1", new CompressedXContent(mapping));
|
||||||
|
@ -58,8 +64,12 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
assertThat(geoShapeFieldMapper.fieldType().orientation(),
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
equalTo(GeoShapeFieldMapper.Defaults.ORIENTATION.value()));
|
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.025d));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoShapeFieldMapper.Defaults.GEOHASH_LEVELS));
|
||||||
|
assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(GeoShapeFieldMapper.Defaults.ORIENTATION));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,11 +77,11 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
*/
|
*/
|
||||||
public void testOrientationParsing() throws IOException {
|
public void testOrientationParsing() throws IOException {
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
.startObject("properties").startObject("location")
|
.startObject("properties").startObject("location")
|
||||||
.field("type", "geo_shape")
|
.field("type", "geo_shape")
|
||||||
.field("orientation", "left")
|
.field("orientation", "left")
|
||||||
.endObject().endObject()
|
.endObject().endObject()
|
||||||
.endObject().endObject());
|
.endObject().endObject());
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
.parse("type1", new CompressedXContent(mapping));
|
||||||
|
@ -85,11 +95,11 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
|
|
||||||
// explicit right orientation test
|
// explicit right orientation test
|
||||||
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
.startObject("properties").startObject("location")
|
.startObject("properties").startObject("location")
|
||||||
.field("type", "geo_shape")
|
.field("type", "geo_shape")
|
||||||
.field("orientation", "right")
|
.field("orientation", "right")
|
||||||
.endObject().endObject()
|
.endObject().endObject()
|
||||||
.endObject().endObject());
|
.endObject().endObject());
|
||||||
|
|
||||||
defaultMapper = createIndex("test2").mapperService().documentMapperParser()
|
defaultMapper = createIndex("test2").mapperService().documentMapperParser()
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
.parse("type1", new CompressedXContent(mapping));
|
||||||
|
@ -107,11 +117,11 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
*/
|
*/
|
||||||
public void testCoerceParsing() throws IOException {
|
public void testCoerceParsing() throws IOException {
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
.startObject("properties").startObject("location")
|
.startObject("properties").startObject("location")
|
||||||
.field("type", "geo_shape")
|
.field("type", "geo_shape")
|
||||||
.field("coerce", "true")
|
.field("coerce", "true")
|
||||||
.endObject().endObject()
|
.endObject().endObject()
|
||||||
.endObject().endObject());
|
.endObject().endObject());
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
.parse("type1", new CompressedXContent(mapping));
|
||||||
|
@ -123,11 +133,11 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
|
|
||||||
// explicit false coerce test
|
// explicit false coerce test
|
||||||
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
.startObject("properties").startObject("location")
|
.startObject("properties").startObject("location")
|
||||||
.field("type", "geo_shape")
|
.field("type", "geo_shape")
|
||||||
.field("coerce", "false")
|
.field("coerce", "false")
|
||||||
.endObject().endObject()
|
.endObject().endObject()
|
||||||
.endObject().endObject());
|
.endObject().endObject());
|
||||||
|
|
||||||
defaultMapper = createIndex("test2").mapperService().documentMapperParser()
|
defaultMapper = createIndex("test2").mapperService().documentMapperParser()
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
.parse("type1", new CompressedXContent(mapping));
|
||||||
|
@ -136,7 +146,6 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
|
|
||||||
coerce = ((GeoShapeFieldMapper)fieldMapper).coerce().value();
|
coerce = ((GeoShapeFieldMapper)fieldMapper).coerce().value();
|
||||||
assertThat(coerce, equalTo(false));
|
assertThat(coerce, equalTo(false));
|
||||||
assertFieldWarnings("tree");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -213,45 +222,304 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
assertThat(ignoreMalformed.value(), equalTo(false));
|
assertThat(ignoreMalformed.value(), equalTo(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testGeohashConfiguration() throws IOException {
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "geohash")
|
||||||
|
.field("tree_levels", "4")
|
||||||
|
.field("distance_error_pct", "0.1")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
private void assertFieldWarnings(String... fieldNames) {
|
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
||||||
String[] warnings = new String[fieldNames.length];
|
.parse("type1", new CompressedXContent(mapping));
|
||||||
for (int i = 0; i < fieldNames.length; ++i) {
|
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
||||||
warnings[i] = "Field parameter [" + fieldNames[i] + "] "
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
+ "is deprecated and will be removed in a future version.";
|
|
||||||
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.1));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testQuadtreeConfiguration() throws IOException {
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.field("tree_levels", "6")
|
||||||
|
.field("distance_error_pct", "0.5")
|
||||||
|
.field("points_only", true)
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
|
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
||||||
|
.parse("type1", new CompressedXContent(mapping));
|
||||||
|
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
||||||
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(6));
|
||||||
|
assertThat(strategy.isPointsOnly(), equalTo(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLevelPrecisionConfiguration() throws IOException {
|
||||||
|
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
|
||||||
|
|
||||||
|
{
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.field("tree_levels", "6")
|
||||||
|
.field("precision", "70m")
|
||||||
|
.field("distance_error_pct", "0.5")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
|
|
||||||
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
|
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
||||||
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
||||||
|
// 70m is more precise so it wins
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.quadTreeLevelsForPrecision(70d)));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.field("tree_levels", "26")
|
||||||
|
.field("precision", "70m")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
|
|
||||||
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
|
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
||||||
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
// distance_error_pct was not specified so we expect the mapper to take the highest precision between "precision" and
|
||||||
|
// "tree_levels" setting distErrPct to 0 to guarantee desired precision
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.0));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
||||||
|
// 70m is less precise so it loses
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(26));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "geohash")
|
||||||
|
.field("tree_levels", "6")
|
||||||
|
.field("precision", "70m")
|
||||||
|
.field("distance_error_pct", "0.5")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
|
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
||||||
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||||
|
// 70m is more precise so it wins
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(70d)));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "geohash")
|
||||||
|
.field("tree_levels", GeoUtils.geoHashLevelsForPrecision(70d)+1)
|
||||||
|
.field("precision", "70m")
|
||||||
|
.field("distance_error_pct", "0.5")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
|
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
||||||
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(70d)+1));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.field("tree_levels", GeoUtils.quadTreeLevelsForPrecision(70d)+1)
|
||||||
|
.field("precision", "70m")
|
||||||
|
.field("distance_error_pct", "0.5")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
|
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
||||||
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.quadTreeLevelsForPrecision(70d)+1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPointsOnlyOption() throws IOException {
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "geohash")
|
||||||
|
.field("points_only", true)
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
|
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
||||||
|
.parse("type1", new CompressedXContent(mapping));
|
||||||
|
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
||||||
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||||
|
assertThat(strategy.isPointsOnly(), equalTo(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLevelDefaults() throws IOException {
|
||||||
|
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
|
||||||
|
{
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.field("distance_error_pct", "0.5")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
|
|
||||||
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
|
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
||||||
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
||||||
|
/* 50m is default */
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.quadTreeLevelsForPrecision(50d)));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "geohash")
|
||||||
|
.field("distance_error_pct", "0.5")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
|
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
||||||
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||||
|
/* 50m is default */
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(50d)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGeoShapeMapperMerge() throws Exception {
|
public void testGeoShapeMapperMerge() throws Exception {
|
||||||
String stage1Mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties")
|
String stage1Mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties")
|
||||||
.startObject("shape").field("type", "geo_shape")
|
.startObject("shape").field("type", "geo_shape").field("tree", "geohash")
|
||||||
.field("orientation", "ccw")
|
.field("strategy", "recursive")
|
||||||
.endObject().endObject().endObject().endObject());
|
.field("precision", "1m").field("tree_levels", 8).field("distance_error_pct", 0.01)
|
||||||
|
.field("orientation", "ccw")
|
||||||
|
.endObject().endObject().endObject().endObject());
|
||||||
MapperService mapperService = createIndex("test").mapperService();
|
MapperService mapperService = createIndex("test").mapperService();
|
||||||
DocumentMapper docMapper = mapperService.merge("type", new CompressedXContent(stage1Mapping),
|
DocumentMapper docMapper = mapperService.merge("type", new CompressedXContent(stage1Mapping),
|
||||||
MapperService.MergeReason.MAPPING_UPDATE);
|
MapperService.MergeReason.MAPPING_UPDATE);
|
||||||
String stage2Mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
|
String stage2Mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
|
||||||
.startObject("properties").startObject("shape").field("type", "geo_shape")
|
.startObject("properties").startObject("shape").field("type", "geo_shape")
|
||||||
.field("orientation", "cw").endObject().endObject().endObject().endObject());
|
.field("tree", "quadtree")
|
||||||
mapperService.merge("type", new CompressedXContent(stage2Mapping), MapperService.MergeReason.MAPPING_UPDATE);
|
.field("strategy", "term").field("precision", "1km")
|
||||||
|
.field("tree_levels", 26).field("distance_error_pct", 26)
|
||||||
|
.field("orientation", "cw").endObject().endObject().endObject().endObject());
|
||||||
|
try {
|
||||||
|
mapperService.merge("type", new CompressedXContent(stage2Mapping), MapperService.MergeReason.MAPPING_UPDATE);
|
||||||
|
fail();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
assertThat(e.getMessage(), containsString("mapper [shape] has different [strategy]"));
|
||||||
|
assertThat(e.getMessage(), containsString("mapper [shape] has different [tree]"));
|
||||||
|
assertThat(e.getMessage(), containsString("mapper [shape] has different [tree_levels]"));
|
||||||
|
assertThat(e.getMessage(), containsString("mapper [shape] has different [precision]"));
|
||||||
|
}
|
||||||
|
|
||||||
// verify nothing changed
|
// verify nothing changed
|
||||||
Mapper fieldMapper = docMapper.mappers().getMapper("shape");
|
Mapper fieldMapper = docMapper.mappers().getMapper("shape");
|
||||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy, instanceOf(RecursivePrefixTreeStrategy.class));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.01));
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(1d)));
|
||||||
assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(ShapeBuilder.Orientation.CCW));
|
assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(ShapeBuilder.Orientation.CCW));
|
||||||
|
|
||||||
// change mapping; orientation
|
// correct mapping
|
||||||
stage2Mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
|
stage2Mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
|
||||||
.startObject("properties").startObject("shape").field("type", "geo_shape")
|
.startObject("properties").startObject("shape").field("type", "geo_shape").field("precision", "1m")
|
||||||
.field("orientation", "cw").endObject().endObject().endObject().endObject());
|
.field("tree_levels", 8).field("distance_error_pct", 0.001)
|
||||||
|
.field("orientation", "cw").endObject().endObject().endObject().endObject());
|
||||||
docMapper = mapperService.merge("type", new CompressedXContent(stage2Mapping), MapperService.MergeReason.MAPPING_UPDATE);
|
docMapper = mapperService.merge("type", new CompressedXContent(stage2Mapping), MapperService.MergeReason.MAPPING_UPDATE);
|
||||||
|
|
||||||
fieldMapper = docMapper.mappers().getMapper("shape");
|
fieldMapper = docMapper.mappers().getMapper("shape");
|
||||||
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy, instanceOf(RecursivePrefixTreeStrategy.class));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.001));
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(1d)));
|
||||||
assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(ShapeBuilder.Orientation.CW));
|
assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(ShapeBuilder.Orientation.CW));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,12 +544,112 @@ public class GeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
.startObject("properties").startObject("location")
|
.startObject("properties").startObject("location")
|
||||||
.field("type", "geo_shape")
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
.endObject().endObject()
|
.endObject().endObject()
|
||||||
.endObject().endObject());
|
.endObject().endObject());
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
String serialized = toXContentString((GeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
String serialized = toXContentString((GeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
||||||
assertTrue(serialized, serialized.contains("\"orientation\":\"" + BaseGeoShapeFieldMapper.Defaults.ORIENTATION.value() + "\""));
|
assertTrue(serialized, serialized.contains("\"precision\":\"50.0m\""));
|
||||||
|
assertTrue(serialized, serialized.contains("\"tree_levels\":21"));
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "geohash")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
|
String serialized = toXContentString((GeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
||||||
|
assertTrue(serialized, serialized.contains("\"precision\":\"50.0m\""));
|
||||||
|
assertTrue(serialized, serialized.contains("\"tree_levels\":9"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.field("tree_levels", "6")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
|
String serialized = toXContentString((GeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
||||||
|
assertFalse(serialized, serialized.contains("\"precision\":"));
|
||||||
|
assertTrue(serialized, serialized.contains("\"tree_levels\":6"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.field("precision", "6")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
|
String serialized = toXContentString((GeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
||||||
|
assertTrue(serialized, serialized.contains("\"precision\":\"6.0m\""));
|
||||||
|
assertFalse(serialized, serialized.contains("\"tree_levels\":"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.field("precision", "6m")
|
||||||
|
.field("tree_levels", "5")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
||||||
|
String serialized = toXContentString((GeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
||||||
|
assertTrue(serialized, serialized.contains("\"precision\":\"6.0m\""));
|
||||||
|
assertTrue(serialized, serialized.contains("\"tree_levels\":5"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPointsOnlyDefaultsWithTermStrategy() throws IOException {
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.field("precision", "10m")
|
||||||
|
.field("strategy", "term")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
|
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
||||||
|
.parse("type1", new CompressedXContent(mapping));
|
||||||
|
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
||||||
|
assertThat(fieldMapper, instanceOf(GeoShapeFieldMapper.class));
|
||||||
|
|
||||||
|
GeoShapeFieldMapper geoShapeFieldMapper = (GeoShapeFieldMapper) fieldMapper;
|
||||||
|
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultStrategy();
|
||||||
|
|
||||||
|
assertThat(strategy.getDistErrPct(), equalTo(0.0));
|
||||||
|
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
||||||
|
assertThat(strategy.getGrid().getMaxLevels(), equalTo(23));
|
||||||
|
assertThat(strategy.isPointsOnly(), equalTo(true));
|
||||||
|
// term strategy changes the default for points_only, check that we handle it correctly
|
||||||
|
assertThat(toXContentString(geoShapeFieldMapper, false), not(containsString("points_only")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void testPointsOnlyFalseWithTermStrategy() throws Exception {
|
||||||
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.field("precision", "10m")
|
||||||
|
.field("strategy", "term")
|
||||||
|
.field("points_only", false)
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
|
|
||||||
|
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
|
||||||
|
|
||||||
|
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
|
||||||
|
() -> parser.parse("type1", new CompressedXContent(mapping))
|
||||||
|
);
|
||||||
|
assertThat(e.getMessage(), containsString("points_only cannot be set to false for term strategy"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toXContentString(GeoShapeFieldMapper mapper, boolean includeDefaults) throws IOException {
|
public String toXContentString(GeoShapeFieldMapper mapper, boolean includeDefaults) throws IOException {
|
||||||
|
|
|
@ -18,23 +18,69 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.index.mapper;
|
package org.elasticsearch.index.mapper;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.index.mapper.GeoShapeFieldMapper.GeoShapeFieldType;
|
import org.elasticsearch.index.mapper.GeoShapeFieldMapper.GeoShapeFieldType;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public class GeoShapeFieldTypeTests extends FieldTypeTestCase {
|
public class GeoShapeFieldTypeTests extends FieldTypeTestCase {
|
||||||
@Override
|
@Override
|
||||||
protected MappedFieldType createDefaultFieldType() {
|
protected MappedFieldType createDefaultFieldType() {
|
||||||
return new GeoShapeFieldType();
|
return new GeoShapeFieldMapper.GeoShapeFieldType();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setupProperties() {
|
public void setupProperties() {
|
||||||
addModifier(new FieldTypeTestCase.Modifier("orientation", true) {
|
addModifier(new Modifier("tree", false) {
|
||||||
@Override
|
@Override
|
||||||
public void modify(MappedFieldType ft) {
|
public void modify(MappedFieldType ft) {
|
||||||
((GeoShapeFieldType)ft).setOrientation(ShapeBuilder.Orientation.LEFT);
|
((GeoShapeFieldMapper.GeoShapeFieldType)ft).setTree("quadtree");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addModifier(new Modifier("strategy", false) {
|
||||||
|
@Override
|
||||||
|
public void modify(MappedFieldType ft) {
|
||||||
|
((GeoShapeFieldMapper.GeoShapeFieldType)ft).setStrategyName("term");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addModifier(new Modifier("tree_levels", false) {
|
||||||
|
@Override
|
||||||
|
public void modify(MappedFieldType ft) {
|
||||||
|
((GeoShapeFieldMapper.GeoShapeFieldType)ft).setTreeLevels(10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addModifier(new Modifier("precision", false) {
|
||||||
|
@Override
|
||||||
|
public void modify(MappedFieldType ft) {
|
||||||
|
((GeoShapeFieldMapper.GeoShapeFieldType)ft).setPrecisionInMeters(20);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addModifier(new Modifier("distance_error_pct", true) {
|
||||||
|
@Override
|
||||||
|
public void modify(MappedFieldType ft) {
|
||||||
|
((GeoShapeFieldMapper.GeoShapeFieldType)ft).setDefaultDistanceErrorPct(0.5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addModifier(new Modifier("orientation", true) {
|
||||||
|
@Override
|
||||||
|
public void modify(MappedFieldType ft) {
|
||||||
|
((GeoShapeFieldMapper.GeoShapeFieldType)ft).setOrientation(ShapeBuilder.Orientation.LEFT);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link GeoShapeFieldType#setStrategyName(String)} that checks that {@link GeoShapeFieldType#pointsOnly()}
|
||||||
|
* gets set as a side effect when using SpatialStrategy.TERM
|
||||||
|
*/
|
||||||
|
public void testSetStrategyName() throws IOException {
|
||||||
|
GeoShapeFieldType fieldType = new GeoShapeFieldMapper.GeoShapeFieldType();
|
||||||
|
assertFalse(fieldType.pointsOnly());
|
||||||
|
fieldType.setStrategyName(SpatialStrategy.RECURSIVE.getStrategyName());
|
||||||
|
assertFalse(fieldType.pointsOnly());
|
||||||
|
fieldType.setStrategyName(SpatialStrategy.TERM.getStrategyName());
|
||||||
|
assertTrue(fieldType.pointsOnly());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,714 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch under one or more contributor
|
|
||||||
* license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright
|
|
||||||
* ownership. Elasticsearch licenses this file to you under
|
|
||||||
* the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
* not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.index.mapper;
|
|
||||||
|
|
||||||
import org.apache.lucene.spatial.prefix.PrefixTreeStrategy;
|
|
||||||
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
|
|
||||||
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
|
|
||||||
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
|
|
||||||
import org.elasticsearch.ElasticsearchParseException;
|
|
||||||
import org.elasticsearch.common.Explicit;
|
|
||||||
import org.elasticsearch.common.Strings;
|
|
||||||
import org.elasticsearch.common.compress.CompressedXContent;
|
|
||||||
import org.elasticsearch.common.geo.GeoUtils;
|
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
|
||||||
import org.elasticsearch.plugins.Plugin;
|
|
||||||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
|
||||||
import org.elasticsearch.test.InternalSettingsPlugin;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import static org.elasticsearch.index.mapper.GeoPointFieldMapper.Names.IGNORE_Z_VALUE;
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
|
||||||
import static org.hamcrest.Matchers.not;
|
|
||||||
|
|
||||||
public class LegacyGeoShapeFieldMapperTests extends ESSingleNodeTestCase {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Collection<Class<? extends Plugin>> getPlugins() {
|
|
||||||
return pluginList(InternalSettingsPlugin.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testDefaultConfiguration() throws IOException {
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("strategy", "recursive")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
assertThat(geoShapeFieldMapper.fieldType().tree(),
|
|
||||||
equalTo(LegacyGeoShapeFieldMapper.DeprecatedParameters.Defaults.TREE));
|
|
||||||
assertThat(geoShapeFieldMapper.fieldType().treeLevels(),
|
|
||||||
equalTo(LegacyGeoShapeFieldMapper.DeprecatedParameters.Defaults.QUADTREE_LEVELS));
|
|
||||||
assertThat(geoShapeFieldMapper.fieldType().pointsOnly(),
|
|
||||||
equalTo(LegacyGeoShapeFieldMapper.DeprecatedParameters.Defaults.POINTS_ONLY));
|
|
||||||
assertThat(geoShapeFieldMapper.fieldType().distanceErrorPct(),
|
|
||||||
equalTo(LegacyGeoShapeFieldMapper.DeprecatedParameters.Defaults.DISTANCE_ERROR_PCT));
|
|
||||||
assertThat(geoShapeFieldMapper.fieldType().orientation(),
|
|
||||||
equalTo(LegacyGeoShapeFieldMapper.Defaults.ORIENTATION.value()));
|
|
||||||
assertFieldWarnings("strategy");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that orientation parameter correctly parses
|
|
||||||
*/
|
|
||||||
public void testOrientationParsing() throws IOException {
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("orientation", "left")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
ShapeBuilder.Orientation orientation = ((LegacyGeoShapeFieldMapper)fieldMapper).fieldType().orientation();
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.CLOCKWISE));
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.LEFT));
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.CW));
|
|
||||||
|
|
||||||
// explicit right orientation test
|
|
||||||
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("orientation", "right")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
defaultMapper = createIndex("test2").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
orientation = ((LegacyGeoShapeFieldMapper)fieldMapper).fieldType().orientation();
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.COUNTER_CLOCKWISE));
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.RIGHT));
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.CCW));
|
|
||||||
assertFieldWarnings("tree");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that coerce parameter correctly parses
|
|
||||||
*/
|
|
||||||
public void testCoerceParsing() throws IOException {
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("coerce", "true")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
boolean coerce = ((LegacyGeoShapeFieldMapper)fieldMapper).coerce().value();
|
|
||||||
assertThat(coerce, equalTo(true));
|
|
||||||
|
|
||||||
// explicit false coerce test
|
|
||||||
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("coerce", "false")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
defaultMapper = createIndex("test2").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
coerce = ((LegacyGeoShapeFieldMapper)fieldMapper).coerce().value();
|
|
||||||
assertThat(coerce, equalTo(false));
|
|
||||||
assertFieldWarnings("tree");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that accept_z_value parameter correctly parses
|
|
||||||
*/
|
|
||||||
public void testIgnoreZValue() throws IOException {
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("strategy", "recursive")
|
|
||||||
.field(IGNORE_Z_VALUE.getPreferredName(), "true")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
boolean ignoreZValue = ((LegacyGeoShapeFieldMapper)fieldMapper).ignoreZValue().value();
|
|
||||||
assertThat(ignoreZValue, equalTo(true));
|
|
||||||
|
|
||||||
// explicit false accept_z_value test
|
|
||||||
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field(IGNORE_Z_VALUE.getPreferredName(), "false")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
defaultMapper = createIndex("test2").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
ignoreZValue = ((LegacyGeoShapeFieldMapper)fieldMapper).ignoreZValue().value();
|
|
||||||
assertThat(ignoreZValue, equalTo(false));
|
|
||||||
assertFieldWarnings("strategy", "tree");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that ignore_malformed parameter correctly parses
|
|
||||||
*/
|
|
||||||
public void testIgnoreMalformedParsing() throws IOException {
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("ignore_malformed", "true")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
Explicit<Boolean> ignoreMalformed = ((LegacyGeoShapeFieldMapper)fieldMapper).ignoreMalformed();
|
|
||||||
assertThat(ignoreMalformed.value(), equalTo(true));
|
|
||||||
|
|
||||||
// explicit false ignore_malformed test
|
|
||||||
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("ignore_malformed", "false")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
defaultMapper = createIndex("test2").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
ignoreMalformed = ((LegacyGeoShapeFieldMapper)fieldMapper).ignoreMalformed();
|
|
||||||
assertThat(ignoreMalformed.explicit(), equalTo(true));
|
|
||||||
assertThat(ignoreMalformed.value(), equalTo(false));
|
|
||||||
assertFieldWarnings("tree");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGeohashConfiguration() throws IOException {
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "geohash")
|
|
||||||
.field("tree_levels", "4")
|
|
||||||
.field("distance_error_pct", "0.1")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.1));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(4));
|
|
||||||
assertFieldWarnings("tree", "tree_levels", "distance_error_pct");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testQuadtreeConfiguration() throws IOException {
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("tree_levels", "6")
|
|
||||||
.field("distance_error_pct", "0.5")
|
|
||||||
.field("points_only", true)
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(6));
|
|
||||||
assertThat(strategy.isPointsOnly(), equalTo(true));
|
|
||||||
assertFieldWarnings("tree", "tree_levels", "distance_error_pct", "points_only");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertFieldWarnings(String... fieldNames) {
|
|
||||||
String[] warnings = new String[fieldNames.length];
|
|
||||||
for (int i = 0; i < fieldNames.length; ++i) {
|
|
||||||
warnings[i] = "Field parameter [" + fieldNames[i] + "] "
|
|
||||||
+ "is deprecated and will be removed in a future version.";
|
|
||||||
}
|
|
||||||
assertWarnings(warnings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testLevelPrecisionConfiguration() throws IOException {
|
|
||||||
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
|
|
||||||
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("tree_levels", "6")
|
|
||||||
.field("precision", "70m")
|
|
||||||
.field("distance_error_pct", "0.5")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
|
||||||
// 70m is more precise so it wins
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.quadTreeLevelsForPrecision(70d)));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("tree_levels", "26")
|
|
||||||
.field("precision", "70m")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
// distance_error_pct was not specified so we expect the mapper to take the highest precision between "precision" and
|
|
||||||
// "tree_levels" setting distErrPct to 0 to guarantee desired precision
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.0));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
|
||||||
// 70m is less precise so it loses
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(26));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "geohash")
|
|
||||||
.field("tree_levels", "6")
|
|
||||||
.field("precision", "70m")
|
|
||||||
.field("distance_error_pct", "0.5")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
|
||||||
// 70m is more precise so it wins
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(70d)));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "geohash")
|
|
||||||
.field("tree_levels", GeoUtils.geoHashLevelsForPrecision(70d)+1)
|
|
||||||
.field("precision", "70m")
|
|
||||||
.field("distance_error_pct", "0.5")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(70d)+1));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("tree_levels", GeoUtils.quadTreeLevelsForPrecision(70d)+1)
|
|
||||||
.field("precision", "70m")
|
|
||||||
.field("distance_error_pct", "0.5")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.quadTreeLevelsForPrecision(70d)+1));
|
|
||||||
}
|
|
||||||
assertFieldWarnings("tree", "tree_levels", "precision", "distance_error_pct");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPointsOnlyOption() throws IOException {
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "geohash")
|
|
||||||
.field("points_only", true)
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
|
||||||
assertThat(strategy.isPointsOnly(), equalTo(true));
|
|
||||||
assertFieldWarnings("tree", "points_only");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testLevelDefaults() throws IOException {
|
|
||||||
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("distance_error_pct", "0.5")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
|
||||||
/* 50m is default */
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.quadTreeLevelsForPrecision(50d)));
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "geohash")
|
|
||||||
.field("distance_error_pct", "0.5")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.5));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
|
||||||
/* 50m is default */
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(50d)));
|
|
||||||
}
|
|
||||||
assertFieldWarnings("tree", "distance_error_pct");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testGeoShapeMapperMerge() throws Exception {
|
|
||||||
String stage1Mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type").startObject("properties")
|
|
||||||
.startObject("shape").field("type", "geo_shape").field("tree", "geohash")
|
|
||||||
.field("strategy", "recursive")
|
|
||||||
.field("precision", "1m").field("tree_levels", 8).field("distance_error_pct", 0.01)
|
|
||||||
.field("orientation", "ccw")
|
|
||||||
.endObject().endObject().endObject().endObject());
|
|
||||||
MapperService mapperService = createIndex("test").mapperService();
|
|
||||||
DocumentMapper docMapper = mapperService.merge("type", new CompressedXContent(stage1Mapping),
|
|
||||||
MapperService.MergeReason.MAPPING_UPDATE);
|
|
||||||
String stage2Mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
|
|
||||||
.startObject("properties").startObject("shape").field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("strategy", "term").field("precision", "1km")
|
|
||||||
.field("tree_levels", 26).field("distance_error_pct", 26)
|
|
||||||
.field("orientation", "cw").endObject().endObject().endObject().endObject());
|
|
||||||
try {
|
|
||||||
mapperService.merge("type", new CompressedXContent(stage2Mapping), MapperService.MergeReason.MAPPING_UPDATE);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
assertThat(e.getMessage(), containsString("mapper [shape] has different [strategy]"));
|
|
||||||
assertThat(e.getMessage(), containsString("mapper [shape] has different [tree]"));
|
|
||||||
assertThat(e.getMessage(), containsString("mapper [shape] has different [tree_levels]"));
|
|
||||||
assertThat(e.getMessage(), containsString("mapper [shape] has different [precision]"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify nothing changed
|
|
||||||
Mapper fieldMapper = docMapper.mappers().getMapper("shape");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy, instanceOf(RecursivePrefixTreeStrategy.class));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.01));
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(1d)));
|
|
||||||
assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(ShapeBuilder.Orientation.CCW));
|
|
||||||
|
|
||||||
// correct mapping
|
|
||||||
stage2Mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type")
|
|
||||||
.startObject("properties").startObject("shape").field("type", "geo_shape")
|
|
||||||
.field("tree", "geohash")
|
|
||||||
.field("strategy", "recursive")
|
|
||||||
.field("precision", "1m")
|
|
||||||
.field("tree_levels", 8).field("distance_error_pct", 0.001)
|
|
||||||
.field("orientation", "cw").endObject().endObject().endObject().endObject());
|
|
||||||
docMapper = mapperService.merge("type", new CompressedXContent(stage2Mapping), MapperService.MergeReason.MAPPING_UPDATE);
|
|
||||||
|
|
||||||
fieldMapper = docMapper.mappers().getMapper("shape");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy, instanceOf(RecursivePrefixTreeStrategy.class));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(GeohashPrefixTree.class));
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.001));
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(GeoUtils.geoHashLevelsForPrecision(1d)));
|
|
||||||
assertThat(geoShapeFieldMapper.fieldType().orientation(), equalTo(ShapeBuilder.Orientation.CW));
|
|
||||||
|
|
||||||
assertFieldWarnings("tree", "strategy", "precision", "tree_levels", "distance_error_pct");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testEmptyName() throws Exception {
|
|
||||||
// after 5.x
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
|
|
||||||
|
|
||||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
|
|
||||||
() -> parser.parse("type1", new CompressedXContent(mapping))
|
|
||||||
);
|
|
||||||
assertThat(e.getMessage(), containsString("name cannot be empty string"));
|
|
||||||
assertFieldWarnings("tree");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testSerializeDefaults() throws Exception {
|
|
||||||
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
String serialized = toXContentString((LegacyGeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
|
||||||
assertTrue(serialized, serialized.contains("\"precision\":\"50.0m\""));
|
|
||||||
assertTrue(serialized, serialized.contains("\"tree_levels\":21"));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "geohash")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
String serialized = toXContentString((LegacyGeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
|
||||||
assertTrue(serialized, serialized.contains("\"precision\":\"50.0m\""));
|
|
||||||
assertTrue(serialized, serialized.contains("\"tree_levels\":9"));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("tree_levels", "6")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
String serialized = toXContentString((LegacyGeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
|
||||||
assertFalse(serialized, serialized.contains("\"precision\":"));
|
|
||||||
assertTrue(serialized, serialized.contains("\"tree_levels\":6"));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("precision", "6")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
String serialized = toXContentString((LegacyGeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
|
||||||
assertTrue(serialized, serialized.contains("\"precision\":\"6.0m\""));
|
|
||||||
assertTrue(serialized, serialized.contains("\"tree_levels\":10"));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("precision", "6m")
|
|
||||||
.field("tree_levels", "5")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
DocumentMapper defaultMapper = parser.parse("type1", new CompressedXContent(mapping));
|
|
||||||
String serialized = toXContentString((LegacyGeoShapeFieldMapper) defaultMapper.mappers().getMapper("location"));
|
|
||||||
assertTrue(serialized, serialized.contains("\"precision\":\"6.0m\""));
|
|
||||||
assertTrue(serialized, serialized.contains("\"tree_levels\":5"));
|
|
||||||
}
|
|
||||||
assertFieldWarnings("tree", "tree_levels", "precision");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPointsOnlyDefaultsWithTermStrategy() throws IOException {
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("precision", "10m")
|
|
||||||
.field("strategy", "term")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapper defaultMapper = createIndex("test").mapperService().documentMapperParser()
|
|
||||||
.parse("type1", new CompressedXContent(mapping));
|
|
||||||
Mapper fieldMapper = defaultMapper.mappers().getMapper("location");
|
|
||||||
assertThat(fieldMapper, instanceOf(LegacyGeoShapeFieldMapper.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper geoShapeFieldMapper = (LegacyGeoShapeFieldMapper) fieldMapper;
|
|
||||||
PrefixTreeStrategy strategy = geoShapeFieldMapper.fieldType().defaultPrefixTreeStrategy();
|
|
||||||
|
|
||||||
assertThat(strategy.getDistErrPct(), equalTo(0.0));
|
|
||||||
assertThat(strategy.getGrid(), instanceOf(QuadPrefixTree.class));
|
|
||||||
assertThat(strategy.getGrid().getMaxLevels(), equalTo(23));
|
|
||||||
assertThat(strategy.isPointsOnly(), equalTo(true));
|
|
||||||
// term strategy changes the default for points_only, check that we handle it correctly
|
|
||||||
assertThat(toXContentString(geoShapeFieldMapper, false), not(containsString("points_only")));
|
|
||||||
assertFieldWarnings("tree", "precision", "strategy");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void testPointsOnlyFalseWithTermStrategy() throws Exception {
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("precision", "10m")
|
|
||||||
.field("strategy", "term")
|
|
||||||
.field("points_only", false)
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
DocumentMapperParser parser = createIndex("test").mapperService().documentMapperParser();
|
|
||||||
|
|
||||||
ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class,
|
|
||||||
() -> parser.parse("type1", new CompressedXContent(mapping))
|
|
||||||
);
|
|
||||||
assertThat(e.getMessage(), containsString("points_only cannot be set to false for term strategy"));
|
|
||||||
assertFieldWarnings("tree", "precision", "strategy", "points_only");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toXContentString(LegacyGeoShapeFieldMapper mapper, boolean includeDefaults) throws IOException {
|
|
||||||
XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
|
|
||||||
ToXContent.Params params;
|
|
||||||
if (includeDefaults) {
|
|
||||||
params = new ToXContent.MapParams(Collections.singletonMap("include_defaults", "true"));
|
|
||||||
} else {
|
|
||||||
params = ToXContent.EMPTY_PARAMS;
|
|
||||||
}
|
|
||||||
mapper.doXContentBody(builder, includeDefaults, params);
|
|
||||||
return Strings.toString(builder.endObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toXContentString(LegacyGeoShapeFieldMapper mapper) throws IOException {
|
|
||||||
return toXContentString(mapper, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch under one or more contributor
|
|
||||||
* license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright
|
|
||||||
* ownership. Elasticsearch licenses this file to you under
|
|
||||||
* the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
* not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.index.mapper;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
|
||||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper.GeoShapeFieldType;
|
|
||||||
import org.junit.Before;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class LegacyGeoShapeFieldTypeTests extends FieldTypeTestCase {
|
|
||||||
@Override
|
|
||||||
protected MappedFieldType createDefaultFieldType() {
|
|
||||||
return new GeoShapeFieldType();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setupProperties() {
|
|
||||||
addModifier(new Modifier("tree", false) {
|
|
||||||
@Override
|
|
||||||
public void modify(MappedFieldType ft) {
|
|
||||||
((GeoShapeFieldType)ft).setTree("geohash");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
addModifier(new Modifier("strategy", false) {
|
|
||||||
@Override
|
|
||||||
public void modify(MappedFieldType ft) {
|
|
||||||
((GeoShapeFieldType)ft).setStrategy(SpatialStrategy.TERM);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
addModifier(new Modifier("tree_levels", false) {
|
|
||||||
@Override
|
|
||||||
public void modify(MappedFieldType ft) {
|
|
||||||
((GeoShapeFieldType)ft).setTreeLevels(10);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
addModifier(new Modifier("precision", false) {
|
|
||||||
@Override
|
|
||||||
public void modify(MappedFieldType ft) {
|
|
||||||
((GeoShapeFieldType)ft).setPrecisionInMeters(20);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
addModifier(new Modifier("distance_error_pct", true) {
|
|
||||||
@Override
|
|
||||||
public void modify(MappedFieldType ft) {
|
|
||||||
((GeoShapeFieldType)ft).setDefaultDistanceErrorPct(0.5);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
addModifier(new Modifier("orientation", true) {
|
|
||||||
@Override
|
|
||||||
public void modify(MappedFieldType ft) {
|
|
||||||
((GeoShapeFieldType)ft).setOrientation(ShapeBuilder.Orientation.LEFT);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test for {@link LegacyGeoShapeFieldMapper.GeoShapeFieldType#setStrategy(SpatialStrategy)} that checks
|
|
||||||
* that {@link LegacyGeoShapeFieldMapper.GeoShapeFieldType#pointsOnly()} gets set as a side effect when using SpatialStrategy.TERM
|
|
||||||
*/
|
|
||||||
public void testSetStrategyName() throws IOException {
|
|
||||||
GeoShapeFieldType fieldType = new GeoShapeFieldType();
|
|
||||||
assertFalse(fieldType.pointsOnly());
|
|
||||||
fieldType.setStrategy(SpatialStrategy.RECURSIVE);
|
|
||||||
assertFalse(fieldType.pointsOnly());
|
|
||||||
fieldType.setStrategy(SpatialStrategy.TERM);
|
|
||||||
assertTrue(fieldType.pointsOnly());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,6 +16,7 @@
|
||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.elasticsearch.index.query;
|
package org.elasticsearch.index.query;
|
||||||
|
|
||||||
import org.apache.lucene.search.BooleanQuery;
|
import org.apache.lucene.search.BooleanQuery;
|
||||||
|
@ -28,6 +29,7 @@ import org.elasticsearch.action.get.GetResponse;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.bytes.BytesArray;
|
import org.elasticsearch.common.bytes.BytesArray;
|
||||||
import org.elasticsearch.common.geo.ShapeRelation;
|
import org.elasticsearch.common.geo.ShapeRelation;
|
||||||
|
import org.elasticsearch.common.geo.SpatialStrategy;
|
||||||
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
|
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||||
|
@ -52,41 +54,29 @@ import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQueryBuilder> {
|
public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQueryBuilder> {
|
||||||
|
|
||||||
protected static String indexedShapeId;
|
private static String indexedShapeId;
|
||||||
protected static String indexedShapeType;
|
private static String indexedShapeType;
|
||||||
protected static String indexedShapePath;
|
private static String indexedShapePath;
|
||||||
protected static String indexedShapeIndex;
|
private static String indexedShapeIndex;
|
||||||
protected static String indexedShapeRouting;
|
private static String indexedShapeRouting;
|
||||||
protected static ShapeBuilder<?, ?> indexedShapeToReturn;
|
private static ShapeBuilder<?, ?> indexedShapeToReturn;
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean enableWarningsCheck() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String fieldName() {
|
|
||||||
return GEO_SHAPE_FIELD_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected GeoShapeQueryBuilder doCreateTestQueryBuilder() {
|
protected GeoShapeQueryBuilder doCreateTestQueryBuilder() {
|
||||||
return doCreateTestQueryBuilder(randomBoolean());
|
return doCreateTestQueryBuilder(randomBoolean());
|
||||||
}
|
}
|
||||||
|
private GeoShapeQueryBuilder doCreateTestQueryBuilder(boolean indexedShape) {
|
||||||
protected GeoShapeQueryBuilder doCreateTestQueryBuilder(boolean indexedShape) {
|
ShapeType shapeType = ShapeType.randomType(random());
|
||||||
// LatLonShape does not support MultiPoint queries
|
|
||||||
RandomShapeGenerator.ShapeType shapeType =
|
|
||||||
randomFrom(ShapeType.POINT, ShapeType.LINESTRING, ShapeType.MULTILINESTRING, ShapeType.POLYGON);
|
|
||||||
ShapeBuilder<?, ?> shape = RandomShapeGenerator.createShapeWithin(random(), null, shapeType);
|
ShapeBuilder<?, ?> shape = RandomShapeGenerator.createShapeWithin(random(), null, shapeType);
|
||||||
GeoShapeQueryBuilder builder;
|
GeoShapeQueryBuilder builder;
|
||||||
clearShapeFields();
|
clearShapeFields();
|
||||||
if (indexedShape == false) {
|
if (indexedShape == false) {
|
||||||
builder = new GeoShapeQueryBuilder(fieldName(), shape);
|
builder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape);
|
||||||
} else {
|
} else {
|
||||||
indexedShapeToReturn = shape;
|
indexedShapeToReturn = shape;
|
||||||
indexedShapeId = randomAlphaOfLengthBetween(3, 20);
|
indexedShapeId = randomAlphaOfLengthBetween(3, 20);
|
||||||
indexedShapeType = randomAlphaOfLengthBetween(3, 20);
|
indexedShapeType = randomAlphaOfLengthBetween(3, 20);
|
||||||
builder = new GeoShapeQueryBuilder(fieldName(), indexedShapeId, indexedShapeType);
|
builder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, indexedShapeId, indexedShapeType);
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
indexedShapeIndex = randomAlphaOfLengthBetween(3, 20);
|
indexedShapeIndex = randomAlphaOfLengthBetween(3, 20);
|
||||||
builder.indexedShapeIndex(indexedShapeIndex);
|
builder.indexedShapeIndex(indexedShapeIndex);
|
||||||
|
@ -101,11 +91,15 @@ public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
if (shapeType == ShapeType.LINESTRING || shapeType == ShapeType.MULTILINESTRING) {
|
SpatialStrategy strategy = randomFrom(SpatialStrategy.values());
|
||||||
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS));
|
// ShapeType.MULTILINESTRING + SpatialStrategy.TERM can lead to large queries and will slow down tests, so
|
||||||
} else {
|
// we try to avoid that combination
|
||||||
// LatLonShape does not support CONTAINS:
|
while (shapeType == ShapeType.MULTILINESTRING && strategy == SpatialStrategy.TERM) {
|
||||||
builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.INTERSECTS, ShapeRelation.WITHIN));
|
strategy = randomFrom(SpatialStrategy.values());
|
||||||
|
}
|
||||||
|
builder.strategy(strategy);
|
||||||
|
if (strategy != SpatialStrategy.TERM) {
|
||||||
|
builder.relation(randomFrom(ShapeRelation.values()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,28 +161,41 @@ public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQue
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testNoShape() throws IOException {
|
public void testNoShape() throws IOException {
|
||||||
expectThrows(IllegalArgumentException.class, () -> new GeoShapeQueryBuilder(fieldName(), null));
|
expectThrows(IllegalArgumentException.class, () -> new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testNoIndexedShape() throws IOException {
|
public void testNoIndexedShape() throws IOException {
|
||||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
|
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
|
||||||
() -> new GeoShapeQueryBuilder(fieldName(), null, "type"));
|
() -> new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, null, "type"));
|
||||||
assertEquals("either shapeBytes or indexedShapeId and indexedShapeType are required", e.getMessage());
|
assertEquals("either shapeBytes or indexedShapeId and indexedShapeType are required", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testNoIndexedShapeType() throws IOException {
|
public void testNoIndexedShapeType() throws IOException {
|
||||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
|
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
|
||||||
() -> new GeoShapeQueryBuilder(fieldName(), "id", null));
|
() -> new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, "id", null));
|
||||||
assertEquals("indexedShapeType is required if indexedShapeId is specified", e.getMessage());
|
assertEquals("indexedShapeType is required if indexedShapeId is specified", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testNoRelation() throws IOException {
|
public void testNoRelation() throws IOException {
|
||||||
ShapeBuilder<?, ?> shape = RandomShapeGenerator.createShapeWithin(random(), null);
|
ShapeBuilder<?, ?> shape = RandomShapeGenerator.createShapeWithin(random(), null);
|
||||||
GeoShapeQueryBuilder builder = new GeoShapeQueryBuilder(fieldName(), shape);
|
GeoShapeQueryBuilder builder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape);
|
||||||
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> builder.relation(null));
|
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> builder.relation(null));
|
||||||
assertEquals("No Shape Relation defined", e.getMessage());
|
assertEquals("No Shape Relation defined", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testInvalidRelation() throws IOException {
|
||||||
|
ShapeBuilder<?, ?> shape = RandomShapeGenerator.createShapeWithin(random(), null);
|
||||||
|
GeoShapeQueryBuilder builder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape);
|
||||||
|
builder.strategy(SpatialStrategy.TERM);
|
||||||
|
expectThrows(IllegalArgumentException.class, () -> builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.WITHIN)));
|
||||||
|
GeoShapeQueryBuilder builder2 = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape);
|
||||||
|
builder2.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.WITHIN));
|
||||||
|
expectThrows(IllegalArgumentException.class, () -> builder2.strategy(SpatialStrategy.TERM));
|
||||||
|
GeoShapeQueryBuilder builder3 = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape);
|
||||||
|
builder3.strategy(SpatialStrategy.TERM);
|
||||||
|
expectThrows(IllegalArgumentException.class, () -> builder3.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.WITHIN)));
|
||||||
|
}
|
||||||
|
|
||||||
// see #3878
|
// see #3878
|
||||||
public void testThatXContentSerializationInsideOfArrayWorks() throws Exception {
|
public void testThatXContentSerializationInsideOfArrayWorks() throws Exception {
|
||||||
EnvelopeBuilder envelopeBuilder = new EnvelopeBuilder(new Coordinate(0, 0), new Coordinate(10, 10));
|
EnvelopeBuilder envelopeBuilder = new EnvelopeBuilder(new Coordinate(0, 0), new Coordinate(10, 10));
|
||||||
|
@ -198,7 +205,7 @@ public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQue
|
||||||
|
|
||||||
public void testFromJson() throws IOException {
|
public void testFromJson() throws IOException {
|
||||||
String json =
|
String json =
|
||||||
"{\n" +
|
"{\n" +
|
||||||
" \"geo_shape\" : {\n" +
|
" \"geo_shape\" : {\n" +
|
||||||
" \"location\" : {\n" +
|
" \"location\" : {\n" +
|
||||||
" \"shape\" : {\n" +
|
" \"shape\" : {\n" +
|
||||||
|
@ -223,7 +230,7 @@ public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQue
|
||||||
UnsupportedOperationException e = expectThrows(UnsupportedOperationException.class, () -> query.toQuery(createShardContext()));
|
UnsupportedOperationException e = expectThrows(UnsupportedOperationException.class, () -> query.toQuery(createShardContext()));
|
||||||
assertEquals("query must be rewritten first", e.getMessage());
|
assertEquals("query must be rewritten first", e.getMessage());
|
||||||
QueryBuilder rewrite = rewriteAndFetch(query, createShardContext());
|
QueryBuilder rewrite = rewriteAndFetch(query, createShardContext());
|
||||||
GeoShapeQueryBuilder geoShapeQueryBuilder = new GeoShapeQueryBuilder(fieldName(), indexedShapeToReturn);
|
GeoShapeQueryBuilder geoShapeQueryBuilder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, indexedShapeToReturn);
|
||||||
geoShapeQueryBuilder.strategy(query.strategy());
|
geoShapeQueryBuilder.strategy(query.strategy());
|
||||||
geoShapeQueryBuilder.relation(query.relation());
|
geoShapeQueryBuilder.relation(query.relation());
|
||||||
assertEquals(geoShapeQueryBuilder, rewrite);
|
assertEquals(geoShapeQueryBuilder, rewrite);
|
||||||
|
@ -237,7 +244,7 @@ public class GeoShapeQueryBuilderTests extends AbstractQueryTestCase<GeoShapeQue
|
||||||
|
|
||||||
builder = rewriteAndFetch(builder, createShardContext());
|
builder = rewriteAndFetch(builder, createShardContext());
|
||||||
|
|
||||||
GeoShapeQueryBuilder expectedShape = new GeoShapeQueryBuilder(fieldName(), indexedShapeToReturn);
|
GeoShapeQueryBuilder expectedShape = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, indexedShapeToReturn);
|
||||||
expectedShape.strategy(shape.strategy());
|
expectedShape.strategy(shape.strategy());
|
||||||
expectedShape.relation(shape.relation());
|
expectedShape.relation(shape.relation());
|
||||||
QueryBuilder expected = new BoolQueryBuilder()
|
QueryBuilder expected = new BoolQueryBuilder()
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch under one or more contributor
|
|
||||||
* license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright
|
|
||||||
* ownership. Elasticsearch licenses this file to you under
|
|
||||||
* the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
* not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.elasticsearch.index.query;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.geo.ShapeRelation;
|
|
||||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
|
||||||
import org.elasticsearch.test.geo.RandomShapeGenerator;
|
|
||||||
import org.elasticsearch.test.geo.RandomShapeGenerator.ShapeType;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class LegacyGeoShapeFieldQueryTests extends GeoShapeQueryBuilderTests {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String fieldName() {
|
|
||||||
return LEGACY_GEO_SHAPE_FIELD_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GeoShapeQueryBuilder doCreateTestQueryBuilder(boolean indexedShape) {
|
|
||||||
ShapeType shapeType = ShapeType.randomType(random());
|
|
||||||
ShapeBuilder<?, ?> shape = RandomShapeGenerator.createShapeWithin(random(), null, shapeType);
|
|
||||||
GeoShapeQueryBuilder builder;
|
|
||||||
clearShapeFields();
|
|
||||||
if (indexedShape == false) {
|
|
||||||
builder = new GeoShapeQueryBuilder(fieldName(), shape);
|
|
||||||
} else {
|
|
||||||
indexedShapeToReturn = shape;
|
|
||||||
indexedShapeId = randomAlphaOfLengthBetween(3, 20);
|
|
||||||
indexedShapeType = randomAlphaOfLengthBetween(3, 20);
|
|
||||||
builder = new GeoShapeQueryBuilder(fieldName(), indexedShapeId, indexedShapeType);
|
|
||||||
if (randomBoolean()) {
|
|
||||||
indexedShapeIndex = randomAlphaOfLengthBetween(3, 20);
|
|
||||||
builder.indexedShapeIndex(indexedShapeIndex);
|
|
||||||
}
|
|
||||||
if (randomBoolean()) {
|
|
||||||
indexedShapePath = randomAlphaOfLengthBetween(3, 20);
|
|
||||||
builder.indexedShapePath(indexedShapePath);
|
|
||||||
}
|
|
||||||
if (randomBoolean()) {
|
|
||||||
indexedShapeRouting = randomAlphaOfLengthBetween(3, 20);
|
|
||||||
builder.indexedShapeRouting(indexedShapeRouting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (randomBoolean()) {
|
|
||||||
SpatialStrategy strategy = randomFrom(SpatialStrategy.values());
|
|
||||||
// ShapeType.MULTILINESTRING + SpatialStrategy.TERM can lead to large queries and will slow down tests, so
|
|
||||||
// we try to avoid that combination
|
|
||||||
while (shapeType == ShapeType.MULTILINESTRING && strategy == SpatialStrategy.TERM) {
|
|
||||||
strategy = randomFrom(SpatialStrategy.values());
|
|
||||||
}
|
|
||||||
builder.strategy(strategy);
|
|
||||||
if (strategy != SpatialStrategy.TERM) {
|
|
||||||
builder.relation(randomFrom(ShapeRelation.values()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (randomBoolean()) {
|
|
||||||
builder.ignoreUnmapped(randomBoolean());
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testInvalidRelation() throws IOException {
|
|
||||||
ShapeBuilder<?, ?> shape = RandomShapeGenerator.createShapeWithin(random(), null);
|
|
||||||
GeoShapeQueryBuilder builder = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape);
|
|
||||||
builder.strategy(SpatialStrategy.TERM);
|
|
||||||
expectThrows(IllegalArgumentException.class, () -> builder.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.WITHIN)));
|
|
||||||
GeoShapeQueryBuilder builder2 = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape);
|
|
||||||
builder2.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.WITHIN));
|
|
||||||
expectThrows(IllegalArgumentException.class, () -> builder2.strategy(SpatialStrategy.TERM));
|
|
||||||
GeoShapeQueryBuilder builder3 = new GeoShapeQueryBuilder(GEO_SHAPE_FIELD_NAME, shape);
|
|
||||||
builder3.strategy(SpatialStrategy.TERM);
|
|
||||||
expectThrows(IllegalArgumentException.class, () -> builder3.relation(randomFrom(ShapeRelation.DISJOINT, ShapeRelation.WITHIN)));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -62,7 +62,6 @@ import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
|
||||||
public class MatchQueryBuilderTests extends AbstractQueryTestCase<MatchQueryBuilder> {
|
public class MatchQueryBuilderTests extends AbstractQueryTestCase<MatchQueryBuilder> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected MatchQueryBuilder doCreateTestQueryBuilder() {
|
protected MatchQueryBuilder doCreateTestQueryBuilder() {
|
||||||
String fieldName = randomFrom(STRING_FIELD_NAME, STRING_ALIAS_FIELD_NAME, BOOLEAN_FIELD_NAME, INT_FIELD_NAME,
|
String fieldName = randomFrom(STRING_FIELD_NAME, STRING_ALIAS_FIELD_NAME, BOOLEAN_FIELD_NAME, INT_FIELD_NAME,
|
||||||
|
|
|
@ -1048,12 +1048,6 @@ public class QueryStringQueryBuilderTests extends AbstractQueryTestCase<QueryStr
|
||||||
"_field_names", "enabled=true"))),
|
"_field_names", "enabled=true"))),
|
||||||
MapperService.MergeReason.MAPPING_UPDATE);
|
MapperService.MergeReason.MAPPING_UPDATE);
|
||||||
}
|
}
|
||||||
assertWarnings(new String[] {
|
|
||||||
"Field parameter [tree_levels] is deprecated and will be removed in a future version.",
|
|
||||||
"Field parameter [precision] is deprecated and will be removed in a future version.",
|
|
||||||
"Field parameter [strategy] is deprecated and will be removed in a future version.",
|
|
||||||
"Field parameter [distance_error_pct] is deprecated and will be removed in a future version."
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -380,7 +380,6 @@ public class GeoFilterIT extends ESIntegTestCase {
|
||||||
.endObject()
|
.endObject()
|
||||||
.startObject("location")
|
.startObject("location")
|
||||||
.field("type", "geo_shape")
|
.field("type", "geo_shape")
|
||||||
.field("ignore_malformed", true)
|
|
||||||
.endObject()
|
.endObject()
|
||||||
.endObject()
|
.endObject()
|
||||||
.endObject()
|
.endObject()
|
||||||
|
|
|
@ -45,21 +45,21 @@ public class GeoShapeIntegrationIT extends ESIntegTestCase {
|
||||||
public void testOrientationPersistence() throws Exception {
|
public void testOrientationPersistence() throws Exception {
|
||||||
String idxName = "orientation";
|
String idxName = "orientation";
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("shape")
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("shape")
|
||||||
.startObject("properties").startObject("location")
|
.startObject("properties").startObject("location")
|
||||||
.field("type", "geo_shape")
|
.field("type", "geo_shape")
|
||||||
.field("orientation", "left")
|
.field("orientation", "left")
|
||||||
.endObject().endObject()
|
.endObject().endObject()
|
||||||
.endObject().endObject());
|
.endObject().endObject());
|
||||||
|
|
||||||
// create index
|
// create index
|
||||||
assertAcked(prepareCreate(idxName).addMapping("shape", mapping, XContentType.JSON));
|
assertAcked(prepareCreate(idxName).addMapping("shape", mapping, XContentType.JSON));
|
||||||
|
|
||||||
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("shape")
|
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("shape")
|
||||||
.startObject("properties").startObject("location")
|
.startObject("properties").startObject("location")
|
||||||
.field("type", "geo_shape")
|
.field("type", "geo_shape")
|
||||||
.field("orientation", "right")
|
.field("orientation", "right")
|
||||||
.endObject().endObject()
|
.endObject().endObject()
|
||||||
.endObject().endObject());
|
.endObject().endObject());
|
||||||
|
|
||||||
assertAcked(prepareCreate(idxName+"2").addMapping("shape", mapping, XContentType.JSON));
|
assertAcked(prepareCreate(idxName+"2").addMapping("shape", mapping, XContentType.JSON));
|
||||||
ensureGreen(idxName, idxName+"2");
|
ensureGreen(idxName, idxName+"2");
|
||||||
|
@ -144,8 +144,9 @@ public class GeoShapeIntegrationIT extends ESIntegTestCase {
|
||||||
|
|
||||||
String source = "{\n" +
|
String source = "{\n" +
|
||||||
" \"shape\" : {\n" +
|
" \"shape\" : {\n" +
|
||||||
" \"type\" : \"bbox\",\n" +
|
" \"type\" : \"circle\",\n" +
|
||||||
" \"coordinates\" : [[-45.0, 45.0], [45.0, -45.0]]\n" +
|
" \"coordinates\" : [-45.0, 45.0],\n" +
|
||||||
|
" \"radius\" : \"100m\"\n" +
|
||||||
" }\n" +
|
" }\n" +
|
||||||
"}";
|
"}";
|
||||||
|
|
||||||
|
|
|
@ -19,21 +19,16 @@
|
||||||
|
|
||||||
package org.elasticsearch.search.geo;
|
package org.elasticsearch.search.geo;
|
||||||
|
|
||||||
import com.carrotsearch.randomizedtesting.generators.RandomNumbers;
|
|
||||||
import org.apache.lucene.geo.GeoTestUtil;
|
|
||||||
import org.elasticsearch.action.get.GetResponse;
|
import org.elasticsearch.action.get.GetResponse;
|
||||||
import org.elasticsearch.action.index.IndexRequest;
|
import org.elasticsearch.action.index.IndexRequest;
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
import org.elasticsearch.action.search.SearchResponse;
|
||||||
import org.elasticsearch.common.CheckedSupplier;
|
import org.elasticsearch.common.CheckedSupplier;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.geo.ShapeRelation;
|
import org.elasticsearch.common.geo.ShapeRelation;
|
||||||
import org.elasticsearch.common.geo.SpatialStrategy;
|
|
||||||
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
|
import org.elasticsearch.common.geo.builders.CoordinatesBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
|
import org.elasticsearch.common.geo.builders.EnvelopeBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
|
import org.elasticsearch.common.geo.builders.GeometryCollectionBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.LineStringBuilder;
|
import org.elasticsearch.common.geo.builders.LineStringBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.MultiPointBuilder;
|
|
||||||
import org.elasticsearch.common.geo.builders.PointBuilder;
|
|
||||||
import org.elasticsearch.common.geo.builders.PolygonBuilder;
|
import org.elasticsearch.common.geo.builders.PolygonBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
@ -41,9 +36,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
|
||||||
import org.elasticsearch.index.mapper.MapperParsingException;
|
import org.elasticsearch.index.mapper.MapperParsingException;
|
||||||
import org.elasticsearch.index.query.ExistsQueryBuilder;
|
|
||||||
import org.elasticsearch.index.query.GeoShapeQueryBuilder;
|
import org.elasticsearch.index.query.GeoShapeQueryBuilder;
|
||||||
import org.elasticsearch.index.query.QueryBuilders;
|
import org.elasticsearch.index.query.QueryBuilders;
|
||||||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||||
|
@ -70,26 +63,12 @@ import static org.hamcrest.Matchers.greaterThan;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
|
||||||
public class GeoShapeQueryTests extends ESSingleNodeTestCase {
|
public class GeoShapeQueryTests extends ESSingleNodeTestCase {
|
||||||
private static final String[] PREFIX_TREES = new String[] {
|
|
||||||
LegacyGeoShapeFieldMapper.DeprecatedParameters.PrefixTrees.GEOHASH,
|
|
||||||
LegacyGeoShapeFieldMapper.DeprecatedParameters.PrefixTrees.QUADTREE
|
|
||||||
};
|
|
||||||
|
|
||||||
private XContentBuilder createMapping() throws Exception {
|
|
||||||
XContentBuilder xcb = XContentFactory.jsonBuilder().startObject().startObject("type1")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape");
|
|
||||||
if (randomBoolean()) {
|
|
||||||
xcb = xcb.field("tree", randomFrom(PREFIX_TREES))
|
|
||||||
.field("strategy", randomFrom(SpatialStrategy.RECURSIVE, SpatialStrategy.TERM));
|
|
||||||
}
|
|
||||||
xcb = xcb.endObject().endObject().endObject().endObject();
|
|
||||||
|
|
||||||
return xcb;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testNullShape() throws Exception {
|
public void testNullShape() throws Exception {
|
||||||
String mapping = Strings.toString(createMapping());
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get();
|
client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get();
|
||||||
ensureGreen();
|
ensureGreen();
|
||||||
|
|
||||||
|
@ -100,7 +79,12 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testIndexPointsFilterRectangle() throws Exception {
|
public void testIndexPointsFilterRectangle() throws Exception {
|
||||||
String mapping = Strings.toString(createMapping());
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get();
|
client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get();
|
||||||
ensureGreen();
|
ensureGreen();
|
||||||
|
|
||||||
|
@ -142,11 +126,12 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEdgeCases() throws Exception {
|
public void testEdgeCases() throws Exception {
|
||||||
XContentBuilder xcb = XContentFactory.jsonBuilder().startObject().startObject("type1")
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
.startObject("properties").startObject("location")
|
.startObject("properties").startObject("location")
|
||||||
.field("type", "geo_shape")
|
.field("type", "geo_shape")
|
||||||
.endObject().endObject().endObject().endObject();
|
.field("tree", "quadtree")
|
||||||
String mapping = Strings.toString(xcb);
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get();
|
client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get();
|
||||||
ensureGreen();
|
ensureGreen();
|
||||||
|
|
||||||
|
@ -178,7 +163,12 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testIndexedShapeReference() throws Exception {
|
public void testIndexedShapeReference() throws Exception {
|
||||||
String mapping = Strings.toString(createMapping());
|
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("type1")
|
||||||
|
.startObject("properties").startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.endObject().endObject()
|
||||||
|
.endObject().endObject());
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get();
|
client().admin().indices().prepareCreate("test").addMapping("type1", mapping, XContentType.JSON).get();
|
||||||
createIndex("shapes");
|
createIndex("shapes");
|
||||||
ensureGreen();
|
ensureGreen();
|
||||||
|
@ -215,7 +205,14 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testIndexedShapeReferenceSourceDisabled() throws Exception {
|
public void testIndexedShapeReferenceSourceDisabled() throws Exception {
|
||||||
XContentBuilder mapping = createMapping();
|
XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
|
||||||
|
.startObject("properties")
|
||||||
|
.startObject("location")
|
||||||
|
.field("type", "geo_shape")
|
||||||
|
.field("tree", "quadtree")
|
||||||
|
.endObject()
|
||||||
|
.endObject()
|
||||||
|
.endObject();
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type1", mapping).get();
|
client().admin().indices().prepareCreate("test").addMapping("type1", mapping).get();
|
||||||
createIndex("shapes", Settings.EMPTY, "shape_type", "_source", "enabled=false");
|
createIndex("shapes", Settings.EMPTY, "shape_type", "_source", "enabled=false");
|
||||||
ensureGreen();
|
ensureGreen();
|
||||||
|
@ -329,107 +326,24 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
|
||||||
assertHitCount(result, 1);
|
assertHitCount(result, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testQueryRandomGeoCollection() throws Exception {
|
public void testShapeFilterWithRandomGeoCollection() throws Exception {
|
||||||
// Create a random geometry collection.
|
// Create a random geometry collection.
|
||||||
GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random());
|
GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random());
|
||||||
org.apache.lucene.geo.Polygon randomPoly = GeoTestUtil.nextPolygon();
|
|
||||||
CoordinatesBuilder cb = new CoordinatesBuilder();
|
|
||||||
for (int i = 0; i < randomPoly.numPoints(); ++i) {
|
|
||||||
cb.coordinate(randomPoly.getPolyLon(i), randomPoly.getPolyLat(i));
|
|
||||||
}
|
|
||||||
gcb.shape(new PolygonBuilder(cb));
|
|
||||||
|
|
||||||
logger.info("Created Random GeometryCollection containing {} shapes", gcb.numShapes());
|
logger.info("Created Random GeometryCollection containing {} shapes", gcb.numShapes());
|
||||||
|
|
||||||
if (randomBoolean()) {
|
client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree")
|
||||||
client().admin().indices().prepareCreate("test")
|
.get();
|
||||||
.addMapping("type", "location", "type=geo_shape").get();
|
|
||||||
} else {
|
|
||||||
client().admin().indices().prepareCreate("test")
|
|
||||||
.addMapping("type", "location", "type=geo_shape,tree=quadtree").get();
|
|
||||||
}
|
|
||||||
|
|
||||||
XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("location"), null).endObject();
|
XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("location"), null).endObject();
|
||||||
client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get();
|
client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get();
|
||||||
|
|
||||||
ShapeBuilder filterShape = (gcb.getShapeAt(gcb.numShapes() - 1));
|
ShapeBuilder filterShape = (gcb.getShapeAt(randomIntBetween(0, gcb.numShapes() - 1)));
|
||||||
|
|
||||||
GeoShapeQueryBuilder geoShapeQueryBuilder = QueryBuilders.geoShapeQuery("location", filterShape);
|
GeoShapeQueryBuilder filter = QueryBuilders.geoShapeQuery("location", filterShape);
|
||||||
geoShapeQueryBuilder.relation(ShapeRelation.INTERSECTS);
|
filter.relation(ShapeRelation.INTERSECTS);
|
||||||
SearchResponse result = client().prepareSearch("test").setTypes("type").setQuery(geoShapeQueryBuilder).get();
|
SearchResponse result = client().prepareSearch("test").setTypes("type").setQuery(QueryBuilders.matchAllQuery())
|
||||||
assertSearchResponse(result);
|
.setPostFilter(filter).get();
|
||||||
assertHitCount(result, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testRandomGeoCollectionQuery() throws Exception {
|
|
||||||
boolean usePrefixTrees = randomBoolean();
|
|
||||||
// Create a random geometry collection to index.
|
|
||||||
GeometryCollectionBuilder gcb;
|
|
||||||
if (usePrefixTrees) {
|
|
||||||
gcb = RandomShapeGenerator.createGeometryCollection(random());
|
|
||||||
} else {
|
|
||||||
// vector strategy does not yet support multipoint queries
|
|
||||||
gcb = new GeometryCollectionBuilder();
|
|
||||||
int numShapes = RandomNumbers.randomIntBetween(random(), 1, 4);
|
|
||||||
for (int i = 0; i < numShapes; ++i) {
|
|
||||||
ShapeBuilder shape;
|
|
||||||
do {
|
|
||||||
shape = RandomShapeGenerator.createShape(random());
|
|
||||||
} while (shape instanceof MultiPointBuilder);
|
|
||||||
gcb.shape(shape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
org.apache.lucene.geo.Polygon randomPoly = GeoTestUtil.nextPolygon();
|
|
||||||
CoordinatesBuilder cb = new CoordinatesBuilder();
|
|
||||||
for (int i = 0; i < randomPoly.numPoints(); ++i) {
|
|
||||||
cb.coordinate(randomPoly.getPolyLon(i), randomPoly.getPolyLat(i));
|
|
||||||
}
|
|
||||||
gcb.shape(new PolygonBuilder(cb));
|
|
||||||
|
|
||||||
logger.info("Created Random GeometryCollection containing {} shapes", gcb.numShapes());
|
|
||||||
|
|
||||||
if (usePrefixTrees == false) {
|
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape")
|
|
||||||
.execute().actionGet();
|
|
||||||
} else {
|
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree")
|
|
||||||
.execute().actionGet();
|
|
||||||
}
|
|
||||||
|
|
||||||
XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("location"), null).endObject();
|
|
||||||
client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get();
|
|
||||||
|
|
||||||
// Create a random geometry collection to query
|
|
||||||
GeometryCollectionBuilder queryCollection = RandomShapeGenerator.createGeometryCollection(random());
|
|
||||||
queryCollection.shape(new PolygonBuilder(cb));
|
|
||||||
|
|
||||||
GeoShapeQueryBuilder geoShapeQueryBuilder = QueryBuilders.geoShapeQuery("location", queryCollection);
|
|
||||||
geoShapeQueryBuilder.relation(ShapeRelation.INTERSECTS);
|
|
||||||
SearchResponse result = client().prepareSearch("test").setTypes("type").setQuery(geoShapeQueryBuilder).get();
|
|
||||||
assertSearchResponse(result);
|
|
||||||
assertTrue(result.getHits().getTotalHits().value > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** tests querying a random geometry collection with a point */
|
|
||||||
public void testPointQuery() throws Exception {
|
|
||||||
// Create a random geometry collection to index.
|
|
||||||
GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random());
|
|
||||||
double[] pt = new double[] {GeoTestUtil.nextLongitude(), GeoTestUtil.nextLatitude()};
|
|
||||||
PointBuilder pb = new PointBuilder(pt[0], pt[1]);
|
|
||||||
gcb.shape(pb);
|
|
||||||
if (randomBoolean()) {
|
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape")
|
|
||||||
.execute().actionGet();
|
|
||||||
} else {
|
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree")
|
|
||||||
.execute().actionGet();
|
|
||||||
}
|
|
||||||
XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("location"), null).endObject();
|
|
||||||
client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get();
|
|
||||||
|
|
||||||
GeoShapeQueryBuilder geoShapeQueryBuilder = QueryBuilders.geoShapeQuery("location", pb);
|
|
||||||
geoShapeQueryBuilder.relation(ShapeRelation.INTERSECTS);
|
|
||||||
SearchResponse result = client().prepareSearch("test").setTypes("type").setQuery(geoShapeQueryBuilder).get();
|
|
||||||
assertSearchResponse(result);
|
assertSearchResponse(result);
|
||||||
assertHitCount(result, 1);
|
assertHitCount(result, 1);
|
||||||
}
|
}
|
||||||
|
@ -461,28 +375,6 @@ public class GeoShapeQueryTests extends ESSingleNodeTestCase {
|
||||||
assertThat(response.getHits().getTotalHits().value, greaterThan(0L));
|
assertThat(response.getHits().getTotalHits().value, greaterThan(0L));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testExistsQuery() throws Exception {
|
|
||||||
// Create a random geometry collection.
|
|
||||||
GeometryCollectionBuilder gcb = RandomShapeGenerator.createGeometryCollection(random());
|
|
||||||
logger.info("Created Random GeometryCollection containing {} shapes", gcb.numShapes());
|
|
||||||
|
|
||||||
if (randomBoolean()) {
|
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape")
|
|
||||||
.execute().actionGet();
|
|
||||||
} else {
|
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree")
|
|
||||||
.execute().actionGet();
|
|
||||||
}
|
|
||||||
|
|
||||||
XContentBuilder docSource = gcb.toXContent(jsonBuilder().startObject().field("location"), null).endObject();
|
|
||||||
client().prepareIndex("test", "type", "1").setSource(docSource).setRefreshPolicy(IMMEDIATE).get();
|
|
||||||
|
|
||||||
ExistsQueryBuilder eqb = QueryBuilders.existsQuery("location");
|
|
||||||
SearchResponse result = client().prepareSearch("test").setTypes("type").setQuery(eqb).get();
|
|
||||||
assertSearchResponse(result);
|
|
||||||
assertHitCount(result, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testShapeFilterWithDefinedGeoCollection() throws Exception {
|
public void testShapeFilterWithDefinedGeoCollection() throws Exception {
|
||||||
createIndex("shapes");
|
createIndex("shapes");
|
||||||
client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree")
|
client().admin().indices().prepareCreate("test").addMapping("type", "location", "type=geo_shape,tree=quadtree")
|
||||||
|
|
|
@ -1,170 +0,0 @@
|
||||||
/*
|
|
||||||
* Licensed to Elasticsearch under one or more contributor
|
|
||||||
* license agreements. See the NOTICE file distributed with
|
|
||||||
* this work for additional information regarding copyright
|
|
||||||
* ownership. Elasticsearch licenses this file to you under
|
|
||||||
* the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
* not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing,
|
|
||||||
* software distributed under the License is distributed on an
|
|
||||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
* KIND, either express or implied. See the License for the
|
|
||||||
* specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.search.geo;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.search.SearchResponse;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
|
|
||||||
import org.elasticsearch.common.Strings;
|
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
|
||||||
import org.elasticsearch.index.IndexService;
|
|
||||||
import org.elasticsearch.index.mapper.LegacyGeoShapeFieldMapper;
|
|
||||||
import org.elasticsearch.index.mapper.MappedFieldType;
|
|
||||||
import org.elasticsearch.indices.IndicesService;
|
|
||||||
import org.elasticsearch.test.ESIntegTestCase;
|
|
||||||
|
|
||||||
import static org.elasticsearch.index.query.QueryBuilders.geoShapeQuery;
|
|
||||||
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
|
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
|
||||||
|
|
||||||
public class LegacyGeoShapeIntegrationIT extends ESIntegTestCase {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that orientation parameter correctly persists across cluster restart
|
|
||||||
*/
|
|
||||||
public void testOrientationPersistence() throws Exception {
|
|
||||||
String idxName = "orientation";
|
|
||||||
String mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("shape")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("orientation", "left")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
// create index
|
|
||||||
assertAcked(prepareCreate(idxName).addMapping("shape", mapping, XContentType.JSON));
|
|
||||||
|
|
||||||
mapping = Strings.toString(XContentFactory.jsonBuilder().startObject().startObject("shape")
|
|
||||||
.startObject("properties").startObject("location")
|
|
||||||
.field("type", "geo_shape")
|
|
||||||
.field("tree", "quadtree")
|
|
||||||
.field("orientation", "right")
|
|
||||||
.endObject().endObject()
|
|
||||||
.endObject().endObject());
|
|
||||||
|
|
||||||
assertAcked(prepareCreate(idxName+"2").addMapping("shape", mapping, XContentType.JSON));
|
|
||||||
ensureGreen(idxName, idxName+"2");
|
|
||||||
|
|
||||||
internalCluster().fullRestart();
|
|
||||||
ensureGreen(idxName, idxName+"2");
|
|
||||||
|
|
||||||
// left orientation test
|
|
||||||
IndicesService indicesService = internalCluster().getInstance(IndicesService.class, findNodeName(idxName));
|
|
||||||
IndexService indexService = indicesService.indexService(resolveIndex(idxName));
|
|
||||||
MappedFieldType fieldType = indexService.mapperService().fullName("location");
|
|
||||||
assertThat(fieldType, instanceOf(LegacyGeoShapeFieldMapper.GeoShapeFieldType.class));
|
|
||||||
|
|
||||||
LegacyGeoShapeFieldMapper.GeoShapeFieldType gsfm = (LegacyGeoShapeFieldMapper.GeoShapeFieldType)fieldType;
|
|
||||||
ShapeBuilder.Orientation orientation = gsfm.orientation();
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.CLOCKWISE));
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.LEFT));
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.CW));
|
|
||||||
|
|
||||||
// right orientation test
|
|
||||||
indicesService = internalCluster().getInstance(IndicesService.class, findNodeName(idxName+"2"));
|
|
||||||
indexService = indicesService.indexService(resolveIndex((idxName+"2")));
|
|
||||||
fieldType = indexService.mapperService().fullName("location");
|
|
||||||
assertThat(fieldType, instanceOf(LegacyGeoShapeFieldMapper.GeoShapeFieldType.class));
|
|
||||||
|
|
||||||
gsfm = (LegacyGeoShapeFieldMapper.GeoShapeFieldType)fieldType;
|
|
||||||
orientation = gsfm.orientation();
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.COUNTER_CLOCKWISE));
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.RIGHT));
|
|
||||||
assertThat(orientation, equalTo(ShapeBuilder.Orientation.CCW));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that ignore_malformed on GeoShapeFieldMapper does not fail the entire document
|
|
||||||
*/
|
|
||||||
public void testIgnoreMalformed() throws Exception {
|
|
||||||
// create index
|
|
||||||
assertAcked(client().admin().indices().prepareCreate("test")
|
|
||||||
.addMapping("geometry", "shape", "type=geo_shape,tree=quadtree,ignore_malformed=true").get());
|
|
||||||
ensureGreen();
|
|
||||||
|
|
||||||
// test self crossing ccw poly not crossing dateline
|
|
||||||
String polygonGeoJson = Strings.toString(XContentFactory.jsonBuilder().startObject().field("type", "Polygon")
|
|
||||||
.startArray("coordinates")
|
|
||||||
.startArray()
|
|
||||||
.startArray().value(176.0).value(15.0).endArray()
|
|
||||||
.startArray().value(-177.0).value(10.0).endArray()
|
|
||||||
.startArray().value(-177.0).value(-10.0).endArray()
|
|
||||||
.startArray().value(176.0).value(-15.0).endArray()
|
|
||||||
.startArray().value(-177.0).value(15.0).endArray()
|
|
||||||
.startArray().value(172.0).value(0.0).endArray()
|
|
||||||
.startArray().value(176.0).value(15.0).endArray()
|
|
||||||
.endArray()
|
|
||||||
.endArray()
|
|
||||||
.endObject());
|
|
||||||
|
|
||||||
indexRandom(true, client().prepareIndex("test", "geometry", "0").setSource("shape",
|
|
||||||
polygonGeoJson));
|
|
||||||
SearchResponse searchResponse = client().prepareSearch("test").setQuery(matchAllQuery()).get();
|
|
||||||
assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that the indexed shape routing can be provided if it is required
|
|
||||||
*/
|
|
||||||
public void testIndexShapeRouting() throws Exception {
|
|
||||||
String mapping = "{\n" +
|
|
||||||
" \"_routing\": {\n" +
|
|
||||||
" \"required\": true\n" +
|
|
||||||
" },\n" +
|
|
||||||
" \"properties\": {\n" +
|
|
||||||
" \"shape\": {\n" +
|
|
||||||
" \"type\": \"geo_shape\",\n" +
|
|
||||||
" \"tree\" : \"quadtree\"\n" +
|
|
||||||
" }\n" +
|
|
||||||
" }\n" +
|
|
||||||
" }";
|
|
||||||
|
|
||||||
|
|
||||||
// create index
|
|
||||||
assertAcked(client().admin().indices().prepareCreate("test").addMapping("doc", mapping, XContentType.JSON).get());
|
|
||||||
ensureGreen();
|
|
||||||
|
|
||||||
String source = "{\n" +
|
|
||||||
" \"shape\" : {\n" +
|
|
||||||
" \"type\" : \"bbox\",\n" +
|
|
||||||
" \"coordinates\" : [[-45.0, 45.0], [45.0, -45.0]]\n" +
|
|
||||||
" }\n" +
|
|
||||||
"}";
|
|
||||||
|
|
||||||
indexRandom(true, client().prepareIndex("test", "doc", "0").setSource(source, XContentType.JSON).setRouting("ABC"));
|
|
||||||
|
|
||||||
SearchResponse searchResponse = client().prepareSearch("test").setQuery(
|
|
||||||
geoShapeQuery("shape", "0", "doc").indexedShapeIndex("test").indexedShapeRouting("ABC")
|
|
||||||
).get();
|
|
||||||
|
|
||||||
assertThat(searchResponse.getHits().getTotalHits().value, equalTo(1L));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String findNodeName(String index) {
|
|
||||||
ClusterState state = client().admin().cluster().prepareState().get().getState();
|
|
||||||
IndexShardRoutingTable shard = state.getRoutingTable().index(index).shard(0);
|
|
||||||
String nodeId = shard.assignedShards().get(0).currentNodeId();
|
|
||||||
return state.getNodes().get(nodeId).getName();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,7 +32,6 @@ import org.elasticsearch.common.geo.builders.MultiPointBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.PointBuilder;
|
import org.elasticsearch.common.geo.builders.PointBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.PolygonBuilder;
|
import org.elasticsearch.common.geo.builders.PolygonBuilder;
|
||||||
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
import org.elasticsearch.common.geo.builders.ShapeBuilder;
|
||||||
import org.elasticsearch.search.geo.GeoShapeQueryTests;
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
|
import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
|
||||||
import org.locationtech.spatial4j.distance.DistanceUtils;
|
import org.locationtech.spatial4j.distance.DistanceUtils;
|
||||||
|
@ -154,7 +153,6 @@ public class RandomShapeGenerator extends RandomGeoGenerator {
|
||||||
/**
|
/**
|
||||||
* Creates a random shape useful for randomized testing, NOTE: exercise caution when using this to build random GeometryCollections
|
* Creates a random shape useful for randomized testing, NOTE: exercise caution when using this to build random GeometryCollections
|
||||||
* as creating a large random number of random shapes can result in massive resource consumption
|
* as creating a large random number of random shapes can result in massive resource consumption
|
||||||
* see: {@link GeoShapeQueryTests#testQueryRandomGeoCollection()}
|
|
||||||
*
|
*
|
||||||
* The following options are included
|
* The following options are included
|
||||||
* @param nearPoint Create a shape near a provided point
|
* @param nearPoint Create a shape near a provided point
|
||||||
|
|
|
@ -113,7 +113,6 @@ public abstract class AbstractBuilderTestCase extends ESTestCase {
|
||||||
protected static final String GEO_POINT_FIELD_NAME = "mapped_geo_point";
|
protected static final String GEO_POINT_FIELD_NAME = "mapped_geo_point";
|
||||||
protected static final String GEO_POINT_ALIAS_FIELD_NAME = "mapped_geo_point_alias";
|
protected static final String GEO_POINT_ALIAS_FIELD_NAME = "mapped_geo_point_alias";
|
||||||
protected static final String GEO_SHAPE_FIELD_NAME = "mapped_geo_shape";
|
protected static final String GEO_SHAPE_FIELD_NAME = "mapped_geo_shape";
|
||||||
protected static final String LEGACY_GEO_SHAPE_FIELD_NAME = "mapped_legacy_geo_shape";
|
|
||||||
protected static final String[] MAPPED_FIELD_NAMES = new String[]{STRING_FIELD_NAME, STRING_ALIAS_FIELD_NAME,
|
protected static final String[] MAPPED_FIELD_NAMES = new String[]{STRING_FIELD_NAME, STRING_ALIAS_FIELD_NAME,
|
||||||
INT_FIELD_NAME, INT_RANGE_FIELD_NAME, DOUBLE_FIELD_NAME, BOOLEAN_FIELD_NAME, DATE_FIELD_NAME,
|
INT_FIELD_NAME, INT_RANGE_FIELD_NAME, DOUBLE_FIELD_NAME, BOOLEAN_FIELD_NAME, DATE_FIELD_NAME,
|
||||||
DATE_RANGE_FIELD_NAME, OBJECT_FIELD_NAME, GEO_POINT_FIELD_NAME, GEO_POINT_ALIAS_FIELD_NAME,
|
DATE_RANGE_FIELD_NAME, OBJECT_FIELD_NAME, GEO_POINT_FIELD_NAME, GEO_POINT_ALIAS_FIELD_NAME,
|
||||||
|
@ -218,28 +217,12 @@ public abstract class AbstractBuilderTestCase extends ESTestCase {
|
||||||
AbstractBuilderTestCase.this, false);
|
AbstractBuilderTestCase.this, false);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
if (enableWarningsCheck() == true) {
|
|
||||||
assertDeprecatedGeoWarnings();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceHolder.clientInvocationHandler.delegate = this;
|
serviceHolder.clientInvocationHandler.delegate = this;
|
||||||
serviceHolderWithNoType.clientInvocationHandler.delegate = this;
|
serviceHolderWithNoType.clientInvocationHandler.delegate = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertDeprecatedGeoWarnings() {
|
|
||||||
String prefix = "Field parameter [";
|
|
||||||
String postfix = "] is deprecated and will be removed in a future version.";
|
|
||||||
String[] deprecationWarnings = new String[] {
|
|
||||||
prefix + "tree" + postfix,
|
|
||||||
prefix + "tree_levels" + postfix,
|
|
||||||
prefix + "precision" + postfix,
|
|
||||||
prefix + "strategy" + postfix,
|
|
||||||
prefix + "distance_error_pct" + postfix
|
|
||||||
};
|
|
||||||
assertWarnings(deprecationWarnings);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static SearchContext getSearchContext(QueryShardContext context) {
|
protected static SearchContext getSearchContext(QueryShardContext context) {
|
||||||
TestSearchContext testSearchContext = new TestSearchContext(context) {
|
TestSearchContext testSearchContext = new TestSearchContext(context) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -413,8 +396,7 @@ public abstract class AbstractBuilderTestCase extends ESTestCase {
|
||||||
OBJECT_FIELD_NAME, "type=object",
|
OBJECT_FIELD_NAME, "type=object",
|
||||||
GEO_POINT_FIELD_NAME, "type=geo_point",
|
GEO_POINT_FIELD_NAME, "type=geo_point",
|
||||||
GEO_POINT_ALIAS_FIELD_NAME, "type=alias,path=" + GEO_POINT_FIELD_NAME,
|
GEO_POINT_ALIAS_FIELD_NAME, "type=alias,path=" + GEO_POINT_FIELD_NAME,
|
||||||
GEO_SHAPE_FIELD_NAME, "type=geo_shape",
|
GEO_SHAPE_FIELD_NAME, "type=geo_shape"
|
||||||
LEGACY_GEO_SHAPE_FIELD_NAME, "type=geo_shape,tree=quadtree"
|
|
||||||
))), MapperService.MergeReason.MAPPING_UPDATE);
|
))), MapperService.MergeReason.MAPPING_UPDATE);
|
||||||
// also add mappings for two inner field in the object field
|
// also add mappings for two inner field in the object field
|
||||||
mapperService.merge("_doc", new CompressedXContent("{\"properties\":{\"" + OBJECT_FIELD_NAME + "\":{\"type\":\"object\","
|
mapperService.merge("_doc", new CompressedXContent("{\"properties\":{\"" + OBJECT_FIELD_NAME + "\":{\"type\":\"object\","
|
||||||
|
|
Loading…
Reference in New Issue