// Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License, version 2.0, // as published by the Free Software Foundation. // // This program is also distributed with certain software (including // but not limited to OpenSSL) that is licensed under separate terms, // as designated in a particular file or component or in included license // documentation. The authors of MySQL hereby grant you an additional // permission to link the program and your derivative works with the // separately licensed software that they have included with MySQL. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License, version 2.0, for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. /// @file /// /// This file implements utility functions for working with geometrycollections. #include "sql/gis/gc_utils.h" #include // boost::geometry::difference #include "my_dbug.h" // DBUG_ASSERT #include "sql/gis/difference_functor.h" #include "sql/gis/geometries.h" #include "sql/gis/geometries_cs.h" #include "sql/gis/geometries_traits.h" #include "sql/gis/union_functor.h" #include "template_utils.h" // down_cast namespace bg = boost::geometry; namespace gis { template static void typed_split_gc(const GC *gc, MPt *mpt, MLs *mls, MPy *mpy) { DBUG_ASSERT(gc->coordinate_system() == mpt->coordinate_system() && gc->coordinate_system() == mls->coordinate_system() && gc->coordinate_system() == mpy->coordinate_system()); for (const auto g : *gc) { switch (g->type()) { case Geometry_type::kPoint: mpt->push_back(*down_cast(g)); break; case Geometry_type::kLinestring: mls->push_back(*down_cast(g)); break; case Geometry_type::kPolygon: mpy->push_back(*down_cast(g)); break; case Geometry_type::kGeometrycollection: typed_split_gc(down_cast(g), mpt, mls, mpy); break; case Geometry_type::kMultipoint: { const MPt *m = down_cast(g); for (std::size_t i = 0; i < m->size(); i++) mpt->push_back(static_cast((*m)[i])); break; } case Geometry_type::kMultilinestring: { const MLs *m = down_cast(g); for (std::size_t i = 0; i < m->size(); i++) mls->push_back(static_cast((*m)[i])); break; } case Geometry_type::kMultipolygon: { const MPy *m = down_cast(g); for (std::size_t i = 0; i < m->size(); i++) mpy->push_back(static_cast((*m)[i])); break; } case Geometry_type::kGeometry: DBUG_ASSERT(false); break; } } } void split_gc(const Geometrycollection *gc, std::unique_ptr *mpt, std::unique_ptr *mls, std::unique_ptr *mpy) { switch (gc->coordinate_system()) { case Coordinate_system::kCartesian: mpt->reset(new Cartesian_multipoint()); mls->reset(new Cartesian_multilinestring()); mpy->reset(new Cartesian_multipolygon()); typed_split_gc( down_cast(gc), down_cast(mpt->get()), down_cast(mls->get()), down_cast(mpy->get())); break; case Coordinate_system::kGeographic: mpt->reset(new Geographic_multipoint()); mls->reset(new Geographic_multilinestring()); mpy->reset(new Geographic_multipolygon()); typed_split_gc( down_cast(gc), down_cast(mpt->get()), down_cast(mls->get()), down_cast(mpy->get())); break; } } template void typed_gc_union(double semi_major, double semi_minor, std::unique_ptr *mpt, std::unique_ptr *mls, std::unique_ptr *mpy) { Difference difference(semi_major, semi_minor); Union union_(semi_major, semi_minor); std::unique_ptr polygons(new MPy()); for (auto &py : *down_cast(mpy->get())) { polygons.reset(down_cast(union_(polygons.get(), &py))); if (polygons->coordinate_system() == Coordinate_system::kGeographic && polygons->is_empty()) { // The result of a union between a geographic multipolygon and a // geographic polygon is empty. There are two reasons why this may happen: // // 1. One of the polygons involved are invalid. // 2. One of the polygons involved covers half the globe, or more. // // Since invalid input is only reported to the extent it is explicitly // detected, we can simply return a too large polygon error in both cases. throw too_large_polygon_exception(); } } std::unique_ptr linestrings(new MLs()); linestrings.reset(down_cast(difference(mls->get(), polygons.get()))); std::unique_ptr points(down_cast( difference(down_cast(mpt->get()), linestrings.get()))); points.reset(down_cast(difference(points.get(), polygons.get()))); mpy->reset(polygons.release()); mls->reset(linestrings.release()); mpt->reset(points.release()); } void gc_union(double semi_major, double semi_minor, std::unique_ptr *mpt, std::unique_ptr *mls, std::unique_ptr *mpy) { DBUG_ASSERT(mpt->get() && mls->get() && mpy->get()); DBUG_ASSERT((*mpt)->coordinate_system() == (*mls)->coordinate_system() && (*mpt)->coordinate_system() == (*mpy)->coordinate_system()); // We're using empty GCs to detect invalid geometries, so empty geometry // collections should be filtered out before calling gc_union. DBUG_ASSERT(!(*mpt)->empty() || !(*mls)->empty() || !(*mpy)->empty()); switch ((*mpt)->coordinate_system()) { case Coordinate_system::kCartesian: { typed_gc_union(semi_major, semi_minor, mpt, mls, mpy); break; } case Coordinate_system::kGeographic: { typed_gc_union(semi_major, semi_minor, mpt, mls, mpy); break; } } // If all collections are empty, we've encountered at least one invalid // geometry. if ((*mpt)->empty() && (*mls)->empty() && (*mpy)->empty()) throw invalid_geometry_exception(); DBUG_ASSERT(mpt->get() && mls->get() && mpy->get()); DBUG_ASSERT(!(*mpt)->empty() || !(*mls)->empty() || !(*mpy)->empty()); } } // namespace gis