/********************************************************************** * * GEOS - Geometry Engine Open Source * http://geos.osgeo.org * * Copyright (C) 2022 ISciences LLC * Copyright (C) 2006 Refractions Research Inc. * * This is free software; you can redistribute and/or modify it under * the terms of the GNU Lesser General Public Licence as published * by the Free Software Foundation. * See the COPYING file for more information. * **********************************************************************/ #pragma once #include #include // for applyCoordinateFilter #include #include #include #include #include // ostream #include // for unique_ptr typedef // Forward declarations namespace geos { namespace geom { class Envelope; class CoordinateFilter; } } namespace geos { namespace geom { // geos::geom /** * \class CoordinateSequence geom.h geos.h * * \brief * The internal representation of a list of coordinates inside a Geometry. * * A CoordinateSequence is capable of storing XY, XYZ, XYM, or XYZM Coordinates. For efficient * storage, the dimensionality of the CoordinateSequence should be specified at creation using * the constructor with `hasz` and `hasm` arguments. Currently most of the GEOS code base * stores 2D Coordinates and accesses using the Coordinate type. Sequences used by these parts * of the code must be created with constructors without `hasz` and `hasm` arguments. * * If a high-dimension Coordinate coordinate is read from a low-dimension CoordinateSequence, * the higher dimensions will be populated with incorrect values or a segfault may occur. * */ class GEOS_DLL CoordinateSequence { public: /// Standard ordinate index values enum { X, Y, Z, M }; using iterator = CoordinateSequenceIterator; using const_iterator = CoordinateSequenceIterator; typedef std::unique_ptr Ptr; /// \defgroup construct Constructors /// @{ /** * Create an CoordinateSequence capable of storing XY or XYZ coordinates. */ CoordinateSequence(); /** * Create a CoordinateSequence capable of storing XY, XYZ or XYZM coordinates. * * @param size size of the sequence to create. * @param dim 2 for 2D, 3 for XYZ, 4 for XYZM, or 0 to determine * this based on the first coordinate in the sequence */ CoordinateSequence(std::size_t size, std::size_t dim = 0); /** * Create a CoordinateSequence that packs coordinates of any dimension. * Code using a CoordinateSequence constructed in this way must not * attempt to access references to coordinates with dimensions that * are not actually stored in the sequence. * * @param size size of the sequence to create * @param hasz true if the stored * @param hasm * @param initialize */ CoordinateSequence(std::size_t size, bool hasz, bool hasm, bool initialize = true); /** * Create a CoordinateSequence from a list of XYZ coordinates. * Code using the sequence may only access references to CoordinateXY * or Coordinate objects. */ CoordinateSequence(const std::initializer_list&); /** * Create a CoordinateSequence from a list of XY coordinates. * Code using the sequence may only access references to CoordinateXY objects. */ CoordinateSequence(const std::initializer_list&); /** * Create a CoordinateSequence from a list of XYM coordinates. * Code using the sequence may only access references to CoordinateXY * or CoordinateXYM objects. */ CoordinateSequence(const std::initializer_list&); /** * Create a CoordinateSequence from a list of XYZM coordinates. */ CoordinateSequence(const std::initializer_list&); /** * Create a CoordinateSequence storing XY values only. * * @param size size of the sequence to create */ static CoordinateSequence XY(std::size_t size) { return CoordinateSequence(size, false, false); } /** * Create a CoordinateSequence storing XYZ values only. * * @param size size of the sequence to create */ static CoordinateSequence XYZ(std::size_t size) { return CoordinateSequence(size, true, false); } /** * Create a CoordinateSequence storing XYZM values only. * * @param size size of the sequence to create */ static CoordinateSequence XYZM(std::size_t size) { return CoordinateSequence(size, true, true); } /** * Create a CoordinateSequence storing XYM values only. * * @param size size of the sequence to create */ static CoordinateSequence XYM(std::size_t size) { return CoordinateSequence(size, false, true); } /** \brief * Returns a heap-allocated deep copy of this CoordinateSequence. */ std::unique_ptr clone() const; /// @} /// \defgroup prop Properties /// @{ /** * Return the Envelope containing all points in this sequence. * The Envelope is not cached and is computed each time the * method is called. */ Envelope getEnvelope() const; /** \brief * Returns the number of Coordinates */ std::size_t getSize() const { return size(); } /** \brief * Returns the number of Coordinates */ size_t size() const { assert(stride() == 2 || stride() == 3 || stride() == 4); switch(stride()) { case 2: return m_vect.size() / 2; case 4: return m_vect.size() / 4; default : return m_vect.size() / 3; } } /// Returns true if list contains no coordinates. bool isEmpty() const { return m_vect.empty(); } /// Returns true if there is 1 coordinate and if it is null. bool isNullPoint() const { if (size() != 1) { return false; } switch(getCoordinateType()) { case CoordinateType::XY: return getAt(0).isNull(); case CoordinateType::XYZ: return getAt(0).isNull(); case CoordinateType::XYZM: return getAt(0).isNull(); case CoordinateType::XYM: return getAt(0).isNull(); default: return false; } } /** \brief * Tests whether an a {@link CoordinateSequence} forms a ring, * by checking length and closure. Self-intersection is not checked. * * @return true if the coordinate form a ring. */ bool isRing() const; /** * Returns the dimension (number of ordinates in each coordinate) * for this sequence. * * @return the dimension of the sequence. */ std::size_t getDimension() const; bool hasZ() const { return m_hasdim ? m_hasz : (m_vect.empty() || !std::isnan(m_vect[2])); } bool hasM() const { return m_hasm; } /// Returns true if contains any two consecutive points bool hasRepeatedPoints() const; /// Returns true if contains any NaN/Inf coordinates bool hasRepeatedOrInvalidPoints() const; /// Get the backing type of this CoordinateSequence. This is not necessarily /// consistent with the dimensionality of the stored Coordinates; 2D Coordinates /// may be stored as a XYZ coordinates. CoordinateType getCoordinateType() const { switch(stride()) { case 4: return CoordinateType::XYZM; case 2: return CoordinateType::XY; default: return hasM() ? CoordinateType::XYM : CoordinateType::XYZ; } } /// @} /// \defgroup access Accessors /// @{ /** \brief * Returns a read-only reference to Coordinate at position i. */ template const T& getAt(std::size_t i) const { static_assert(std::is_base_of::value, "Must be a Coordinate class"); assert(sizeof(T) <= sizeof(double) * stride()); assert(i*stride() < m_vect.size()); const T* orig = reinterpret_cast(&m_vect[i*stride()]); return *orig; } /** \brief * Returns a reference to Coordinate at position i. */ template T& getAt(std::size_t i) { static_assert(std::is_base_of::value, "Must be a Coordinate class"); assert(sizeof(T) <= sizeof(double) * stride()); assert(i*stride() < m_vect.size()); T* orig = reinterpret_cast(&m_vect[i*stride()]); return *orig; } /** \brief * Write Coordinate at position i to given Coordinate. */ template void getAt(std::size_t i, T& c) const { switch(getCoordinateType()) { case CoordinateType::XY: c = getAt(i); break; case CoordinateType::XYZ: c = getAt(i); break; case CoordinateType::XYZM: c = getAt(i); break; case CoordinateType::XYM: c = getAt(i); break; default: getAt(i); } } void getAt(std::size_t i, CoordinateXY& c) const { c = getAt(i); } // TODO: change to return CoordinateXY /** * Returns a read-only reference to Coordinate at i */ const Coordinate& operator[](std::size_t i) const { return getAt(i); } // TODO: change to return CoordinateXY /** * Returns a reference to Coordinate at i */ Coordinate& operator[](std::size_t i) { return getAt(i); } /** * Returns the ordinate of a coordinate in this sequence. * Ordinate indices 0 and 1 are assumed to be X and Y. * Ordinates indices greater than 1 have user-defined semantics * (for instance, they may contain other dimensions or measure values). * * @param index the coordinate index in the sequence * @param ordinateIndex the ordinate index in the coordinate * (in range [0, dimension-1]) */ double getOrdinate(std::size_t index, std::size_t ordinateIndex) const; /** * Returns ordinate X (0) of the specified coordinate. * * @param index * @return the value of the X ordinate in the index'th coordinate */ double getX(std::size_t index) const { return m_vect[index * stride()]; } /** * Returns ordinate Y (1) of the specified coordinate. * * @param index * @return the value of the Y ordinate in the index'th coordinate */ double getY(std::size_t index) const { return m_vect[index * stride() + 1]; } /// Return last Coordinate in the sequence template const T& back() const { return getAt(size() - 1); } /// Return last Coordinate in the sequence template T& back() { return getAt(size() - 1); } /// Return first Coordinate in the sequence template const T& front() const { return *(reinterpret_cast(m_vect.data())); } /// Return first Coordinate in the sequence template T& front() { return *(reinterpret_cast(m_vect.data())); } /// Pushes all Coordinates of this sequence into the provided vector. void toVector(std::vector& coords) const; void toVector(std::vector& coords) const; /// @} /// \defgroup mutate Mutators /// @{ /// Copy Coordinate c to position pos template void setAt(const T& c, std::size_t pos) { switch(getCoordinateType()) { case CoordinateType::XY: setAtImpl(c, pos); break; case CoordinateType::XYZ: setAtImpl(c, pos); break; case CoordinateType::XYZM: setAtImpl(c, pos); break; case CoordinateType::XYM: setAtImpl(c, pos); break; default: setAtImpl(c, pos); } } /** * Sets the value for a given ordinate of a coordinate in this sequence. * * @param index the coordinate index in the sequence * @param ordinateIndex the ordinate index in the coordinate * (in range [0, dimension-1]) * @param value the new ordinate value */ void setOrdinate(std::size_t index, std::size_t ordinateIndex, double value); /// Substitute Coordinate list with a copy of the given vector void setPoints(const std::vector& v); /// @} /// \defgroup add Adding methods /// @{ /// Adds the specified coordinate to the end of the sequence. Dimensions /// present in the coordinate but not in the sequence will be ignored. /// If multiple coordinates are to be added, a multiple-insert method should /// be used for best performance. template void add(const T& c) { add(c, size()); } /// Adds the specified coordinate to the end of the sequence. Dimensions /// present in the coordinate but not in the sequence will be ignored. If /// allowRepeated is false, the coordinate will not be added if it is the /// same as the last coordinate in the sequence. /// If multiple coordinates are to be added, a multiple-insert method should /// be used for best performance. template void add(const T& c, bool allowRepeated) { if(!allowRepeated && !isEmpty()) { const CoordinateXY& last = back(); if(last.equals2D(c)) { return; } } add(c); } /** \brief * Inserts the specified coordinate at the specified position in * this sequence. If multiple coordinates are to be added, a multiple- * insert method should be used for best performance. * * @param c the coordinate to insert * @param pos the position at which to insert */ template void add(const T& c, std::size_t pos) { static_assert(std::is_base_of::value, "Must be a Coordinate class"); // c may be a reference inside m_vect, so we make sure it will not // grow before adding it if (m_vect.size() + stride() <= m_vect.capacity()) { make_space(pos, 1); setAt(c, static_cast(pos)); } else { T tmp{c}; make_space(pos, 1); setAt(tmp, static_cast(pos)); } } /** \brief * Inserts the specified coordinate at the specified position in * this list. * * @param i the position at which to insert * @param coord the coordinate to insert * @param allowRepeated if set to false, repeated coordinates are * collapsed */ template void add(std::size_t i, const T& coord, bool allowRepeated) { // don't add duplicate coordinates if(! allowRepeated) { std::size_t sz = size(); if(sz > 0) { if(i > 0) { const CoordinateXY& prev = getAt(i - 1); if(prev.equals2D(coord)) { return; } } if(i < sz) { const CoordinateXY& next = getAt(i); if(next.equals2D(coord)) { return; } } } } add(coord, i); } void add(double x, double y) { CoordinateXY c(x, y); add(c); } void add(const CoordinateSequence& cs); void add(const CoordinateSequence& cs, bool allowRepeated); void add(const CoordinateSequence& cl, bool allowRepeated, bool forwardDirection); void add(const CoordinateSequence& cs, std::size_t from, std::size_t to); void add(const CoordinateSequence& cs, std::size_t from, std::size_t to, bool allowRepeated); template void add(T begin, T end, Args... args) { for (auto it = begin; it != end; ++it) { add(*it, args...); } } template void add(std::size_t i, T from, T to) { auto npts = static_cast(std::distance(from, to)); make_space(i, npts); for (auto it = from; it != to; ++it) { setAt(*it, i); i++; } } /// @} /// \defgroup util Utilities /// @{ void clear() { m_vect.clear(); } void reserve(std::size_t capacity) { m_vect.reserve(capacity * stride()); } void resize(std::size_t capacity) { m_vect.resize(capacity * stride()); } void pop_back(); /// Get a string representation of CoordinateSequence std::string toString() const; /// Returns lower-left Coordinate in list const CoordinateXY* minCoordinate() const; /** \brief * Returns either the given CoordinateSequence if its length * is greater than the given amount, or an empty CoordinateSequence. */ static CoordinateSequence* atLeastNCoordinatesOrNothing(std::size_t n, CoordinateSequence* c); /// Return position of a Coordinate // /// or numeric_limits::max() if not found /// static std::size_t indexOf(const CoordinateXY* coordinate, const CoordinateSequence* cl); /** * \brief * Returns true if the two arrays are identical, both null, * or pointwise equal in two dimensions */ static bool equals(const CoordinateSequence* cl1, const CoordinateSequence* cl2); /** * \brief * Returns true if the two sequences are identical (pointwise * equal in all dimensions, with NaN == NaN). */ bool equalsIdentical(const CoordinateSequence& other) const; /// Scroll given CoordinateSequence so to start with given Coordinate. static void scroll(CoordinateSequence* cl, const CoordinateXY* firstCoordinate); /** \brief * Determines which orientation of the {@link Coordinate} array * is (overall) increasing. * * In other words, determines which end of the array is "smaller" * (using the standard ordering on {@link Coordinate}). * Returns an integer indicating the increasing direction. * If the sequence is a palindrome, it is defined to be * oriented in a positive direction. * * @param pts the array of Coordinates to test * @return 1 if the array is smaller at the start * or is a palindrome, * -1 if smaller at the end * * NOTE: this method is found in CoordinateArrays class for JTS */ static int increasingDirection(const CoordinateSequence& pts); /// Reverse Coordinate order in given CoordinateSequence void reverse(); void sort(); /** * Expands the given Envelope to include the coordinates in the * sequence. * @param env the envelope to expand */ void expandEnvelope(Envelope& env) const; void closeRing(bool allowRepeated = false); /// @} /// \defgroup iterate Iteration /// @{ template void apply_rw(const Filter* filter) { switch(getCoordinateType()) { case CoordinateType::XY: for (auto& c : items()) { filter->filter_rw(&c); } break; case CoordinateType::XYZ: for (auto& c : items()) { filter->filter_rw(&c); } break; case CoordinateType::XYM: for (auto& c : items()) { filter->filter_rw(&c); } break; case CoordinateType::XYZM: for (auto& c : items()) { filter->filter_rw(&c); } break; } m_hasdim = m_hasz = false; // re-check (see http://trac.osgeo.org/geos/ticket/435) } template void apply_ro(Filter* filter) const { switch(getCoordinateType()) { case CoordinateType::XY: for (const auto& c : items()) { filter->filter_ro(&c); } break; case CoordinateType::XYZ: for (const auto& c : items()) { filter->filter_ro(&c); } break; case CoordinateType::XYM: for (const auto& c : items()) { filter->filter_ro(&c); } break; case CoordinateType::XYZM: for (const auto& c : items()) { filter->filter_ro(&c); } break; } } template void forEach(F&& fun) const { switch(getCoordinateType()) { case CoordinateType::XY: for (const auto& c : items()) { fun(c); } break; case CoordinateType::XYZ: for (const auto& c : items()) { fun(c); } break; case CoordinateType::XYM: for (const auto& c : items()) { fun(c); } break; case CoordinateType::XYZM: for (const auto& c : items()) { fun(c); } break; } } template void forEach(F&& fun) const { for (std::size_t i = 0; i < size(); i++) { fun(getAt(i)); } } template void forEach(std::size_t from, std::size_t to, F&& fun) const { for (std::size_t i = from; i <= to; i++) { fun(getAt(i)); } } template class Coordinates { public: using SequenceType = typename std::conditional::value, const CoordinateSequence, CoordinateSequence>::type; explicit Coordinates(SequenceType* seq) : m_seq(seq) {} CoordinateSequenceIterator begin() { return {m_seq}; } CoordinateSequenceIterator end() { return {m_seq, m_seq->getSize()}; } CoordinateSequenceIterator::type> begin() const { return CoordinateSequenceIterator::type>{m_seq}; } CoordinateSequenceIterator::type> end() const { return CoordinateSequenceIterator::type>{m_seq, m_seq->getSize()}; } CoordinateSequenceIterator::type> cbegin() const { return CoordinateSequenceIterator::type>{m_seq}; } CoordinateSequenceIterator::type> cend() const { return CoordinateSequenceIterator::type>{m_seq, m_seq->getSize()}; } private: SequenceType* m_seq; }; template Coordinates::type> items() const { return Coordinates::type>(this); } template Coordinates items() { return Coordinates(this); } /// @} double* data() { return m_vect.data(); } const double* data() const { return m_vect.data(); } private: std::vector m_vect; // Vector to store values uint8_t m_stride; // Stride of stored values, corresponding to underlying type mutable bool m_hasdim; // Has the dimension of this sequence been determined? Or was it created with no // explicit dimensionality, and we're waiting for getDimension() to be called // after some coordinates have been added? mutable bool m_hasz; bool m_hasm; void initialize(); template void setAtImpl(const T2& c, std::size_t pos) { auto& orig = getAt(pos); orig = c; } void make_space(std::size_t pos, std::size_t n) { m_vect.insert(std::next(m_vect.begin(), static_cast(pos * stride())), m_stride * n, DoubleNotANumber); } std::uint8_t stride() const { return m_stride; } }; GEOS_DLL std::ostream& operator<< (std::ostream& os, const CoordinateSequence& cs); GEOS_DLL bool operator== (const CoordinateSequence& s1, const CoordinateSequence& s2); GEOS_DLL bool operator!= (const CoordinateSequence& s1, const CoordinateSequence& s2); } // namespace geos::geom } // namespace geos