|
@@ -7,8 +7,16 @@
|
|
|
package org.elasticsearch.xpack.spatial.index.mapper;
|
|
|
|
|
|
import org.apache.lucene.index.IndexableField;
|
|
|
+import org.apache.lucene.util.BytesRef;
|
|
|
import org.elasticsearch.common.Strings;
|
|
|
+import org.elasticsearch.common.geo.GeoJson;
|
|
|
+import org.elasticsearch.common.geo.GeometryNormalizer;
|
|
|
import org.elasticsearch.common.geo.Orientation;
|
|
|
+import org.elasticsearch.geo.GeometryTestUtils;
|
|
|
+import org.elasticsearch.geometry.Geometry;
|
|
|
+import org.elasticsearch.geometry.utils.GeometryValidator;
|
|
|
+import org.elasticsearch.geometry.utils.WellKnownBinary;
|
|
|
+import org.elasticsearch.geometry.utils.WellKnownText;
|
|
|
import org.elasticsearch.index.IndexVersion;
|
|
|
import org.elasticsearch.index.IndexVersions;
|
|
|
import org.elasticsearch.index.mapper.AbstractGeometryFieldMapper;
|
|
@@ -22,13 +30,17 @@ import org.elasticsearch.index.mapper.MapperParsingException;
|
|
|
import org.elasticsearch.index.mapper.MapperService;
|
|
|
import org.elasticsearch.index.mapper.ParsedDocument;
|
|
|
import org.elasticsearch.index.mapper.SourceToParse;
|
|
|
+import org.elasticsearch.test.ESTestCase;
|
|
|
import org.elasticsearch.test.index.IndexVersionUtils;
|
|
|
import org.elasticsearch.xcontent.ToXContent;
|
|
|
+import org.elasticsearch.xcontent.XContentBuilder;
|
|
|
import org.junit.AssumptionViolatedException;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
import java.util.Collections;
|
|
|
import java.util.List;
|
|
|
+import java.util.function.Function;
|
|
|
+import java.util.function.Supplier;
|
|
|
|
|
|
import static org.hamcrest.Matchers.containsString;
|
|
|
import static org.hamcrest.Matchers.equalTo;
|
|
@@ -422,7 +434,161 @@ public class GeoShapeWithDocValuesFieldMapperTests extends GeoFieldMapperTests {
|
|
|
|
|
|
@Override
|
|
|
protected SyntheticSourceSupport syntheticSourceSupport(boolean ignoreMalformed) {
|
|
|
- throw new AssumptionViolatedException("not supported");
|
|
|
+ // Almost like GeoShapeType but no circles
|
|
|
+ enum ShapeType {
|
|
|
+ POINT,
|
|
|
+ LINESTRING,
|
|
|
+ POLYGON,
|
|
|
+ MULTIPOINT,
|
|
|
+ MULTILINESTRING,
|
|
|
+ MULTIPOLYGON,
|
|
|
+ GEOMETRYCOLLECTION,
|
|
|
+ ENVELOPE
|
|
|
+ }
|
|
|
+
|
|
|
+ return new SyntheticSourceSupport() {
|
|
|
+ @Override
|
|
|
+ public boolean preservesExactSource() {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SyntheticSourceExample example(int maxValues) throws IOException {
|
|
|
+ if (randomBoolean()) {
|
|
|
+ Value v = generateValue();
|
|
|
+ if (v.blockLoaderOutput != null) {
|
|
|
+ return new SyntheticSourceExample(v.input, v.output, v.blockLoaderOutput, this::mapping);
|
|
|
+ }
|
|
|
+ return new SyntheticSourceExample(v.input, v.output, this::mapping);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Value> values = randomList(1, maxValues, this::generateValue);
|
|
|
+ List<Object> in = values.stream().map(Value::input).toList();
|
|
|
+ List<Object> out = values.stream().map(Value::output).toList();
|
|
|
+
|
|
|
+ // Block loader infrastructure will never return nulls
|
|
|
+ List<Object> outBlockList = values.stream()
|
|
|
+ .filter(v -> v.input != null)
|
|
|
+ .map(v -> v.blockLoaderOutput != null ? v.blockLoaderOutput : v.output)
|
|
|
+ .toList();
|
|
|
+ var outBlock = outBlockList.size() == 1 ? outBlockList.get(0) : outBlockList;
|
|
|
+
|
|
|
+ return new SyntheticSourceExample(in, out, outBlock, this::mapping);
|
|
|
+ }
|
|
|
+
|
|
|
+ private record Value(Object input, Object output, String blockLoaderOutput) {
|
|
|
+ Value(Object input, Object output) {
|
|
|
+ this(input, output, null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Value generateValue() {
|
|
|
+ if (ignoreMalformed && randomBoolean()) {
|
|
|
+ List<Supplier<Object>> choices = List.of(
|
|
|
+ () -> randomAlphaOfLength(3),
|
|
|
+ ESTestCase::randomInt,
|
|
|
+ ESTestCase::randomLong,
|
|
|
+ ESTestCase::randomFloat,
|
|
|
+ ESTestCase::randomDouble
|
|
|
+ );
|
|
|
+ Object v = randomFrom(choices).get();
|
|
|
+ return new Value(v, v);
|
|
|
+ }
|
|
|
+ if (randomBoolean()) {
|
|
|
+ return new Value(null, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ var type = randomFrom(ShapeType.values());
|
|
|
+ var isGeoJson = randomBoolean();
|
|
|
+
|
|
|
+ switch (type) {
|
|
|
+ case POINT -> {
|
|
|
+ var point = GeometryTestUtils.randomPoint(false);
|
|
|
+ return value(point, isGeoJson);
|
|
|
+ }
|
|
|
+ case LINESTRING -> {
|
|
|
+ var line = GeometryTestUtils.randomLine(false);
|
|
|
+ return value(line, isGeoJson);
|
|
|
+ }
|
|
|
+ case POLYGON -> {
|
|
|
+ var polygon = GeometryTestUtils.randomPolygon(false);
|
|
|
+ return value(polygon, isGeoJson);
|
|
|
+ }
|
|
|
+ case MULTIPOINT -> {
|
|
|
+ var multiPoint = GeometryTestUtils.randomMultiPoint(false);
|
|
|
+ return value(multiPoint, isGeoJson);
|
|
|
+ }
|
|
|
+ case MULTILINESTRING -> {
|
|
|
+ var multiPoint = GeometryTestUtils.randomMultiLine(false);
|
|
|
+ return value(multiPoint, isGeoJson);
|
|
|
+ }
|
|
|
+ case MULTIPOLYGON -> {
|
|
|
+ var multiPolygon = GeometryTestUtils.randomMultiPolygon(false);
|
|
|
+ return value(multiPolygon, isGeoJson);
|
|
|
+ }
|
|
|
+ case GEOMETRYCOLLECTION -> {
|
|
|
+ var multiPolygon = GeometryTestUtils.randomGeometryCollectionWithoutCircle(false);
|
|
|
+ return value(multiPolygon, isGeoJson);
|
|
|
+ }
|
|
|
+ case ENVELOPE -> {
|
|
|
+ var rectangle = GeometryTestUtils.randomRectangle();
|
|
|
+ var wktString = WellKnownText.toWKT(rectangle);
|
|
|
+
|
|
|
+ return new Value(wktString, wktString);
|
|
|
+ }
|
|
|
+ default -> throw new UnsupportedOperationException("Unsupported shape");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Value value(Geometry geometry, boolean isGeoJson) {
|
|
|
+ var wktString = WellKnownText.toWKT(geometry);
|
|
|
+ var normalizedWktString = GeometryNormalizer.needsNormalize(Orientation.RIGHT, geometry)
|
|
|
+ ? WellKnownText.toWKT(GeometryNormalizer.apply(Orientation.RIGHT, geometry))
|
|
|
+ : wktString;
|
|
|
+
|
|
|
+ if (isGeoJson) {
|
|
|
+ var map = GeoJson.toMap(geometry);
|
|
|
+ return new Value(map, map, normalizedWktString);
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Value(wktString, wktString, normalizedWktString);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void mapping(XContentBuilder b) throws IOException {
|
|
|
+ b.field("type", "geo_shape");
|
|
|
+ if (rarely()) {
|
|
|
+ b.field("index", false);
|
|
|
+ }
|
|
|
+ if (rarely()) {
|
|
|
+ b.field("doc_values", false);
|
|
|
+ }
|
|
|
+ if (ignoreMalformed) {
|
|
|
+ b.field("ignore_malformed", true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<SyntheticSourceInvalidExample> invalidExample() throws IOException {
|
|
|
+ return List.of();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected Function<Object, Object> loadBlockExpected(BlockReaderSupport blockReaderSupport, boolean columnReader) {
|
|
|
+ return v -> asWKT((BytesRef) v);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected static Object asWKT(BytesRef value) {
|
|
|
+ // Internally we use WKB in BytesRef, but for test assertions we want to use WKT for readability
|
|
|
+ Geometry geometry = WellKnownBinary.fromWKB(GeometryValidator.NOOP, false, value.bytes);
|
|
|
+ return WellKnownText.toWKT(geometry);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected BlockReaderSupport getSupportedReaders(MapperService mapper, String loaderFieldName) {
|
|
|
+ // Synthetic source is currently not supported.
|
|
|
+ return new BlockReaderSupport(false, false, mapper, loaderFieldName);
|
|
|
}
|
|
|
|
|
|
@Override
|