/* * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial agreement. */ package io.crate.operation.scalar.geo; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import io.crate.analyze.symbol.Function; import io.crate.analyze.symbol.Literal; import io.crate.analyze.symbol.Symbol; import io.crate.geo.GeoJSONUtils; import io.crate.metadata.FunctionIdent; import io.crate.metadata.FunctionInfo; import io.crate.metadata.Scalar; import io.crate.metadata.TransactionContext; import io.crate.data.Input; import io.crate.operation.scalar.ScalarFunctionModule; import io.crate.types.DataType; import io.crate.types.DataTypes; import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.lucene.BytesRefs; import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.shape.Shape; import org.locationtech.spatial4j.shape.SpatialRelation; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; public class WithinFunction extends Scalar<Boolean, Object> { public static final String NAME = "within"; private static final Set<DataType> LEFT_TYPES = ImmutableSet.<DataType>of( DataTypes.GEO_POINT, DataTypes.GEO_SHAPE, DataTypes.OBJECT, DataTypes.STRING ); private static final Set<DataType> RIGHT_TYPES = ImmutableSet.<DataType>of( DataTypes.GEO_SHAPE, DataTypes.OBJECT, DataTypes.STRING ); public static void register(ScalarFunctionModule scalarFunctionModule) { for (DataType left : LEFT_TYPES) { for (DataType right : RIGHT_TYPES) { scalarFunctionModule.register(new WithinFunction(info(left, right))); } } } private static FunctionInfo info(DataType pointType, DataType shapeType) { return new FunctionInfo( new FunctionIdent(NAME, ImmutableList.of(pointType, shapeType)), DataTypes.BOOLEAN ); } private static final FunctionInfo SHAPE_INFO = info(DataTypes.GEO_POINT, DataTypes.GEO_SHAPE); private final FunctionInfo info; private WithinFunction(FunctionInfo info) { this.info = info; } @Override public Boolean evaluate(Input[] args) { assert args.length == 2 : "number of args must be 2"; return evaluate(args[0], args[1]); } public Boolean evaluate(Input leftInput, Input rightInput) { Object left = leftInput.value(); if (left == null) { return null; } Object right = rightInput.value(); if (right == null) { return null; } return parseLeftShape(left).relate(parseRightShape(right)) == SpatialRelation.WITHIN; } @SuppressWarnings("unchecked") private Shape parseLeftShape(Object left) { Shape shape; if (left instanceof Double[]) { Double[] values = (Double[]) left; shape = SpatialContext.GEO.makePoint(values[0], values[1]); } else if (left instanceof List) { // ESSearchTask / ESGetTask returns it as list List values = (List) left; assert values.size() == 2 : "number of values must be 2"; shape = SpatialContext.GEO.makePoint((Double) values.get(0), (Double) values.get(1)); } else if (left instanceof BytesRef) { shape = GeoJSONUtils.wkt2Shape(BytesRefs.toString(left)); } else { shape = GeoJSONUtils.map2Shape((Map<String, Object>) left); } return shape; } @SuppressWarnings("unchecked") private Shape parseRightShape(Object right) { return (right instanceof BytesRef) ? GeoJSONUtils.wkt2Shape(BytesRefs.toString(right)) : GeoJSONUtils.map2Shape((Map<String, Object>) right); } @Override public FunctionInfo info() { return info; } @Override public Symbol normalizeSymbol(Function symbol, TransactionContext transactionContext) { Symbol left = symbol.arguments().get(0); Symbol right = symbol.arguments().get(1); boolean literalConverted = false; short numLiterals = 0; if (left.symbolType().isValueSymbol()) { numLiterals++; Symbol converted = convertTo(DataTypes.GEO_POINT, (Literal) left); literalConverted = converted != right; left = converted; } if (right.symbolType().isValueSymbol()) { numLiterals++; Symbol converted = convertTo(DataTypes.GEO_SHAPE, (Literal) right); literalConverted = literalConverted || converted != right; right = converted; } if (numLiterals == 2) { return Literal.of(evaluate((Input) left, (Input) right)); } if (literalConverted) { return new Function(SHAPE_INFO, Arrays.asList(left, right)); } return symbol; } private static Symbol convertTo(DataType toType, Literal convertMe) { if (convertMe.valueType().equals(toType)) { return convertMe; } return Literal.convert(convertMe, toType); } }