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 | |
26 | namespace ProfileEvents |
27 | { |
28 | extern const Event PolygonsAddedToPool; |
29 | extern const Event PolygonsInPoolAllocatedBytes; |
30 | } |
31 | |
32 | namespace DB |
33 | { |
34 | |
35 | namespace 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 | |
43 | namespace FunctionPointInPolygonDetail |
44 | { |
45 | |
46 | template <typename Polygon, typename PointInPolygonImpl> |
47 | ColumnPtr 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 | |
73 | template <typename Polygon, typename PointInPolygonImpl> |
74 | ColumnPtr 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 | |
82 | template <template <typename> typename PointInPolygonImpl, bool use_object_pool = false> |
83 | class FunctionPointInPolygon : public IFunction |
84 | { |
85 | public: |
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 | |
184 | private: |
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 | |
256 | template <typename Type> |
257 | using Point = boost::geometry::model::d2::point_xy<Type>; |
258 | |
259 | template <typename Type> |
260 | using PointInPolygonWithGrid = GeoUtils::PointInPolygonWithGrid<Type>; |
261 | |
262 | template <> |
263 | const char * FunctionPointInPolygon<PointInPolygonWithGrid, true>::name = "pointInPolygon" ; |
264 | |
265 | void registerFunctionPointInPolygon(FunctionFactory & factory) |
266 | { |
267 | factory.registerFunction<FunctionPointInPolygon<PointInPolygonWithGrid, true>>(); |
268 | } |
269 | |
270 | } |
271 | |