1#include <Functions/FunctionFactory.h>
2#include <Functions/GeoUtils.h>
3#include <Functions/FunctionHelpers.h>
4
5#include <boost/geometry.hpp>
6#include <boost/geometry/geometries/point_xy.hpp>
7#include <boost/geometry/geometries/polygon.hpp>
8
9#include <Columns/ColumnArray.h>
10#include <Columns/ColumnFixedString.h>
11#include <Columns/ColumnString.h>
12#include <Columns/ColumnTuple.h>
13#include <Common/ObjectPool.h>
14#include <Common/ProfileEvents.h>
15#include <DataTypes/DataTypeArray.h>
16#include <DataTypes/DataTypeString.h>
17#include <DataTypes/DataTypeTuple.h>
18#include <DataTypes/DataTypesNumber.h>
19#include <IO/WriteHelpers.h>
20#include <Interpreters/ExpressionActions.h>
21
22#include <string>
23#include <memory>
24
25
26namespace ProfileEvents
27{
28 extern const Event PolygonsAddedToPool;
29 extern const Event PolygonsInPoolAllocatedBytes;
30}
31
32namespace DB
33{
34
35namespace ErrorCodes
36{
37 extern const int TOO_FEW_ARGUMENTS_FOR_FUNCTION;
38 extern const int BAD_ARGUMENTS;
39 extern const int ILLEGAL_TYPE_OF_ARGUMENT;
40 extern const int ILLEGAL_COLUMN;
41}
42
43namespace FunctionPointInPolygonDetail
44{
45
46template <typename Polygon, typename PointInPolygonImpl>
47ColumnPtr callPointInPolygonImplWithPool(const IColumn & x, const IColumn & y, Polygon & polygon)
48{
49 using Pool = ObjectPoolMap<PointInPolygonImpl, std::string>;
50 /// C++11 has thread-safe function-local statics on most modern compilers.
51 static Pool known_polygons;
52
53 auto factory = [& polygon]()
54 {
55 GeoUtils::normalizePolygon(polygon);
56 auto ptr = std::make_unique<PointInPolygonImpl>(polygon);
57
58 /// To allocate memory.
59 ptr->init();
60
61 ProfileEvents::increment(ProfileEvents::PolygonsAddedToPool);
62 ProfileEvents::increment(ProfileEvents::PolygonsInPoolAllocatedBytes, ptr->getAllocatedBytes());
63
64 return ptr.release();
65 };
66
67 std::string serialized_polygon = GeoUtils::serialize(polygon);
68 auto impl = known_polygons.get(serialized_polygon, factory);
69
70 return GeoUtils::pointInPolygon(x, y, *impl);
71}
72
73template <typename Polygon, typename PointInPolygonImpl>
74ColumnPtr callPointInPolygonImpl(const IColumn & x, const IColumn & y, Polygon & polygon)
75{
76 PointInPolygonImpl impl(polygon);
77 return GeoUtils::pointInPolygon(x, y, impl);
78}
79
80}
81
82template <template <typename> typename PointInPolygonImpl, bool use_object_pool = false>
83class FunctionPointInPolygon : public IFunction
84{
85public:
86
87 template <typename Type>
88 using Point = boost::geometry::model::d2::point_xy<Type>;
89 template <typename Type>
90 using Polygon = boost::geometry::model::polygon<Point<Type>, false>;
91 template <typename Type>
92 using Box = boost::geometry::model::box<Point<Type>>;
93
94 static const char * name;
95
96 static FunctionPtr create(const Context &)
97 {
98 return std::make_shared<FunctionPointInPolygon<PointInPolygonImpl, use_object_pool>>();
99 }
100
101 String getName() const override
102 {
103 return name;
104 }
105
106 bool isVariadic() const override
107 {
108 return true;
109 }
110
111 size_t getNumberOfArguments() const override
112 {
113 return 0;
114 }
115
116 DataTypePtr getReturnTypeImpl(const DataTypes & arguments) const override
117 {
118 if (arguments.size() < 2)
119 {
120 throw Exception("Too few arguments", ErrorCodes::TOO_FEW_ARGUMENTS_FOR_FUNCTION);
121 }
122
123 auto getMsgPrefix = [this](size_t i) { return "Argument " + toString(i + 1) + " for function " + getName(); };
124
125 for (size_t i = 1; i < arguments.size(); ++i)
126 {
127 auto * array = checkAndGetDataType<DataTypeArray>(arguments[i].get());
128 if (array == nullptr && i != 1)
129 throw Exception(getMsgPrefix(i) + " must be array of tuples.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
130
131 auto * tuple = checkAndGetDataType<DataTypeTuple>(array ? array->getNestedType().get() : arguments[i].get());
132 if (tuple == nullptr)
133 throw Exception(getMsgPrefix(i) + " must contains tuple.", ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
134
135 const DataTypes & elements = tuple->getElements();
136
137 if (elements.size() != 2)
138 throw Exception(getMsgPrefix(i) + " must have exactly two elements.", ErrorCodes::BAD_ARGUMENTS);
139
140 for (auto j : ext::range(0, elements.size()))
141 {
142 if (!isNativeNumber(elements[j]))
143 {
144 throw Exception(getMsgPrefix(i) + " must contains numeric tuple at position " + toString(j + 1),
145 ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
146 }
147 }
148 }
149
150 return std::make_shared<DataTypeUInt8>();
151 }
152
153 void executeImpl(Block & block, const ColumnNumbers & arguments, size_t result, size_t /*input_rows_count*/) override
154 {
155
156 const IColumn * point_col = block.getByPosition(arguments[0]).column.get();
157 auto const_tuple_col = checkAndGetColumn<ColumnConst>(point_col);
158 if (const_tuple_col)
159 point_col = &const_tuple_col->getDataColumn();
160 auto tuple_col = checkAndGetColumn<ColumnTuple>(point_col);
161
162 if (!tuple_col)
163 {
164 throw Exception("First argument for function " + getName() + " must be constant array of tuples.",
165 ErrorCodes::ILLEGAL_COLUMN);
166 }
167
168 const auto & tuple_columns = tuple_col->getColumns();
169 const DataTypes & tuple_types = typeid_cast<const DataTypeTuple &>(*block.getByPosition(arguments[0]).type).getElements();
170
171 bool use_float64 = WhichDataType(tuple_types[0]).isFloat64() || WhichDataType(tuple_types[1]).isFloat64();
172
173 auto & result_column = block.safeGetByPosition(result).column;
174
175 if (use_float64)
176 result_column = executeForType<Float64>(*tuple_columns[0], *tuple_columns[1], block, arguments);
177 else
178 result_column = executeForType<Float32>(*tuple_columns[0], *tuple_columns[1], block, arguments);
179
180 if (const_tuple_col)
181 result_column = ColumnConst::create(result_column, const_tuple_col->size());
182 }
183
184private:
185
186 Float64 getCoordinateFromField(const Field & field)
187 {
188 switch (field.getType())
189 {
190 case Field::Types::Float64:
191 return field.get<Float64>();
192 case Field::Types::Int64:
193 return field.get<Int64>();
194 case Field::Types::UInt64:
195 return field.get<UInt64>();
196 default:
197 {
198 std::string msg = "Expected numeric field, but got ";
199 throw Exception(msg + Field::Types::toString(field.getType()), ErrorCodes::LOGICAL_ERROR);
200 }
201 }
202 }
203
204 template <typename Type>
205 ColumnPtr executeForType(const IColumn & x, const IColumn & y, Block & block, const ColumnNumbers & arguments)
206 {
207 Polygon<Type> polygon;
208
209 auto getMsgPrefix = [this](size_t i) { return "Argument " + toString(i + 1) + " for function " + getName(); };
210
211 for (size_t i = 1; i < arguments.size(); ++i)
212 {
213 auto const_col = checkAndGetColumn<ColumnConst>(block.getByPosition(arguments[i]).column.get());
214 auto array_col = const_col ? checkAndGetColumn<ColumnArray>(&const_col->getDataColumn()) : nullptr;
215 auto tuple_col = array_col ? checkAndGetColumn<ColumnTuple>(&array_col->getData()) : nullptr;
216
217 if (!tuple_col)
218 throw Exception(getMsgPrefix(i) + " must be constant array of tuples.", ErrorCodes::ILLEGAL_COLUMN);
219
220 const auto & tuple_columns = tuple_col->getColumns();
221 const auto & column_x = tuple_columns[0];
222 const auto & column_y = tuple_columns[1];
223
224 if (!polygon.outer().empty())
225 polygon.inners().emplace_back();
226
227 auto & container = polygon.outer().empty() ? polygon.outer() : polygon.inners().back();
228
229 auto size = column_x->size();
230
231 if (size == 0)
232 throw Exception(getMsgPrefix(i) + " shouldn't be empty.", ErrorCodes::ILLEGAL_COLUMN);
233
234 for (auto j : ext::range(0, size))
235 {
236 Type x_coord = getCoordinateFromField((*column_x)[j]);
237 Type y_coord = getCoordinateFromField((*column_y)[j]);
238 container.push_back(Point<Type>(x_coord, y_coord));
239 }
240
241 /// Polygon assumed to be closed. Allow user to escape repeating of first point.
242 if (!boost::geometry::equals(container.front(), container.back()))
243 container.push_back(container.front());
244 }
245
246 auto callImpl = use_object_pool
247 ? FunctionPointInPolygonDetail::callPointInPolygonImplWithPool<Polygon<Type>, PointInPolygonImpl<Type>>
248 : FunctionPointInPolygonDetail::callPointInPolygonImpl<Polygon<Type>, PointInPolygonImpl<Type>>;
249
250 return callImpl(x, y, polygon);
251 }
252
253};
254
255
256template <typename Type>
257using Point = boost::geometry::model::d2::point_xy<Type>;
258
259template <typename Type>
260using PointInPolygonWithGrid = GeoUtils::PointInPolygonWithGrid<Type>;
261
262template <>
263const char * FunctionPointInPolygon<PointInPolygonWithGrid, true>::name = "pointInPolygon";
264
265void registerFunctionPointInPolygon(FunctionFactory & factory)
266{
267 factory.registerFunction<FunctionPointInPolygon<PointInPolygonWithGrid, true>>();
268}
269
270}
271