| 1 | #pragma once |
| 2 | |
| 3 | #include <Core/Types.h> |
| 4 | #include <Core/Defines.h> |
| 5 | #include <Core/TypeListNumber.h> |
| 6 | #include <Columns/IColumn.h> |
| 7 | #include <Columns/ColumnVector.h> |
| 8 | #include <Common/typeid_cast.h> |
| 9 | #include <ext/range.h> |
| 10 | |
| 11 | /// Warning in boost::geometry during template strategy substitution. |
| 12 | #pragma GCC diagnostic push |
| 13 | |
| 14 | #if !__clang__ |
| 15 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" |
| 16 | #endif |
| 17 | |
| 18 | #pragma GCC diagnostic ignored "-Wunused-parameter" |
| 19 | |
| 20 | #include <boost/geometry.hpp> |
| 21 | |
| 22 | #pragma GCC diagnostic pop |
| 23 | |
| 24 | #include <boost/geometry/geometries/point_xy.hpp> |
| 25 | #include <boost/geometry/geometries/polygon.hpp> |
| 26 | #include <boost/geometry/geometries/multi_polygon.hpp> |
| 27 | #include <boost/geometry/geometries/segment.hpp> |
| 28 | #include <boost/geometry/algorithms/comparable_distance.hpp> |
| 29 | #include <boost/geometry/strategies/cartesian/distance_pythagoras.hpp> |
| 30 | |
| 31 | #include <array> |
| 32 | #include <vector> |
| 33 | #include <iterator> |
| 34 | #include <cmath> |
| 35 | #include <algorithm> |
| 36 | #include <IO/WriteBufferFromString.h> |
| 37 | |
| 38 | namespace DB |
| 39 | { |
| 40 | |
| 41 | namespace ErrorCodes |
| 42 | { |
| 43 | extern const int LOGICAL_ERROR; |
| 44 | } |
| 45 | |
| 46 | |
| 47 | namespace GeoUtils |
| 48 | { |
| 49 | |
| 50 | |
| 51 | template <typename Polygon> |
| 52 | UInt64 getPolygonAllocatedBytes(const Polygon & polygon) |
| 53 | { |
| 54 | UInt64 size = 0; |
| 55 | |
| 56 | using RingType = typename Polygon::ring_type; |
| 57 | using ValueType = typename RingType::value_type; |
| 58 | |
| 59 | auto sizeOfRing = [](const RingType & ring) { return sizeof(ring) + ring.capacity() * sizeof(ValueType); }; |
| 60 | |
| 61 | size += sizeOfRing(polygon.outer()); |
| 62 | |
| 63 | const auto & inners = polygon.inners(); |
| 64 | size += sizeof(inners) + inners.capacity() * sizeof(RingType); |
| 65 | for (auto & inner : inners) |
| 66 | size += sizeOfRing(inner); |
| 67 | |
| 68 | return size; |
| 69 | } |
| 70 | |
| 71 | template <typename MultiPolygon> |
| 72 | UInt64 getMultiPolygonAllocatedBytes(const MultiPolygon & multi_polygon) |
| 73 | { |
| 74 | using ValueType = typename MultiPolygon::value_type; |
| 75 | UInt64 size = multi_polygon.capacity() * sizeof(ValueType); |
| 76 | |
| 77 | for (const auto & polygon : multi_polygon) |
| 78 | size += getPolygonAllocatedBytes(polygon); |
| 79 | |
| 80 | return size; |
| 81 | } |
| 82 | |
| 83 | template <typename CoordinateType = Float32> |
| 84 | class PointInPolygonWithGrid |
| 85 | { |
| 86 | public: |
| 87 | using Point = boost::geometry::model::d2::point_xy<CoordinateType>; |
| 88 | /// Counter-Clockwise ordering. |
| 89 | using Polygon = boost::geometry::model::polygon<Point, false>; |
| 90 | using MultiPolygon = boost::geometry::model::multi_polygon<Polygon>; |
| 91 | using Box = boost::geometry::model::box<Point>; |
| 92 | using Segment = boost::geometry::model::segment<Point>; |
| 93 | |
| 94 | explicit PointInPolygonWithGrid(const Polygon & polygon_, UInt16 grid_size_ = 8) |
| 95 | : grid_size(std::max<UInt16>(1, grid_size_)), polygon(polygon_) {} |
| 96 | |
| 97 | void init(); |
| 98 | |
| 99 | /// True if bound box is empty. |
| 100 | bool hasEmptyBound() const { return has_empty_bound; } |
| 101 | |
| 102 | UInt64 getAllocatedBytes() const; |
| 103 | |
| 104 | inline bool ALWAYS_INLINE contains(CoordinateType x, CoordinateType y); |
| 105 | |
| 106 | private: |
| 107 | enum class CellType |
| 108 | { |
| 109 | inner, |
| 110 | outer, |
| 111 | singleLine, |
| 112 | pairOfLinesSingleConvexPolygon, |
| 113 | pairOfLinesSingleNonConvexPolygons, |
| 114 | pairOfLinesDifferentPolygons, |
| 115 | complexPolygon |
| 116 | }; |
| 117 | |
| 118 | struct HalfPlane |
| 119 | { |
| 120 | /// Line, a * x + b * y + c = 0. Vector (a, b) points inside half-plane. |
| 121 | CoordinateType a; |
| 122 | CoordinateType b; |
| 123 | CoordinateType c; |
| 124 | |
| 125 | /// Take left half-plane. |
| 126 | void fill(const Point & from, const Point & to) |
| 127 | { |
| 128 | a = -(to.y() - from.y()); |
| 129 | b = to.x() - from.x(); |
| 130 | c = -from.x() * a - from.y() * b; |
| 131 | } |
| 132 | |
| 133 | /// Inner part of the HalfPlane is the left side of initialized vector. |
| 134 | bool ALWAYS_INLINE contains(CoordinateType x, CoordinateType y) const { return a * x + b * y + c >= 0; } |
| 135 | }; |
| 136 | |
| 137 | struct Cell |
| 138 | { |
| 139 | static const int max_stored_half_planes = 2; |
| 140 | |
| 141 | HalfPlane half_planes[max_stored_half_planes]; |
| 142 | size_t index_of_inner_polygon; |
| 143 | CellType type; |
| 144 | }; |
| 145 | |
| 146 | const UInt16 grid_size; |
| 147 | |
| 148 | Polygon polygon; |
| 149 | std::vector<Cell> cells; |
| 150 | std::vector<MultiPolygon> polygons; |
| 151 | |
| 152 | CoordinateType cell_width; |
| 153 | CoordinateType cell_height; |
| 154 | |
| 155 | CoordinateType x_shift; |
| 156 | CoordinateType y_shift; |
| 157 | CoordinateType x_scale; |
| 158 | CoordinateType y_scale; |
| 159 | |
| 160 | bool has_empty_bound = false; |
| 161 | bool was_grid_built = false; |
| 162 | |
| 163 | void buildGrid(); |
| 164 | |
| 165 | void calcGridAttributes(Box & box); |
| 166 | |
| 167 | template <typename T> |
| 168 | T ALWAYS_INLINE getCellIndex(T row, T col) const { return row * grid_size + col; } |
| 169 | |
| 170 | /// Complex case. Will check intersection directly. |
| 171 | inline void addComplexPolygonCell(size_t index, const Box & box); |
| 172 | |
| 173 | /// Empty intersection or intersection == box. |
| 174 | inline void addCell(size_t index, const Box & empty_box); |
| 175 | |
| 176 | /// Intersection is a single polygon. |
| 177 | inline void addCell(size_t index, const Box & box, const Polygon & intersection); |
| 178 | |
| 179 | /// Intersection is a pair of polygons. |
| 180 | inline void addCell(size_t index, const Box & box, const Polygon & first, const Polygon & second); |
| 181 | |
| 182 | /// Returns a list of half-planes were formed from intersection edges without box edges. |
| 183 | inline std::vector<HalfPlane> findHalfPlanes(const Box & box, const Polygon & intersection); |
| 184 | |
| 185 | /// Check that polygon.outer() is convex. |
| 186 | inline bool isConvex(const Polygon & polygon); |
| 187 | |
| 188 | using Distance = typename boost::geometry::default_comparable_distance_result<Point, Segment>::type; |
| 189 | |
| 190 | /// min(distance(point, edge) : edge in polygon) |
| 191 | inline Distance distance(const Point & point, const Polygon & polygon); |
| 192 | }; |
| 193 | |
| 194 | template <typename CoordinateType> |
| 195 | UInt64 PointInPolygonWithGrid<CoordinateType>::getAllocatedBytes() const |
| 196 | { |
| 197 | UInt64 size = sizeof(*this); |
| 198 | |
| 199 | size += cells.capacity() * sizeof(Cell); |
| 200 | size += polygons.capacity() * sizeof(MultiPolygon); |
| 201 | size += getPolygonAllocatedBytes(polygon); |
| 202 | |
| 203 | for (const auto & elem : polygons) |
| 204 | size += getMultiPolygonAllocatedBytes(elem); |
| 205 | |
| 206 | return size; |
| 207 | } |
| 208 | |
| 209 | template <typename CoordinateType> |
| 210 | void PointInPolygonWithGrid<CoordinateType>::init() |
| 211 | { |
| 212 | if (!was_grid_built) |
| 213 | buildGrid(); |
| 214 | |
| 215 | was_grid_built = true; |
| 216 | } |
| 217 | |
| 218 | template <typename CoordinateType> |
| 219 | void PointInPolygonWithGrid<CoordinateType>::calcGridAttributes( |
| 220 | PointInPolygonWithGrid<CoordinateType>::Box & box) |
| 221 | { |
| 222 | boost::geometry::envelope(polygon, box); |
| 223 | |
| 224 | const Point & min_corner = box.min_corner(); |
| 225 | const Point & max_corner = box.max_corner(); |
| 226 | |
| 227 | #pragma GCC diagnostic push |
| 228 | #if !__clang__ |
| 229 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" |
| 230 | #endif |
| 231 | |
| 232 | cell_width = (max_corner.x() - min_corner.x()) / grid_size; |
| 233 | cell_height = (max_corner.y() - min_corner.y()) / grid_size; |
| 234 | |
| 235 | #pragma GCC diagnostic pop |
| 236 | |
| 237 | if (cell_width == 0 || cell_height == 0) |
| 238 | { |
| 239 | has_empty_bound = true; |
| 240 | return; |
| 241 | } |
| 242 | |
| 243 | x_scale = 1 / cell_width; |
| 244 | y_scale = 1 / cell_height; |
| 245 | x_shift = -min_corner.x(); |
| 246 | y_shift = -min_corner.y(); |
| 247 | } |
| 248 | |
| 249 | template <typename CoordinateType> |
| 250 | void PointInPolygonWithGrid<CoordinateType>::buildGrid() |
| 251 | { |
| 252 | Box box; |
| 253 | calcGridAttributes(box); |
| 254 | |
| 255 | if (has_empty_bound) |
| 256 | return; |
| 257 | |
| 258 | cells.assign(grid_size * grid_size, {}); |
| 259 | |
| 260 | const Point & min_corner = box.min_corner(); |
| 261 | |
| 262 | for (size_t row = 0; row < grid_size; ++row) |
| 263 | { |
| 264 | #pragma GCC diagnostic push |
| 265 | #if !__clang__ |
| 266 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" |
| 267 | #endif |
| 268 | CoordinateType y_min = min_corner.y() + row * cell_height; |
| 269 | CoordinateType y_max = min_corner.y() + (row + 1) * cell_height; |
| 270 | |
| 271 | for (size_t col = 0; col < grid_size; ++col) |
| 272 | { |
| 273 | CoordinateType x_min = min_corner.x() + col * cell_width; |
| 274 | CoordinateType x_max = min_corner.x() + (col + 1) * cell_width; |
| 275 | #pragma GCC diagnostic pop |
| 276 | Box cell_box(Point(x_min, y_min), Point(x_max, y_max)); |
| 277 | |
| 278 | Polygon cell_bound; |
| 279 | boost::geometry::convert(cell_box, cell_bound); |
| 280 | |
| 281 | MultiPolygon intersection; |
| 282 | boost::geometry::intersection(polygon, cell_bound, intersection); |
| 283 | |
| 284 | size_t cellIndex = getCellIndex(row, col); |
| 285 | |
| 286 | if (intersection.empty()) |
| 287 | addCell(cellIndex, cell_box); |
| 288 | else if (intersection.size() == 1) |
| 289 | addCell(cellIndex, cell_box, intersection.front()); |
| 290 | else if (intersection.size() == 2) |
| 291 | addCell(cellIndex, cell_box, intersection.front(), intersection.back()); |
| 292 | else |
| 293 | addComplexPolygonCell(cellIndex, cell_box); |
| 294 | } |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | template <typename CoordinateType> |
| 299 | bool PointInPolygonWithGrid<CoordinateType>::contains(CoordinateType x, CoordinateType y) |
| 300 | { |
| 301 | if (has_empty_bound) |
| 302 | return false; |
| 303 | |
| 304 | CoordinateType float_row = (y + y_shift) * y_scale; |
| 305 | CoordinateType float_col = (x + x_shift) * x_scale; |
| 306 | |
| 307 | if (float_row < 0 || float_row > grid_size) |
| 308 | return false; |
| 309 | if (float_col < 0 || float_col > grid_size) |
| 310 | return false; |
| 311 | |
| 312 | int row = std::min<int>(float_row, grid_size - 1); |
| 313 | int col = std::min<int>(float_col, grid_size - 1); |
| 314 | |
| 315 | int index = getCellIndex(row, col); |
| 316 | const auto & cell = cells[index]; |
| 317 | |
| 318 | switch (cell.type) |
| 319 | { |
| 320 | case CellType::inner: |
| 321 | return true; |
| 322 | case CellType::outer: |
| 323 | return false; |
| 324 | case CellType::singleLine: |
| 325 | return cell.half_planes[0].contains(x, y); |
| 326 | case CellType::pairOfLinesSingleConvexPolygon: |
| 327 | return cell.half_planes[0].contains(x, y) && cell.half_planes[1].contains(x, y); |
| 328 | case CellType::pairOfLinesDifferentPolygons: [[fallthrough]]; |
| 329 | case CellType::pairOfLinesSingleNonConvexPolygons: |
| 330 | return cell.half_planes[0].contains(x, y) || cell.half_planes[1].contains(x, y); |
| 331 | case CellType::complexPolygon: |
| 332 | return boost::geometry::within(Point(x, y), polygons[cell.index_of_inner_polygon]); |
| 333 | } |
| 334 | |
| 335 | __builtin_unreachable(); |
| 336 | } |
| 337 | |
| 338 | template <typename CoordinateType> |
| 339 | typename PointInPolygonWithGrid<CoordinateType>::Distance |
| 340 | PointInPolygonWithGrid<CoordinateType>::distance( |
| 341 | const PointInPolygonWithGrid<CoordinateType>::Point & point, |
| 342 | const PointInPolygonWithGrid<CoordinateType>::Polygon & poly) |
| 343 | { |
| 344 | const auto & outer = poly.outer(); |
| 345 | Distance distance = 0; |
| 346 | for (auto i : ext::range(0, outer.size() - 1)) |
| 347 | { |
| 348 | Segment segment(outer[i], outer[i + 1]); |
| 349 | Distance current = boost::geometry::comparable_distance(point, segment); |
| 350 | distance = i ? std::min(current, distance) : current; |
| 351 | } |
| 352 | return distance; |
| 353 | } |
| 354 | |
| 355 | template <typename CoordinateType> |
| 356 | bool PointInPolygonWithGrid<CoordinateType>::isConvex(const PointInPolygonWithGrid<CoordinateType>::Polygon & poly) |
| 357 | { |
| 358 | const auto & outer = poly.outer(); |
| 359 | /// Segment or point. |
| 360 | if (outer.size() < 4) |
| 361 | return false; |
| 362 | |
| 363 | auto vecProduct = [](const Point & from, const Point & to) { return from.x() * to.y() - from.y() * to.x(); }; |
| 364 | auto getVector = [](const Point & from, const Point & to) -> Point |
| 365 | { |
| 366 | return Point(to.x() - from.x(), to.y() - from.y()); |
| 367 | }; |
| 368 | |
| 369 | Point first = getVector(outer[0], outer[1]); |
| 370 | Point prev = first; |
| 371 | |
| 372 | for (auto i : ext::range(1, outer.size() - 1)) |
| 373 | { |
| 374 | Point cur = getVector(outer[i], outer[i + 1]); |
| 375 | if (vecProduct(prev, cur) < 0) |
| 376 | return false; |
| 377 | |
| 378 | prev = cur; |
| 379 | } |
| 380 | |
| 381 | return vecProduct(prev, first) >= 0; |
| 382 | } |
| 383 | |
| 384 | template <typename CoordinateType> |
| 385 | std::vector<typename PointInPolygonWithGrid<CoordinateType>::HalfPlane> |
| 386 | PointInPolygonWithGrid<CoordinateType>::findHalfPlanes( |
| 387 | const PointInPolygonWithGrid<CoordinateType>::Box & box, |
| 388 | const PointInPolygonWithGrid<CoordinateType>::Polygon & intersection) |
| 389 | { |
| 390 | std::vector<HalfPlane> half_planes; |
| 391 | Polygon bound; |
| 392 | boost::geometry::convert(box, bound); |
| 393 | const auto & outer = intersection.outer(); |
| 394 | |
| 395 | for (auto i : ext::range(0, outer.size() - 1)) |
| 396 | { |
| 397 | /// Want to detect is intersection edge was formed from box edge or from polygon edge. |
| 398 | /// If center of the edge closer to box, than don't form the half-plane. |
| 399 | Segment segment(outer[i], outer[i + 1]); |
| 400 | Point center((segment.first.x() + segment.second.x()) / 2, (segment.first.y() + segment.second.y()) / 2); |
| 401 | if (distance(center, polygon) < distance(center, bound)) |
| 402 | { |
| 403 | half_planes.push_back({}); |
| 404 | half_planes.back().fill(segment.first, segment.second); |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | return half_planes; |
| 409 | } |
| 410 | |
| 411 | template <typename CoordinateType> |
| 412 | void PointInPolygonWithGrid<CoordinateType>::addComplexPolygonCell( |
| 413 | size_t index, const PointInPolygonWithGrid<CoordinateType>::Box & box) |
| 414 | { |
| 415 | cells[index].type = CellType::complexPolygon; |
| 416 | cells[index].index_of_inner_polygon = polygons.size(); |
| 417 | |
| 418 | /// Expand box in (1 + eps_factor) times to eliminate errors for points on box bound. |
| 419 | static constexpr CoordinateType eps_factor = 0.01; |
| 420 | auto x_eps = eps_factor * (box.max_corner().x() - box.min_corner().x()); |
| 421 | auto y_eps = eps_factor * (box.max_corner().y() - box.min_corner().y()); |
| 422 | |
| 423 | Point min_corner(box.min_corner().x() - x_eps, box.min_corner().y() - y_eps); |
| 424 | Point max_corner(box.max_corner().x() + x_eps, box.max_corner().y() + y_eps); |
| 425 | Box box_with_eps_bound(min_corner, max_corner); |
| 426 | |
| 427 | Polygon bound; |
| 428 | boost::geometry::convert(box_with_eps_bound, bound); |
| 429 | |
| 430 | MultiPolygon intersection; |
| 431 | boost::geometry::intersection(polygon, bound, intersection); |
| 432 | |
| 433 | polygons.push_back(intersection); |
| 434 | } |
| 435 | |
| 436 | template <typename CoordinateType> |
| 437 | void PointInPolygonWithGrid<CoordinateType>::addCell( |
| 438 | size_t index, const PointInPolygonWithGrid<CoordinateType>::Box & empty_box) |
| 439 | { |
| 440 | const auto & min_corner = empty_box.min_corner(); |
| 441 | const auto & max_corner = empty_box.max_corner(); |
| 442 | |
| 443 | Point center((min_corner.x() + max_corner.x()) / 2, (min_corner.y() + max_corner.y()) / 2); |
| 444 | |
| 445 | if (boost::geometry::within(center, polygon)) |
| 446 | cells[index].type = CellType::inner; |
| 447 | else |
| 448 | cells[index].type = CellType::outer; |
| 449 | |
| 450 | } |
| 451 | |
| 452 | template <typename CoordinateType> |
| 453 | void PointInPolygonWithGrid<CoordinateType>::addCell( |
| 454 | size_t index, |
| 455 | const PointInPolygonWithGrid<CoordinateType>::Box & box, |
| 456 | const PointInPolygonWithGrid<CoordinateType>::Polygon & intersection) |
| 457 | { |
| 458 | if (!intersection.inners().empty()) |
| 459 | addComplexPolygonCell(index, box); |
| 460 | |
| 461 | auto half_planes = findHalfPlanes(box, intersection); |
| 462 | |
| 463 | if (half_planes.empty()) |
| 464 | addCell(index, box); |
| 465 | else if (half_planes.size() == 1) |
| 466 | { |
| 467 | cells[index].type = CellType::singleLine; |
| 468 | cells[index].half_planes[0] = half_planes[0]; |
| 469 | } |
| 470 | else if (half_planes.size() == 2) |
| 471 | { |
| 472 | cells[index].type = isConvex(intersection) ? CellType::pairOfLinesSingleConvexPolygon |
| 473 | : CellType::pairOfLinesSingleNonConvexPolygons; |
| 474 | cells[index].half_planes[0] = half_planes[0]; |
| 475 | cells[index].half_planes[1] = half_planes[1]; |
| 476 | } |
| 477 | else |
| 478 | addComplexPolygonCell(index, box); |
| 479 | } |
| 480 | |
| 481 | template <typename CoordinateType> |
| 482 | void PointInPolygonWithGrid<CoordinateType>::addCell( |
| 483 | size_t index, |
| 484 | const PointInPolygonWithGrid<CoordinateType>::Box & box, |
| 485 | const PointInPolygonWithGrid<CoordinateType>::Polygon & first, |
| 486 | const PointInPolygonWithGrid<CoordinateType>::Polygon & second) |
| 487 | { |
| 488 | if (!first.inners().empty() || !second.inners().empty()) |
| 489 | addComplexPolygonCell(index, box); |
| 490 | |
| 491 | auto first_half_planes = findHalfPlanes(box, first); |
| 492 | auto second_half_planes = findHalfPlanes(box, second); |
| 493 | |
| 494 | if (first_half_planes.empty()) |
| 495 | addCell(index, box, first); |
| 496 | else if (second_half_planes.empty()) |
| 497 | addCell(index, box, second); |
| 498 | else if (first_half_planes.size() == 1 && second_half_planes.size() == 1) |
| 499 | { |
| 500 | cells[index].type = CellType::pairOfLinesDifferentPolygons; |
| 501 | cells[index].half_planes[0] = first_half_planes[0]; |
| 502 | cells[index].half_planes[1] = second_half_planes[0]; |
| 503 | } |
| 504 | else |
| 505 | addComplexPolygonCell(index, box); |
| 506 | } |
| 507 | |
| 508 | |
| 509 | template <typename Strategy, typename CoordinateType = Float32> |
| 510 | class PointInPolygon |
| 511 | { |
| 512 | public: |
| 513 | using Point = boost::geometry::model::d2::point_xy<CoordinateType>; |
| 514 | /// Counter-Clockwise ordering. |
| 515 | using Polygon = boost::geometry::model::polygon<Point, false>; |
| 516 | using Box = boost::geometry::model::box<Point>; |
| 517 | |
| 518 | explicit PointInPolygon(const Polygon & polygon_) : polygon(polygon_) {} |
| 519 | |
| 520 | void init() |
| 521 | { |
| 522 | boost::geometry::envelope(polygon, box); |
| 523 | |
| 524 | const Point & min_corner = box.min_corner(); |
| 525 | const Point & max_corner = box.max_corner(); |
| 526 | |
| 527 | if (min_corner.x() == max_corner.x() || min_corner.y() == max_corner.y()) |
| 528 | has_empty_bound = true; |
| 529 | } |
| 530 | |
| 531 | bool hasEmptyBound() const { return has_empty_bound; } |
| 532 | |
| 533 | inline bool ALWAYS_INLINE contains(CoordinateType x, CoordinateType y) |
| 534 | { |
| 535 | Point point(x, y); |
| 536 | |
| 537 | if (!boost::geometry::within(point, box)) |
| 538 | return false; |
| 539 | |
| 540 | return boost::geometry::covered_by(point, polygon, strategy); |
| 541 | } |
| 542 | |
| 543 | UInt64 getAllocatedBytes() const { return sizeof(*this); } |
| 544 | |
| 545 | private: |
| 546 | const Polygon & polygon; |
| 547 | Box box; |
| 548 | bool has_empty_bound = false; |
| 549 | Strategy strategy; |
| 550 | }; |
| 551 | |
| 552 | |
| 553 | /// Algorithms. |
| 554 | |
| 555 | template <typename T, typename U, typename PointInPolygonImpl> |
| 556 | ColumnPtr pointInPolygon(const ColumnVector<T> & x, const ColumnVector<U> & y, PointInPolygonImpl && impl) |
| 557 | { |
| 558 | auto size = x.size(); |
| 559 | |
| 560 | impl.init(); |
| 561 | |
| 562 | if (impl.hasEmptyBound()) |
| 563 | { |
| 564 | return ColumnVector<UInt8>::create(size, 0); |
| 565 | } |
| 566 | |
| 567 | auto result = ColumnVector<UInt8>::create(size); |
| 568 | auto & data = result->getData(); |
| 569 | |
| 570 | const auto & x_data = x.getData(); |
| 571 | const auto & y_data = y.getData(); |
| 572 | |
| 573 | for (auto i : ext::range(0, size)) |
| 574 | { |
| 575 | data[i] = static_cast<UInt8>(impl.contains(x_data[i], y_data[i])); |
| 576 | } |
| 577 | |
| 578 | return result; |
| 579 | } |
| 580 | |
| 581 | template <typename ... Types> |
| 582 | struct CallPointInPolygon; |
| 583 | |
| 584 | template <typename Type, typename ... Types> |
| 585 | struct CallPointInPolygon<Type, Types ...> |
| 586 | { |
| 587 | template <typename T, typename PointInPolygonImpl> |
| 588 | static ColumnPtr call(const ColumnVector<T> & x, const IColumn & y, PointInPolygonImpl && impl) |
| 589 | { |
| 590 | if (auto column = typeid_cast<const ColumnVector<Type> *>(&y)) |
| 591 | return pointInPolygon(x, *column, impl); |
| 592 | return CallPointInPolygon<Types ...>::template call<T>(x, y, impl); |
| 593 | } |
| 594 | |
| 595 | template <typename PointInPolygonImpl> |
| 596 | static ColumnPtr call(const IColumn & x, const IColumn & y, PointInPolygonImpl && impl) |
| 597 | { |
| 598 | using Impl = typename ApplyTypeListForClass<::DB::GeoUtils::CallPointInPolygon, TypeListNativeNumbers>::Type; |
| 599 | if (auto column = typeid_cast<const ColumnVector<Type> *>(&x)) |
| 600 | return Impl::template call<Type>(*column, y, impl); |
| 601 | return CallPointInPolygon<Types ...>::call(x, y, impl); |
| 602 | } |
| 603 | }; |
| 604 | |
| 605 | template <> |
| 606 | struct CallPointInPolygon<> |
| 607 | { |
| 608 | template <typename T, typename PointInPolygonImpl> |
| 609 | static ColumnPtr call(const ColumnVector<T> &, const IColumn & y, PointInPolygonImpl &&) |
| 610 | { |
| 611 | throw Exception(std::string("Unknown numeric column type: " ) + demangle(typeid(y).name()), ErrorCodes::LOGICAL_ERROR); |
| 612 | } |
| 613 | |
| 614 | template <typename PointInPolygonImpl> |
| 615 | static ColumnPtr call(const IColumn & x, const IColumn &, PointInPolygonImpl &&) |
| 616 | { |
| 617 | throw Exception(std::string("Unknown numeric column type: " ) + demangle(typeid(x).name()), ErrorCodes::LOGICAL_ERROR); |
| 618 | } |
| 619 | }; |
| 620 | |
| 621 | template <typename PointInPolygonImpl> |
| 622 | ColumnPtr pointInPolygon(const IColumn & x, const IColumn & y, PointInPolygonImpl && impl) |
| 623 | { |
| 624 | using Impl = typename ApplyTypeListForClass<::DB::GeoUtils::CallPointInPolygon, TypeListNativeNumbers>::Type; |
| 625 | return Impl::call(x, y, impl); |
| 626 | } |
| 627 | |
| 628 | /// Total angle (signed) between neighbor vectors in linestring. Zero if linestring.size() < 2. |
| 629 | template <typename Linestring> |
| 630 | double calcLinestringRotation(const Linestring & points) |
| 631 | { |
| 632 | using Point = std::decay_t<decltype(*points.begin())>; |
| 633 | double rotation = 0; |
| 634 | |
| 635 | auto vecProduct = [](const Point & from, const Point & to) { return from.x() * to.y() - from.y() * to.x(); }; |
| 636 | auto scalarProduct = [](const Point & from, const Point & to) { return from.x() * to.x() + from.y() * to.y(); }; |
| 637 | auto getVector = [](const Point & from, const Point & to) -> Point |
| 638 | { |
| 639 | return Point(to.x() - from.x(), to.y() - from.y()); |
| 640 | }; |
| 641 | |
| 642 | for (auto it = points.begin(); it != points.end(); ++it) |
| 643 | { |
| 644 | if (it != points.begin()) |
| 645 | { |
| 646 | auto prev = std::prev(it); |
| 647 | auto next = std::next(it); |
| 648 | |
| 649 | if (next == points.end()) |
| 650 | next = std::next(points.begin()); |
| 651 | |
| 652 | Point from = getVector(*prev, *it); |
| 653 | Point to = getVector(*it, *next); |
| 654 | |
| 655 | auto vec_prod = vecProduct(from, to); |
| 656 | auto scalar_prod = scalarProduct(from, to); |
| 657 | auto ang = std::atan2(vec_prod, scalar_prod); |
| 658 | rotation += ang; |
| 659 | } |
| 660 | } |
| 661 | |
| 662 | return rotation; |
| 663 | } |
| 664 | |
| 665 | /// Make inner linestring counter-clockwise and outers clockwise oriented. |
| 666 | template <typename Polygon> |
| 667 | void normalizePolygon(Polygon && polygon) |
| 668 | { |
| 669 | auto & outer = polygon.outer(); |
| 670 | if (calcLinestringRotation(outer) < 0) |
| 671 | std::reverse(outer.begin(), outer.end()); |
| 672 | |
| 673 | auto & inners = polygon.inners(); |
| 674 | for (auto & inner : inners) |
| 675 | if (calcLinestringRotation(inner) > 0) |
| 676 | std::reverse(inner.begin(), inner.end()); |
| 677 | } |
| 678 | |
| 679 | |
| 680 | template <typename Polygon> |
| 681 | std::string serialize(Polygon && polygon) |
| 682 | { |
| 683 | std::string result; |
| 684 | |
| 685 | { |
| 686 | WriteBufferFromString buffer(result); |
| 687 | |
| 688 | using RingType = typename std::decay_t<Polygon>::ring_type; |
| 689 | |
| 690 | auto serializeFloat = [&buffer](float value) { buffer.write(reinterpret_cast<char *>(&value), sizeof(value)); }; |
| 691 | auto serializeSize = [&buffer](size_t size) { buffer.write(reinterpret_cast<char *>(&size), sizeof(size)); }; |
| 692 | |
| 693 | auto serializeRing = [& serializeFloat, & serializeSize](const RingType & ring) |
| 694 | { |
| 695 | serializeSize(ring.size()); |
| 696 | for (const auto & point : ring) |
| 697 | { |
| 698 | serializeFloat(point.x()); |
| 699 | serializeFloat(point.y()); |
| 700 | } |
| 701 | }; |
| 702 | |
| 703 | serializeRing(polygon.outer()); |
| 704 | |
| 705 | const auto & inners = polygon.inners(); |
| 706 | serializeSize(inners.size()); |
| 707 | for (auto & inner : inners) |
| 708 | serializeRing(inner); |
| 709 | } |
| 710 | |
| 711 | return result; |
| 712 | } |
| 713 | |
| 714 | size_t geohashEncode(Float64 longitude, Float64 latitude, UInt8 precision, char * out); |
| 715 | |
| 716 | void geohashDecode(const char * encoded_string, size_t encoded_len, Float64 * longitude, Float64 * latitude); |
| 717 | |
| 718 | std::vector<std::pair<Float64, Float64>> geohashCoverBox(Float64 longitude_min, Float64 latitude_min, Float64 longitude_max, Float64 latitude_max, UInt8 precision, UInt32 max_items = 0); |
| 719 | |
| 720 | struct GeohashesInBoxPreparedArgs |
| 721 | { |
| 722 | UInt64 items_count = 0; |
| 723 | UInt8 precision = 0; |
| 724 | |
| 725 | Float64 longitude_min = 0.0; |
| 726 | Float64 latitude_min = 0.0; |
| 727 | Float64 longitude_max = 0.0; |
| 728 | Float64 latitude_max = 0.0; |
| 729 | |
| 730 | Float64 longitude_step = 0.0; |
| 731 | Float64 latitude_step = 0.0; |
| 732 | }; |
| 733 | |
| 734 | GeohashesInBoxPreparedArgs geohashesInBoxPrepare(const Float64 longitude_min, |
| 735 | const Float64 latitude_min, |
| 736 | Float64 longitude_max, |
| 737 | Float64 latitude_max, |
| 738 | UInt8 precision); |
| 739 | |
| 740 | UInt64 geohashesInBox(const GeohashesInBoxPreparedArgs & estimation, char * out); |
| 741 | |
| 742 | } /// GeoUtils |
| 743 | |
| 744 | } /// DB |
| 745 | |