diff --git a/.github/workflows/StableDistributionPipeline.yml b/.github/workflows/StableDistributionPipeline.yml index 2e16bfa1..d9c223b4 100644 --- a/.github/workflows/StableDistributionPipeline.yml +++ b/.github/workflows/StableDistributionPipeline.yml @@ -5,13 +5,13 @@ name: Stable Extension Distribution Pipeline on: pull_request: branches: - - v0.10.1 + - v0.10.2 paths-ignore: - '**/README.md' - 'doc/**' push: branches: - - v0.10.1 + - v0.10.2 paths-ignore: - '**/README.md' - 'doc/**' @@ -24,17 +24,17 @@ concurrency: jobs: duckdb-stable-build: name: Build extension binaries - uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@v0.10.1 + uses: duckdb/extension-ci-tools/.github/workflows/_extension_distribution.yml@v0.10.2 with: - duckdb_version: v0.10.1 + duckdb_version: v0.10.2 extension_name: spatial duckdb-stable-deploy: name: Deploy extension binaries needs: duckdb-stable-build - uses: duckdb/extension-ci-tools/.github/workflows/_extension_deploy.yml@v0.10.1 + uses: duckdb/extension-ci-tools/.github/workflows/_extension_deploy.yml@v0.10.2 secrets: inherit with: - duckdb_version: v0.10.1 + duckdb_version: v0.10.2 extension_name: spatial - deploy_latest: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/v0.10.1' }} + deploy_latest: ${{ startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/v0.10.2' }} diff --git a/duckdb b/duckdb index 6598220b..148a1025 160000 --- a/duckdb +++ b/duckdb @@ -1 +1 @@ -Subproject commit 6598220b953b0be8a612ea1a9d5c1bd85c5379c8 +Subproject commit 148a1025d1d486d4c2751728bb6ac70e4f3bea61 diff --git a/spatial/include/spatial/core/functions/scalar.hpp b/spatial/include/spatial/core/functions/scalar.hpp index f65322f3..2c446e95 100644 --- a/spatial/include/spatial/core/functions/scalar.hpp +++ b/spatial/include/spatial/core/functions/scalar.hpp @@ -33,6 +33,7 @@ struct CoreScalarFunctions { RegisterStHilbert(db); RegisterStIntersects(db); RegisterStIntersectsExtent(db); + RegisterStIsClosed(db); RegisterStIsEmpty(db); RegisterStLength(db); RegisterStMakeEnvelope(db); @@ -137,6 +138,9 @@ struct CoreScalarFunctions { // ST_IntersectsExtent (&&) static void RegisterStIntersectsExtent(DatabaseInstance &db); + // ST_IsClosed + static void RegisterStIsClosed(DatabaseInstance &db); + // ST_IsEmpty static void RegisterStIsEmpty(DatabaseInstance &db); diff --git a/spatial/include/spatial/core/geometry/geometry.hpp b/spatial/include/spatial/core/geometry/geometry.hpp index 1a019c6e..efc60928 100644 --- a/spatial/include/spatial/core/geometry/geometry.hpp +++ b/spatial/include/spatial/core/geometry/geometry.hpp @@ -2,7 +2,7 @@ #include "spatial/common.hpp" #include "spatial/core/geometry/geometry_properties.hpp" -#include "spatial/core/geometry/cursor.hpp" +#include "spatial/core/util/cursor.hpp" #include "spatial/core/geometry/geometry_type.hpp" #include "spatial/core/geometry/vertex.hpp" @@ -10,62 +10,81 @@ namespace spatial { namespace core { -//------------------------------------------------------------------------------ -// Geometry Objects -//------------------------------------------------------------------------------ - class Geometry; //------------------------------------------------------------------------------ -// Base Classes +// Geometry //------------------------------------------------------------------------------ -class BaseGeometry { - friend class Geometry; - friend class Wrap; +class Geometry { + friend struct SinglePartGeometry; + friend struct MultiPartGeometry; + friend struct CollectionGeometry; -protected: +private: GeometryType type; GeometryProperties properties; bool is_readonly; uint32_t data_count; - union { - Geometry *part_data; - data_ptr_t vertex_data; - } data; + data_ptr_t data_ptr; + + Geometry(GeometryType type, GeometryProperties props, bool is_readonly, data_ptr_t data, uint32_t count) + : type(type), properties(props), is_readonly(is_readonly), data_count(count), data_ptr(data) { + } + + // TODO: Maybe make these public... + Geometry &operator[](uint32_t index) { + D_ASSERT(index < data_count); + return reinterpret_cast(data_ptr)[index]; + } + Geometry *begin() { + return reinterpret_cast(data_ptr); + } + Geometry *end() { + return reinterpret_cast(data_ptr) + data_count; + } + + const Geometry &operator[](uint32_t index) const { + D_ASSERT(index < data_count); + return reinterpret_cast(data_ptr)[index]; + } + const Geometry *begin() const { + return reinterpret_cast(data_ptr); + } + const Geometry *end() const { + return reinterpret_cast(data_ptr) + data_count; + } - BaseGeometry(GeometryType type, Geometry *part_data, bool is_readonly, bool has_z, bool has_m) - : type(type), properties(has_z, has_m), is_readonly(is_readonly), data_count(0), data({nullptr}) { - data.part_data = part_data; +public: + // By default, create a read-only empty point + Geometry() + : type(GeometryType::POINT), properties(false, false), is_readonly(true), data_count(0), data_ptr(nullptr) { } - BaseGeometry(GeometryType type, data_ptr_t vert_data, bool is_readonly, bool has_z, bool has_m) - : type(type), properties(has_z, has_m), is_readonly(is_readonly), data_count(0), data({nullptr}) { - data.vertex_data = vert_data; + Geometry(GeometryType type, bool has_z, bool has_m) + : type(type), properties(has_z, has_m), is_readonly(true), data_count(0), data_ptr(nullptr) { } // Copy Constructor - BaseGeometry(const BaseGeometry &other) + Geometry(const Geometry &other) : type(other.type), properties(other.properties), is_readonly(true), data_count(other.data_count), - data({nullptr}) { - data = other.data; + data_ptr(other.data_ptr) { } // Copy Assignment - BaseGeometry &operator=(const BaseGeometry &other) { + Geometry &operator=(const Geometry &other) { type = other.type; properties = other.properties; is_readonly = true; data_count = other.data_count; - data = other.data; + data_ptr = other.data_ptr; return *this; } // Move Constructor - BaseGeometry(BaseGeometry &&other) noexcept + Geometry(Geometry &&other) noexcept : type(other.type), properties(other.properties), is_readonly(other.is_readonly), data_count(other.data_count), - data({nullptr}) { - data = other.data; + data_ptr(other.data_ptr) { if (!other.is_readonly) { // Take ownership of the data, and make the other object read-only other.is_readonly = true; @@ -73,12 +92,12 @@ class BaseGeometry { } // Move Assignment - BaseGeometry &operator=(BaseGeometry &&other) noexcept { + Geometry &operator=(Geometry &&other) noexcept { type = other.type; properties = other.properties; is_readonly = other.is_readonly; data_count = other.data_count; - data = other.data; + data_ptr = other.data_ptr; if (!other.is_readonly) { // Take ownership of the data, and make the other object read-only other.is_readonly = true; @@ -87,800 +106,844 @@ class BaseGeometry { } public: - GeometryProperties GetProperties() const { - return properties; + GeometryType GetType() const { + return type; } GeometryProperties &GetProperties() { return properties; } + const GeometryProperties &GetProperties() const { + return properties; + } + const_data_ptr_t GetData() const { + return data_ptr; + } + data_ptr_t GetData() { + return data_ptr; + } bool IsReadOnly() const { return is_readonly; } uint32_t Count() const { return data_count; } + + bool IsCollection() const { + return GeometryTypes::IsCollection(type); + } + bool IsMultiPart() const { + return GeometryTypes::IsMultiPart(type); + } + bool IsSinglePart() const { + return GeometryTypes::IsSinglePart(type); + } + +public: + // Used for tag dispatching + struct Tags { + // Base types + struct AnyGeometry {}; + struct SinglePartGeometry : public AnyGeometry {}; + struct MultiPartGeometry : public AnyGeometry {}; + struct CollectionGeometry : public MultiPartGeometry {}; + // Concrete types + struct Point : public SinglePartGeometry {}; + struct LineString : public SinglePartGeometry {}; + struct Polygon : public MultiPartGeometry {}; + struct MultiPoint : public CollectionGeometry {}; + struct MultiLineString : public CollectionGeometry {}; + struct MultiPolygon : public CollectionGeometry {}; + struct GeometryCollection : public CollectionGeometry {}; + }; + + template + static auto Match(Geometry &geom, ARGS &&...args) + -> decltype(T::Case(std::declval(), std::declval(), std::declval()...)) { + switch (geom.type) { + case GeometryType::POINT: + return T::Case(Tags::Point {}, geom, std::forward(args)...); + case GeometryType::LINESTRING: + return T::Case(Tags::LineString {}, geom, std::forward(args)...); + case GeometryType::POLYGON: + return T::Case(Tags::Polygon {}, geom, std::forward(args)...); + case GeometryType::MULTIPOINT: + return T::Case(Tags::MultiPoint {}, geom, std::forward(args)...); + case GeometryType::MULTILINESTRING: + return T::Case(Tags::MultiLineString {}, geom, std::forward(args)...); + case GeometryType::MULTIPOLYGON: + return T::Case(Tags::MultiPolygon {}, geom, std::forward(args)...); + case GeometryType::GEOMETRYCOLLECTION: + return T::Case(Tags::GeometryCollection {}, geom, std::forward(args)...); + default: + throw NotImplementedException("Geometry::Match"); + } + } + + template + static auto Match(const Geometry &geom, ARGS &&...args) + -> decltype(T::Case(std::declval(), std::declval(), std::declval()...)) { + switch (geom.type) { + case GeometryType::POINT: + return T::Case(Tags::Point {}, geom, std::forward(args)...); + case GeometryType::LINESTRING: + return T::Case(Tags::LineString {}, geom, std::forward(args)...); + case GeometryType::POLYGON: + return T::Case(Tags::Polygon {}, geom, std::forward(args)...); + case GeometryType::MULTIPOINT: + return T::Case(Tags::MultiPoint {}, geom, std::forward(args)...); + case GeometryType::MULTILINESTRING: + return T::Case(Tags::MultiLineString {}, geom, std::forward(args)...); + case GeometryType::MULTIPOLYGON: + return T::Case(Tags::MultiPolygon {}, geom, std::forward(args)...); + case GeometryType::GEOMETRYCOLLECTION: + return T::Case(Tags::GeometryCollection {}, geom, std::forward(args)...); + default: + throw NotImplementedException("Geometry::Match"); + } + } + + // TODO: Swap this to only have two create methods, + // and use mutating methods for Reference/Copy + static Geometry Create(ArenaAllocator &alloc, GeometryType type, uint32_t count, bool has_z, bool has_m); + static Geometry CreateEmpty(GeometryType type, bool has_z, bool has_m); + + static geometry_t Serialize(const Geometry &geom, Vector &result); + static Geometry Deserialize(ArenaAllocator &arena, const geometry_t &data); + + static bool IsEmpty(const Geometry &geom); + static uint32_t GetDimension(const Geometry &geom, bool recurse); + void SetVertexType(ArenaAllocator &alloc, bool has_z, bool has_m, double default_z = 0, double default_m = 0); + + // Iterate over all points in the geometry, recursing into collections + template + static void ExtractPoints(const Geometry &geom, FUNC &&func); + + // Iterate over all lines in the geometry, recursing into collections + template + static void ExtractLines(const Geometry &geom, FUNC &&func); + + // Iterate over all polygons in the geometry, recursing into collections + template + static void ExtractPolygons(const Geometry &geom, FUNC &&func); }; -static_assert(sizeof(BaseGeometry) <= 16, "GeometryBase should be at most 16 bytes (can be less in WASM)"); +inline Geometry Geometry::Create(ArenaAllocator &alloc, GeometryType type, uint32_t count, bool has_z, bool has_m) { + GeometryProperties props(has_z, has_m); + auto single_part = GeometryTypes::IsSinglePart(type); + auto elem_size = single_part ? props.VertexSize() : sizeof(Geometry); + auto geom = Geometry(type, props, false, alloc.AllocateAligned(count * elem_size), count); + return geom; +} +inline Geometry Geometry::CreateEmpty(GeometryType type, bool has_z, bool has_m) { + GeometryProperties props(has_z, has_m); + return Geometry(type, props, false, nullptr, 0); +} + +//------------------------------------------------------------------------------ +// Inlined Geometry Functions //------------------------------------------------------------------------------ -// All of the following classes are just to provide a type-safe interface to the underlying geometry data, and to -// enable convenient matching of geometry types using function overloads. +template +inline void Geometry::ExtractPoints(const Geometry &geom, FUNC &&func) { + struct op { + static void Case(Geometry::Tags::Point, const Geometry &geom, FUNC &&func) { + func(geom); + } + static void Case(Geometry::Tags::MultiPoint, const Geometry &geom, FUNC &&func) { + for (auto &part : geom) { + func(part); + } + } + static void Case(Geometry::Tags::GeometryCollection, const Geometry &geom, FUNC &&func) { + for (auto &part : geom) { + Match(part, std::forward(func)); + } + } + static void Case(Geometry::Tags::AnyGeometry, const Geometry &, FUNC &&) { + } + }; + Match(geom, std::forward(func)); +} -// A single part geometry, contains a single array of vertices -class SinglePartGeometry : public BaseGeometry { - friend class Geometry; +template +inline void Geometry::ExtractLines(const Geometry &geom, FUNC &&func) { + struct op { + static void Case(Geometry::Tags::LineString, const Geometry &geom, FUNC &&func) { + func(geom); + } + static void Case(Geometry::Tags::MultiLineString, const Geometry &geom, FUNC &&func) { + for (auto &part : geom) { + func(part); + } + } + static void Case(Geometry::Tags::GeometryCollection, const Geometry &geom, FUNC &&func) { + for (auto &part : geom) { + Match(part, std::forward(func)); + } + } + static void Case(Geometry::Tags::AnyGeometry, const Geometry &, FUNC &&) { + } + }; + Match(geom, std::forward(func)); +} -protected: - SinglePartGeometry(GeometryType type, bool has_z, bool has_m) - : BaseGeometry(type, (data_ptr_t) nullptr, true, has_z, has_m) { - } +template +inline void Geometry::ExtractPolygons(const Geometry &geom, FUNC &&func) { + struct op { + static void Case(Geometry::Tags::Polygon, const Geometry &geom, FUNC &&func) { + func(geom); + } + static void Case(Geometry::Tags::MultiPolygon, const Geometry &geom, FUNC &&func) { + for (auto &part : geom) { + func(part); + } + } + static void Case(Geometry::Tags::GeometryCollection, const Geometry &geom, FUNC &&func) { + for (auto &part : geom) { + Match(part, std::forward(func)); + } + } + static void Case(Geometry::Tags::AnyGeometry, const Geometry &, FUNC &&) { + } + }; + Match(geom, std::forward(func)); +} - SinglePartGeometry(GeometryType type, ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) - : BaseGeometry(type, (data_ptr_t) nullptr, false, has_z, has_m) { - data_count = count; - data.vertex_data = alloc.AllocateAligned(count * properties.VertexSize()); +inline bool Geometry::IsEmpty(const Geometry &geom) { + struct op { + static bool Case(Geometry::Tags::SinglePartGeometry, const Geometry &geom) { + return geom.data_count == 0; + } + static bool Case(Geometry::Tags::MultiPartGeometry, const Geometry &geom) { + for (const auto &part : geom) { + if (!Geometry::Match(part)) { + return false; + } + } + return true; + } + }; + return Geometry::Match(geom); +} + +inline uint32_t Geometry::GetDimension(const Geometry &geom, bool ignore_empty) { + if (ignore_empty && Geometry::IsEmpty(geom)) { + return 0; } + struct op { + static uint32_t Case(Geometry::Tags::Point, const Geometry &, bool) { + return 0; + } + static uint32_t Case(Geometry::Tags::LineString, const Geometry &, bool) { + return 1; + } + static uint32_t Case(Geometry::Tags::Polygon, const Geometry &, bool) { + return 2; + } + static uint32_t Case(Geometry::Tags::MultiPoint, const Geometry &, bool) { + return 0; + } + static uint32_t Case(Geometry::Tags::MultiLineString, const Geometry &, bool) { + return 1; + } + static uint32_t Case(Geometry::Tags::MultiPolygon, const Geometry &, bool) { + return 2; + } + static uint32_t Case(Geometry::Tags::GeometryCollection, const Geometry &geom, bool ignore_empty) { + uint32_t max_dimension = 0; + for (const auto &p : geom) { + max_dimension = std::max(max_dimension, Geometry::GetDimension(p, ignore_empty)); + } + return max_dimension; + } + }; + return Geometry::Match(geom, ignore_empty); +} + +//------------------------------------------------------------------------------ +// Iterators +//------------------------------------------------------------------------------ +class PartView { +private: + Geometry *beg_ptr; + Geometry *end_ptr; public: - uint32_t ByteSize() const { - return data_count * properties.VertexSize(); + PartView(Geometry *beg, Geometry *end) : beg_ptr(beg), end_ptr(end) { } - - bool IsEmpty() const { - return data_count == 0; + Geometry *begin() { + return beg_ptr; } - - const_data_ptr_t GetData() const { - return data.vertex_data; + Geometry *end() { + return end_ptr; } - - void Set(uint32_t index, const VertexXY &vertex) { - D_ASSERT(index < data_count); - Store(vertex, data.vertex_data + index * properties.VertexSize()); + Geometry &operator[](uint32_t index) { + return beg_ptr[index]; } +}; - void Set(uint32_t index, double x, double y) { - Set(index, VertexXY {x, y}); - } +class ConstPartView { +private: + const Geometry *beg_ptr; + const Geometry *end_ptr; - VertexXY Get(uint32_t index) const { - D_ASSERT(index < data_count); - return Load(data.vertex_data + index * properties.VertexSize()); +public: + ConstPartView(const Geometry *beg, const Geometry *end) : beg_ptr(beg), end_ptr(end) { } - - template - void SetExact(uint32_t index, const V &vertex) { - static_assert(V::IS_VERTEX, "V must be a vertex type"); - D_ASSERT(V::HAS_Z == properties.HasZ()); - D_ASSERT(V::HAS_M == properties.HasM()); - D_ASSERT(index < data_count); - Store(vertex, data.vertex_data + index * sizeof(V)); + const Geometry *begin() { + return beg_ptr; } - - template - V GetExact(uint32_t index) const { - static_assert(V::IS_VERTEX, "V must be a vertex type"); - D_ASSERT(V::HAS_Z == properties.HasZ()); - D_ASSERT(V::HAS_M == properties.HasM()); - D_ASSERT(index < data_count); - return Load(data.vertex_data + index * sizeof(V)); + const Geometry *end() { + return end_ptr; } + const Geometry &operator[](uint32_t index) { + return beg_ptr[index]; + } +}; - // Turn this geometry into a read-only reference to another geometry, starting at the specified index - void Reference(const SinglePartGeometry &other, uint32_t offset, uint32_t count); +//------------------------------------------------------------------------------ +// Accessors +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// SinglePartGeometry +//------------------------------------------------------------------------------ +struct SinglePartGeometry { // Turn this geometry into a read-only reference to raw data - void ReferenceData(const_data_ptr_t data, uint32_t count, bool has_z, bool has_m); + static void ReferenceData(Geometry &geom, const_data_ptr_t data, uint32_t count, bool has_z, bool has_m) { + geom.data_count = count; + geom.data_ptr = const_cast(data); + geom.is_readonly = true; + geom.properties.SetZ(has_z); + geom.properties.SetM(has_m); + } - void ReferenceData(const_data_ptr_t data, uint32_t count) { - ReferenceData(data, count, properties.HasZ(), properties.HasM()); + // Turn this geometry into a read-only reference to another geometry, starting at the specified index + static void ReferenceData(Geometry &geom, const Geometry &other, uint32_t offset, uint32_t count) { + D_ASSERT(GeometryTypes::IsSinglePart(other.GetType())); + D_ASSERT(offset + count <= other.data_count); + auto vertex_size = other.properties.VertexSize(); + auto has_z = other.properties.HasZ(); + auto has_m = other.properties.HasM(); + ReferenceData(geom, other.data_ptr + offset * vertex_size, count, has_z, has_m); } - // Turn this geometry into a owning copy of another geometry, starting at the specified index - void Copy(ArenaAllocator &alloc, const SinglePartGeometry &other, uint32_t offset, uint32_t count); + static void ReferenceData(Geometry &geom, const_data_ptr_t data, uint32_t count) { + ReferenceData(geom, data, count, geom.properties.HasZ(), geom.properties.HasM()); + } // Turn this geometry into a owning copy of raw data - void CopyData(ArenaAllocator &alloc, const_data_ptr_t data, uint32_t count, bool has_z, bool has_m); + static void CopyData(Geometry &geom, ArenaAllocator &alloc, const_data_ptr_t data, uint32_t count, bool has_z, + bool has_m) { + auto old_vertex_size = geom.properties.VertexSize(); + geom.properties.SetZ(has_z); + geom.properties.SetM(has_m); + auto new_vertex_size = geom.properties.VertexSize(); + if (geom.is_readonly) { + geom.data_ptr = alloc.AllocateAligned(count * new_vertex_size); + } else if (geom.data_count != count) { + geom.data_ptr = + alloc.ReallocateAligned(geom.data_ptr, geom.data_count * old_vertex_size, count * new_vertex_size); + } + memcpy(geom.data_ptr, data, count * new_vertex_size); + geom.data_count = count; + geom.is_readonly = false; + } - void CopyData(ArenaAllocator &alloc, const_data_ptr_t data, uint32_t count) { - CopyData(alloc, data, count, properties.HasZ(), properties.HasM()); + // Turn this geometry into a owning copy of another geometry, starting at the specified index + static void CopyData(Geometry &geom, ArenaAllocator &alloc, const Geometry &other, uint32_t offset, + uint32_t count) { + D_ASSERT(GeometryTypes::IsSinglePart(other.GetType())); + D_ASSERT(offset + count <= other.data_count); + auto vertex_size = geom.properties.VertexSize(); + auto has_z = other.properties.HasZ(); + auto has_m = other.properties.HasM(); + CopyData(geom, alloc, other.data_ptr + offset * vertex_size, count, has_z, has_m); + } + + static void CopyData(Geometry &geom, ArenaAllocator &alloc, const_data_ptr_t data, uint32_t count) { + CopyData(geom, alloc, data, count, geom.properties.HasZ(), geom.properties.HasM()); } // Resize the geometry, truncating or extending with zeroed vertices as needed - void Resize(ArenaAllocator &alloc, uint32_t new_count); + static void Resize(Geometry &geom, ArenaAllocator &alloc, uint32_t new_count); // Append the data from another geometry - void Append(ArenaAllocator &alloc, const SinglePartGeometry &other); + static void Append(Geometry &geom, ArenaAllocator &alloc, const Geometry &other); // Append the data from multiple other geometries - void Append(ArenaAllocator &alloc, const SinglePartGeometry *others, uint32_t others_count); + static void Append(Geometry &geom, ArenaAllocator &alloc, const Geometry *others, uint32_t others_count); // Force the geometry to have a specific vertex type, resizing or shrinking the data as needed - void SetVertexType(ArenaAllocator &alloc, bool has_z, bool has_m, double default_z = 0, double default_m = 0); + static void SetVertexType(Geometry &geom, ArenaAllocator &alloc, bool has_z, bool has_m, double default_z = 0, + double default_m = 0); // If this geometry is read-only, make it mutable by copying the data - void MakeMutable(ArenaAllocator &alloc); + static void MakeMutable(Geometry &geom, ArenaAllocator &alloc); // Print this geometry as a string, starting at the specified index and printing the specified number of vertices // (useful for debugging) - string ToString(uint32_t start = 0, uint32_t count = 0) const; + static string ToString(const Geometry &geom, uint32_t start = 0, uint32_t count = 0); // Check if the geometry is closed (first and last vertex are the same) // A geometry with 1 vertex is considered closed, 0 vertices are considered open - bool IsClosed() const; + static bool IsClosed(const Geometry &geom); + static bool IsEmpty(const Geometry &geom); // Return the planar length of the geometry - double Length() const; -}; + static double Length(const Geometry &geom); -// A multi-part geometry, contains multiple parts -class MultiPartGeometry : public BaseGeometry { -protected: - MultiPartGeometry(GeometryType type, bool has_z, bool has_m) - : BaseGeometry(type, (Geometry *)nullptr, true, has_z, has_m) { - } + static VertexXY GetVertex(const Geometry &geom, uint32_t index); + static void SetVertex(Geometry &geom, uint32_t index, const VertexXY &vertex); - MultiPartGeometry(GeometryType type, ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) - : BaseGeometry(type, (Geometry *)nullptr, false, has_z, has_m) { - data_count = count; - data.vertex_data = alloc.AllocateAligned(count * sizeof(BaseGeometry)); - } - -public: - bool IsEmpty() const; - - Geometry &operator[](uint32_t index); - Geometry *begin(); - Geometry *end(); + template + static V GetVertex(const Geometry &geom, uint32_t index); - const Geometry &operator[](uint32_t index) const; - const Geometry *begin() const; - const Geometry *end() const; + template + static void SetVertex(Geometry &geom, uint32_t index, const V &vertex); - void Resize(ArenaAllocator &alloc, uint32_t new_count); + static uint32_t VertexCount(const Geometry &geom); + static uint32_t VertexSize(const Geometry &geom); + static uint32_t ByteSize(const Geometry &geom); }; -class CollectionGeometry : public MultiPartGeometry { -protected: - CollectionGeometry(GeometryType type, bool has_z, bool has_m) : MultiPartGeometry(type, has_z, has_m) { - } - CollectionGeometry(GeometryType type, ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) - : MultiPartGeometry(type, alloc, count, has_z, has_m) { - } -}; +inline VertexXY SinglePartGeometry::GetVertex(const Geometry &geom, uint32_t index) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.GetType())); + D_ASSERT(index < geom.data_count); + return Load(geom.GetData() + index * geom.GetProperties().VertexSize()); +} -template -class TypedCollectionGeometry : public CollectionGeometry { -protected: - TypedCollectionGeometry(GeometryType type, bool has_z, bool has_m) : CollectionGeometry(type, has_z, has_m) { - } - TypedCollectionGeometry(GeometryType type, ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m); +inline void SinglePartGeometry::SetVertex(Geometry &geom, uint32_t index, const VertexXY &vertex) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.GetType())); + D_ASSERT(index < geom.data_count); + Store(vertex, geom.GetData() + index * geom.GetProperties().VertexSize()); +} -public: - T &operator[](uint32_t index); - T *begin(); - T *end(); +template +inline V SinglePartGeometry::GetVertex(const Geometry &geom, uint32_t index) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.GetType())); + D_ASSERT(V::HAS_Z == geom.GetProperties().HasZ()); + D_ASSERT(V::HAS_M == geom.GetProperties().HasM()); + D_ASSERT(index < geom.data_count); + return Load(geom.GetData() + index * sizeof(V)); +} - const T &operator[](uint32_t index) const; - const T *begin() const; - const T *end() const; -}; +template +inline void SinglePartGeometry::SetVertex(Geometry &geom, uint32_t index, const V &vertex) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.GetType())); + D_ASSERT(V::HAS_Z == geom.GetProperties().HasZ()); + D_ASSERT(V::HAS_M == geom.GetProperties().HasM()); + D_ASSERT(index < geom.data_count); + Store(vertex, geom.GetData() + index * sizeof(V)); +} -//------------------------------------------------------------------------------ -// Concrete Classes -//------------------------------------------------------------------------------ -// These are the actual Geometry types that are instantiated and used in practice +inline uint32_t SinglePartGeometry::VertexCount(const Geometry &geom) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.GetType())); + return geom.data_count; +} -class Point : public SinglePartGeometry { -protected: - friend class TypedCollectionGeometry; - friend class GeometryCollection; +inline uint32_t SinglePartGeometry::VertexSize(const Geometry &geom) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.GetType())); + return geom.GetProperties().VertexSize(); +} - Point(bool has_z = false, bool has_m = false) : SinglePartGeometry(TYPE, has_z, has_m) { - } - Point(ArenaAllocator &alloc, uint32_t size, bool has_z, bool has_m) - : SinglePartGeometry(TYPE, alloc, size, has_z, has_m) { - } +inline uint32_t SinglePartGeometry::ByteSize(const Geometry &geom) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.GetType())); + return geom.data_count * geom.GetProperties().VertexSize(); +} -public: - static constexpr GeometryType TYPE = GeometryType::POINT; +inline bool SinglePartGeometry::IsEmpty(const Geometry &geom) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.GetType())); + return geom.data_count == 0; +} - static Point Empty(bool has_z = false, bool has_m = false) { - return Point(has_z, has_m); - } - static Point Create(ArenaAllocator &alloc, uint32_t size, bool has_z, bool has_m) { - return Point(alloc, size, has_z, has_m); - } +//------------------------------------------------------------------------------ +// MultiPartGeometry +//------------------------------------------------------------------------------ +struct MultiPartGeometry { - // Helpers - template - static Point FromVertex(ArenaAllocator &alloc, const V &vertex) { - static_assert(V::IS_VERTEX, "V must be a vertex type"); - auto point = Point::Create(alloc, 1, V::HAS_Z, V::HAS_M); - point.SetExact(0, vertex); - return point; - } + // static void Resize(Geometry &geom, ArenaAllocator &alloc, uint32_t new_count); - static Point CopyFromData(ArenaAllocator &alloc, const_data_ptr_t data, uint32_t count, bool has_z, bool has_m) { - auto point = Point::Create(alloc, 1, has_z, has_m); - point.CopyData(alloc, data, 1); - return point; - } + static uint32_t PartCount(const Geometry &geom); + static Geometry &Part(Geometry &geom, uint32_t index); + static const Geometry &Part(const Geometry &geom, uint32_t index); + static PartView Parts(Geometry &geom); + static ConstPartView Parts(const Geometry &geom); - static Point FromReference(const SinglePartGeometry &other, uint32_t offset) { - Point point(other.GetProperties().HasZ(), other.GetProperties().HasM()); - point.Reference(other, offset, 1); - return point; + static bool IsEmpty(const Geometry &geom) { + D_ASSERT(GeometryTypes::IsMultiPart(geom.GetType())); + for (uint32_t i = 0; i < geom.data_count; i++) { + if (!Geometry::IsEmpty(Part(geom, i))) { + return false; + } + } + return true; } }; -class LineString : public SinglePartGeometry { -protected: - friend class TypedCollectionGeometry; - friend class Polygon; +inline uint32_t MultiPartGeometry::PartCount(const Geometry &geom) { + D_ASSERT(GeometryTypes::IsMultiPart(geom.GetType())); + return geom.data_count; +} - LineString(bool has_z = false, bool has_m = false) : SinglePartGeometry(TYPE, has_z, has_m) { - } - LineString(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) - : SinglePartGeometry(TYPE, alloc, count, has_z, has_m) { - } +inline Geometry &MultiPartGeometry::Part(Geometry &geom, uint32_t index) { + D_ASSERT(GeometryTypes::IsMultiPart(geom.GetType())); + D_ASSERT(index < geom.data_count); + return *reinterpret_cast(geom.GetData() + index * sizeof(Geometry)); +} -public: - static constexpr GeometryType TYPE = GeometryType::LINESTRING; +inline const Geometry &MultiPartGeometry::Part(const Geometry &geom, uint32_t index) { + D_ASSERT(GeometryTypes::IsMultiPart(geom.GetType())); + D_ASSERT(index < geom.data_count); + return *reinterpret_cast(geom.GetData() + index * sizeof(Geometry)); +} - static LineString Empty(bool has_z = false, bool has_m = false) { - return LineString(has_z, has_m); - } - static LineString Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { - return LineString(alloc, count, has_z, has_m); - } +inline PartView MultiPartGeometry::Parts(Geometry &geom) { + D_ASSERT(GeometryTypes::IsMultiPart(geom.GetType())); + auto ptr = reinterpret_cast(geom.GetData()); + return {ptr, ptr + geom.data_count}; +} - static LineString CopyFromData(ArenaAllocator &alloc, const_data_ptr_t data, uint32_t count, bool has_z, - bool has_m) { - LineString line(alloc, count, has_z, has_m); - line.CopyData(alloc, data, count); - return line; - } -}; +inline ConstPartView MultiPartGeometry::Parts(const Geometry &geom) { + D_ASSERT(GeometryTypes::IsMultiPart(geom.GetType())); + auto ptr = reinterpret_cast(geom.GetData()); + return {ptr, ptr + geom.data_count}; +} -class Polygon : public MultiPartGeometry { +//------------------------------------------------------------------------------ +// CollectionGeometry +//------------------------------------------------------------------------------ +struct CollectionGeometry : public MultiPartGeometry { protected: - friend class TypedCollectionGeometry; - - Polygon(bool has_z = false, bool has_m = false) : MultiPartGeometry(TYPE, has_z, has_m) { + static Geometry Create(ArenaAllocator &alloc, GeometryType type, vector &items, bool has_z, bool has_m) { + D_ASSERT(GeometryTypes::IsCollection(type)); + auto collection = Geometry::Create(alloc, type, items.size(), has_z, has_m); + for (uint32_t i = 0; i < items.size(); i++) { + CollectionGeometry::Part(collection, i) = std::move(items[i]); + } + return collection; } - Polygon(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m); +}; -public: - static constexpr GeometryType TYPE = GeometryType::POLYGON; +//------------------------------------------------------------------------------ +// Point +//------------------------------------------------------------------------------ +struct Point : public SinglePartGeometry { + static Geometry Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m); + static Geometry CreateEmpty(bool has_z, bool has_m); - static Polygon Empty(bool has_z = false, bool has_m = false) { - return Polygon(has_z, has_m); - } - static Polygon Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { - return Polygon(alloc, count, has_z, has_m); + template + static Geometry CreateFromVertex(ArenaAllocator &alloc, const V &vertex); + + static Geometry CreateFromCopy(ArenaAllocator &alloc, const_data_ptr_t data, uint32_t count, bool has_z, + bool has_m) { + auto point = Point::Create(alloc, 1, has_z, has_m); + SinglePartGeometry::CopyData(point, alloc, data, count, has_z, has_m); + return point; } - LineString &operator[](uint32_t index); - LineString *begin(); - LineString *end(); + // Methods + template + static V GetVertex(const Geometry &geom); - const LineString &operator[](uint32_t index) const; - const LineString *begin() const; - const LineString *end() const; + template + static void SetVertex(Geometry &geom, const V &vertex); - // TODO: Generalize this to take a min/max vertex instead - static Polygon FromBox(ArenaAllocator &alloc, double minx, double miny, double maxx, double maxy) { - Polygon box(alloc, 1, false, false); - auto &ring = box[0]; - ring.Resize(alloc, 5); - ring.SetExact(0, VertexXY {minx, miny}); - ring.SetExact(1, VertexXY {minx, maxy}); - ring.SetExact(2, VertexXY {maxx, maxy}); - ring.SetExact(3, VertexXY {maxx, miny}); - ring.SetExact(4, VertexXY {minx, miny}); - return box; - } + // Constants + static const constexpr GeometryType TYPE = GeometryType::POINT; }; -class MultiPoint : public TypedCollectionGeometry { -protected: - MultiPoint(bool has_z = false, bool has_m = false) : TypedCollectionGeometry(TYPE, has_z, has_m) { - } - MultiPoint(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) - : TypedCollectionGeometry(TYPE, alloc, count, has_z, has_m) { - } +inline Geometry Point::Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { + return Geometry::Create(alloc, TYPE, count, has_z, has_m); +} -public: - static constexpr GeometryType TYPE = GeometryType::MULTIPOINT; +inline Geometry Point::CreateEmpty(bool has_z, bool has_m) { + return Geometry::CreateEmpty(TYPE, has_z, has_m); +} - static MultiPoint Empty(bool has_z = false, bool has_m = false) { - return MultiPoint(has_z, has_m); - } - static MultiPoint Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { - return MultiPoint(alloc, count, has_z, has_m); - } -}; +template +inline Geometry Point::CreateFromVertex(ArenaAllocator &alloc, const V &vertex) { + auto point = Create(alloc, 1, V::HAS_Z, V::HAS_M); + Point::SetVertex(point, vertex); + return point; +} -class MultiLineString : public TypedCollectionGeometry { -protected: - MultiLineString(bool has_z = false, bool has_m = false) : TypedCollectionGeometry(TYPE, has_z, has_m) { - } - MultiLineString(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) - : TypedCollectionGeometry(TYPE, alloc, count, has_z, has_m) { - } +template +inline V Point::GetVertex(const Geometry &geom) { + D_ASSERT(geom.GetType() == TYPE); + D_ASSERT(geom.Count() == 1); + D_ASSERT(geom.GetProperties().HasZ() == V::HAS_Z); + D_ASSERT(geom.GetProperties().HasM() == V::HAS_M); + return SinglePartGeometry::GetVertex(geom, 0); +} -public: - static constexpr GeometryType TYPE = GeometryType::MULTILINESTRING; +template +void Point::SetVertex(Geometry &geom, const V &vertex) { + D_ASSERT(geom.GetType() == TYPE); + D_ASSERT(geom.Count() == 1); + D_ASSERT(geom.GetProperties().HasZ() == V::HAS_Z); + D_ASSERT(geom.GetProperties().HasM() == V::HAS_M); + SinglePartGeometry::SetVertex(geom, 0, vertex); +} - static MultiLineString Empty(bool has_z = false, bool has_m = false) { - return MultiLineString(has_z, has_m); - } - static MultiLineString Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { - return MultiLineString(alloc, count, has_z, has_m); - } -}; +//------------------------------------------------------------------------------ +// LineString +//------------------------------------------------------------------------------ +struct LineString : public SinglePartGeometry { + static Geometry Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m); + static Geometry CreateEmpty(bool has_z, bool has_m); -class MultiPolygon : public TypedCollectionGeometry { -protected: - MultiPolygon(bool has_z = false, bool has_m = false) : TypedCollectionGeometry(TYPE, has_z, has_m) { - } - MultiPolygon(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) - : TypedCollectionGeometry(TYPE, alloc, count, has_z, has_m) { + static Geometry CreateFromCopy(ArenaAllocator &alloc, const_data_ptr_t data, uint32_t count, bool has_z, + bool has_m) { + auto line = LineString::Create(alloc, 1, has_z, has_m); + SinglePartGeometry::CopyData(line, alloc, data, count, has_z, has_m); + return line; } -public: - static constexpr GeometryType TYPE = GeometryType::MULTIPOLYGON; - - static MultiPolygon Empty(bool has_z = false, bool has_m = false) { - return MultiPolygon(has_z, has_m); + // TODO: Wrap + // Create a new LineString referencing a slice of the this linestring + static Geometry GetSliceAsReference(const Geometry &geom, uint32_t start, uint32_t count) { + auto line = LineString::CreateEmpty(geom.GetProperties().HasZ(), geom.GetProperties().HasM()); + SinglePartGeometry::ReferenceData(line, geom, start, count); + return line; } - static MultiPolygon Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { - return MultiPolygon(alloc, count, has_z, has_m); + + // TODO: Wrap + // Create a new LineString referencing a single point in the this linestring + static Geometry GetPointAsReference(const Geometry &geom, uint32_t index) { + auto count = index >= geom.Count() ? 0 : 1; + auto point = Point::CreateEmpty(geom.GetProperties().HasZ(), geom.GetProperties().HasM()); + SinglePartGeometry::ReferenceData(point, geom, index, count); + return point; } + + // Constants + static const constexpr GeometryType TYPE = GeometryType::LINESTRING; }; -class GeometryCollection : public CollectionGeometry { -protected: - GeometryCollection(bool has_z = false, bool has_m = false) : CollectionGeometry(TYPE, has_z, has_m) { - } - GeometryCollection(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m); +inline Geometry LineString::Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { + return Geometry::Create(alloc, TYPE, count, has_z, has_m); +} -public: - static constexpr GeometryType TYPE = GeometryType::GEOMETRYCOLLECTION; +inline Geometry LineString::CreateEmpty(bool has_z, bool has_m) { + return Geometry::CreateEmpty(TYPE, has_z, has_m); +} - static GeometryCollection Empty(bool has_z = false, bool has_m = false) { - return GeometryCollection(has_z, has_m); - } - static GeometryCollection Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { - return GeometryCollection(alloc, count, has_z, has_m); - } -}; +//------------------------------------------------------------------------------ +// LinearRing (special case of LineString) +//------------------------------------------------------------------------------ +struct LinearRing : public LineString { + static Geometry Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m); + static Geometry CreateEmpty(bool has_z, bool has_m); -class Geometry { - union { - Point point; - LineString linestring; - Polygon polygon; - MultiPoint multipoint; - MultiLineString multilinestring; - MultiPolygon multipolygon; - GeometryCollection collection; - }; + // Methods + static bool IsClosed(const Geometry &geom); -public: - // This is legal because all members is standard layout and have the same common initial sequence - // Additionally, a union is pointer-interconvertible with its first member - GeometryType GetType() const { - return point.type; - } - GeometryProperties GetProperties() const { - return point.properties; - } - GeometryProperties &GetProperties() { - return point.properties; - } - bool IsReadOnly() const { - return reinterpret_cast(*this).IsReadOnly(); - } - bool IsCollection() const { - auto type = GetType(); - return type == GeometryType::MULTIPOINT || type == GeometryType::MULTILINESTRING || - type == GeometryType::MULTIPOLYGON || type == GeometryType::GEOMETRYCOLLECTION; - } + // Constants + // TODO: We dont have a LinearRing type, so we use LineString for now + static const constexpr GeometryType TYPE = GeometryType::LINESTRING; +}; - // NOLINTBEGIN - Geometry(Point point) : point(point) { - } - Geometry(LineString linestring) : linestring(linestring) { - } - Geometry(Polygon polygon) : polygon(polygon) { - } - Geometry(MultiPoint multipoint) : multipoint(multipoint) { - } - Geometry(MultiLineString multilinestring) : multilinestring(multilinestring) { - } - Geometry(MultiPolygon multipolygon) : multipolygon(multipolygon) { - } - Geometry(GeometryCollection collection) : collection(collection) { - } - // NOLINTEND +inline Geometry LinearRing::Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { + return LineString::Create(alloc, count, has_z, has_m); +} - // Copy Constructor - Geometry(const Geometry &other) { - switch (other.GetType()) { - case GeometryType::POINT: - new (&point) Point(other.point); - break; - case GeometryType::LINESTRING: - new (&linestring) LineString(other.linestring); - break; - case GeometryType::POLYGON: - new (&polygon) Polygon(other.polygon); - break; - case GeometryType::MULTIPOINT: - new (&multipoint) MultiPoint(other.multipoint); - break; - case GeometryType::MULTILINESTRING: - new (&multilinestring) MultiLineString(other.multilinestring); - break; - case GeometryType::MULTIPOLYGON: - new (&multipolygon) MultiPolygon(other.multipolygon); - break; - case GeometryType::GEOMETRYCOLLECTION: - new (&collection) GeometryCollection(other.collection); - break; - default: - throw NotImplementedException("Geometry::Geometry(const Geometry&)"); - } - } +inline Geometry LinearRing::CreateEmpty(bool has_z, bool has_m) { + return LineString::CreateEmpty(has_z, has_m); +} - // Copy Assignment - Geometry &operator=(const Geometry &other) { - if (this == &other) { - return *this; - } - this->~Geometry(); - switch (other.GetType()) { - case GeometryType::POINT: - new (&point) Point(other.point); - break; - case GeometryType::LINESTRING: - new (&linestring) LineString(other.linestring); - break; - case GeometryType::POLYGON: - new (&polygon) Polygon(other.polygon); - break; - case GeometryType::MULTIPOINT: - new (&multipoint) MultiPoint(other.multipoint); - break; - case GeometryType::MULTILINESTRING: - new (&multilinestring) MultiLineString(other.multilinestring); - break; - case GeometryType::MULTIPOLYGON: - new (&multipolygon) MultiPolygon(other.multipolygon); - break; - case GeometryType::GEOMETRYCOLLECTION: - new (&collection) GeometryCollection(other.collection); - break; - default: - throw NotImplementedException("Geometry::operator=(const Geometry&)"); - } - return *this; +inline bool LinearRing::IsClosed(const Geometry &geom) { + D_ASSERT(geom.GetType() == TYPE); + // The difference between LineString is that a empty LinearRing is considered closed + if (LinearRing::IsEmpty(geom)) { + return true; } + return LineString::IsClosed(geom); +} - // Move Constructor - Geometry(Geometry &&other) noexcept { - switch (other.GetType()) { - case GeometryType::POINT: - new (&point) Point(std::move(other.point)); - break; - case GeometryType::LINESTRING: - new (&linestring) LineString(std::move(other.linestring)); - break; - case GeometryType::POLYGON: - new (&polygon) Polygon(std::move(other.polygon)); - break; - case GeometryType::MULTIPOINT: - new (&multipoint) MultiPoint(std::move(other.multipoint)); - break; - case GeometryType::MULTILINESTRING: - new (&multilinestring) MultiLineString(std::move(other.multilinestring)); - break; - case GeometryType::MULTIPOLYGON: - new (&multipolygon) MultiPolygon(std::move(other.multipolygon)); - break; - case GeometryType::GEOMETRYCOLLECTION: - new (&collection) GeometryCollection(std::move(other.collection)); - break; - default: - D_ASSERT(false); - new (&point) Point(std::move(other.point)); - break; - } - } +//------------------------------------------------------------------------------ +// Polygon +//------------------------------------------------------------------------------ +struct Polygon : public MultiPartGeometry { + // Constructors + static Geometry Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m); + static Geometry CreateEmpty(bool has_z, bool has_m); + static Geometry CreateFromBox(ArenaAllocator &alloc, double minx, double miny, double maxx, double maxy); + + // Methods + static const Geometry &ExteriorRing(const Geometry &geom); + static Geometry &ExteriorRing(Geometry &geom); + + // Constants + static const constexpr GeometryType TYPE = GeometryType::POLYGON; +}; - // Move Assignment - Geometry &operator=(Geometry &&other) noexcept { - if (this == &other) { - return *this; - } - this->~Geometry(); - switch (other.GetType()) { - case GeometryType::POINT: - new (&point) Point(std::move(other.point)); - break; - case GeometryType::LINESTRING: - new (&linestring) LineString(std::move(other.linestring)); - break; - case GeometryType::POLYGON: - new (&polygon) Polygon(std::move(other.polygon)); - break; - case GeometryType::MULTIPOINT: - new (&multipoint) MultiPoint(std::move(other.multipoint)); - break; - case GeometryType::MULTILINESTRING: - new (&multilinestring) MultiLineString(std::move(other.multilinestring)); - break; - case GeometryType::MULTIPOLYGON: - new (&multipolygon) MultiPolygon(std::move(other.multipolygon)); - break; - case GeometryType::GEOMETRYCOLLECTION: - new (&collection) GeometryCollection(std::move(other.collection)); - break; - default: - D_ASSERT(false); - new (&point) Point(std::move(other.point)); - break; - } - return *this; +inline Geometry Polygon::Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { + auto geom = Geometry::Create(alloc, TYPE, count, has_z, has_m); + for (uint32_t i = 0; i < count; i++) { + // Placement new + new (&Polygon::Part(geom, i)) Geometry(GeometryType::LINESTRING, has_z, has_m); } + return geom; +} - template - T &As() & { - D_ASSERT(GetType() == T::TYPE); - return reinterpret_cast(*this); - } +inline Geometry Polygon::CreateEmpty(bool has_z, bool has_m) { + return Geometry::CreateEmpty(TYPE, has_z, has_m); +} - template - const T &As() const & { - D_ASSERT(GetType() == T::TYPE); - return reinterpret_cast(*this); - } +inline Geometry Polygon::CreateFromBox(ArenaAllocator &alloc, double minx, double miny, double maxx, double maxy) { + auto polygon = Polygon::Create(alloc, 1, false, false); + auto &ring = Polygon::Part(polygon, 0); + LineString::Resize(ring, alloc, 5); + LineString::SetVertex(ring, 0, {minx, miny}); + LineString::SetVertex(ring, 1, {minx, maxy}); + LineString::SetVertex(ring, 2, {maxx, maxy}); + LineString::SetVertex(ring, 3, {maxx, miny}); + LineString::SetVertex(ring, 4, {minx, miny}); + return polygon; +} - // Apply a functor to the contained geometry - template - auto Visit(ARGS &&...args) const -> decltype(F::Apply(std::declval(), std::forward(args)...)) { - switch (GetType()) { - case GeometryType::POINT: - return F::Apply(const_cast(point), std::forward(args)...); - case GeometryType::LINESTRING: - return F::Apply(const_cast(linestring), std::forward(args)...); - case GeometryType::POLYGON: - return F::Apply(const_cast(polygon), std::forward(args)...); - case GeometryType::MULTIPOINT: - return F::Apply(const_cast(multipoint), std::forward(args)...); - case GeometryType::MULTILINESTRING: - return F::Apply(const_cast(multilinestring), std::forward(args)...); - case GeometryType::MULTIPOLYGON: - return F::Apply(const_cast(multipolygon), std::forward(args)...); - case GeometryType::GEOMETRYCOLLECTION: - return F::Apply(const_cast(collection), std::forward(args)...); - default: - throw NotImplementedException("Geometry::Visit()"); - } - } +inline Geometry &Polygon::ExteriorRing(Geometry &geom) { + D_ASSERT(geom.GetType() == TYPE); + D_ASSERT(Polygon::PartCount(geom) > 0); + return Polygon::Part(geom, 0); +} - // Apply a functor to the contained geometry - template - auto Visit(ARGS &&...args) -> decltype(F::Apply(std::declval(), std::forward(args)...)) { - switch (GetType()) { - case GeometryType::POINT: - return F::Apply(static_cast(point), std::forward(args)...); - case GeometryType::LINESTRING: - return F::Apply(static_cast(linestring), std::forward(args)...); - case GeometryType::POLYGON: - return F::Apply(static_cast(polygon), std::forward(args)...); - case GeometryType::MULTIPOINT: - return F::Apply(static_cast(multipoint), std::forward(args)...); - case GeometryType::MULTILINESTRING: - return F::Apply(static_cast(multilinestring), std::forward(args)...); - case GeometryType::MULTIPOLYGON: - return F::Apply(static_cast(multipolygon), std::forward(args)...); - case GeometryType::GEOMETRYCOLLECTION: - return F::Apply(static_cast(collection), std::forward(args)...); - default: - throw NotImplementedException("Geometry::Visit()"); - } - } +inline const Geometry &Polygon::ExteriorRing(const Geometry &geom) { + D_ASSERT(geom.GetType() == TYPE); + D_ASSERT(Polygon::PartCount(geom) > 0); + return Polygon::Part(geom, 0); +} - uint32_t GetDimension(bool skip_empty) const { - if (skip_empty && IsEmpty()) { - return 0; - } - struct op { - static uint32_t Apply(const Point &, bool) { - return 0; - } - static uint32_t Apply(const LineString &, bool) { - return 1; - } - static uint32_t Apply(const Polygon &, bool) { - return 2; - } - static uint32_t Apply(const MultiPoint &, bool) { - return 0; - } - static uint32_t Apply(const MultiLineString &, bool) { - return 1; - } - static uint32_t Apply(const MultiPolygon &, bool) { - return 2; - } - static uint32_t Apply(const GeometryCollection &gc, bool skip_empty) { - uint32_t max = 0; +//------------------------------------------------------------------------------ +// MultiPoint +//------------------------------------------------------------------------------ +struct MultiPoint : public CollectionGeometry { + static Geometry Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m); + static Geometry CreateEmpty(bool has_z, bool has_m); + static Geometry Create(ArenaAllocator &alloc, vector &items, bool has_z, bool has_m); - for (const auto &item : gc) { - max = std::max(max, item.GetDimension(skip_empty)); - } - return max; - } - }; - return Visit(skip_empty); - } + // Constants + static const constexpr GeometryType TYPE = GeometryType::MULTIPOINT; +}; - bool IsEmpty() const { - struct op { - static bool Apply(const SinglePartGeometry &g) { - return g.IsEmpty(); - } - static bool Apply(const MultiPartGeometry &g) { - return g.IsEmpty(); - } - }; - return Visit(); +inline Geometry MultiPoint::Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { + auto geom = Geometry::Create(alloc, TYPE, count, has_z, has_m); + for (uint32_t i = 0; i < count; i++) { + // Placement new + new (&MultiPoint::Part(geom, i)) Geometry(GeometryType::POINT, has_z, has_m); } + return geom; +} - void SetVertexType(ArenaAllocator &arena, bool has_z, bool has_m, double default_z = 0, double default_m = 0) { - struct op { - static void Apply(SinglePartGeometry &g, ArenaAllocator &arena, bool has_z, bool has_m, double default_z, - double default_m) { - g.SetVertexType(arena, has_z, has_m); - } - static void Apply(MultiPartGeometry &g, ArenaAllocator &arena, bool has_z, bool has_m, double default_z, - double default_m) { - g.properties.SetZ(has_z); - g.properties.SetM(has_m); - for (auto &part : g) { - part.SetVertexType(arena, has_z, has_m, default_z, default_m); - } - } - }; - Visit(arena, has_z, has_m, default_z, default_m); - } +inline Geometry MultiPoint::CreateEmpty(bool has_z, bool has_m) { + return Geometry::CreateEmpty(TYPE, has_z, has_m); +} - geometry_t Serialize(Vector &result); - static Geometry Deserialize(ArenaAllocator &arena, const geometry_t &data); -}; +inline Geometry MultiPoint::Create(ArenaAllocator &alloc, vector &items, bool has_z, bool has_m) { + return CollectionGeometry::Create(alloc, TYPE, items, has_z, has_m); +} //------------------------------------------------------------------------------ -// Inlined Methods +// MultiLineString //------------------------------------------------------------------------------ +struct MultiLineString : public CollectionGeometry { + static Geometry Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m); + static Geometry CreateEmpty(bool has_z, bool has_m); + static Geometry Create(ArenaAllocator &alloc, vector &items, bool has_z, bool has_m); -inline Polygon::Polygon(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) - : MultiPartGeometry(TYPE, alloc, count, has_z, has_m) { - auto ptr = data.part_data; - for (uint32_t i = 0; i < count; i++) { - new (ptr++) LineString(alloc, 0, has_z, has_m); - } -} + static bool IsClosed(const Geometry &geom); -inline GeometryCollection::GeometryCollection(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) - : CollectionGeometry(TYPE, alloc, count, has_z, has_m) { - auto ptr = data.part_data; + // Constants + static const constexpr GeometryType TYPE = GeometryType::MULTILINESTRING; +}; + +inline Geometry MultiLineString::Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { + auto geom = Geometry::Create(alloc, TYPE, count, has_z, has_m); for (uint32_t i = 0; i < count; i++) { - new (ptr++) Point(has_z, has_m); + // Placement new + new (&MultiLineString::Part(geom, i)) Geometry(GeometryType::LINESTRING, has_z, has_m); } + return geom; } -template -inline TypedCollectionGeometry::TypedCollectionGeometry(GeometryType type, ArenaAllocator &alloc, uint32_t count, - bool has_z, bool has_m) - : CollectionGeometry(type, alloc, count, has_z, has_m) { - auto ptr = data.part_data; - for (uint32_t i = 0; i < count; i++) { - new (ptr++) T(has_z, has_m); - } +inline Geometry MultiLineString::CreateEmpty(bool has_z, bool has_m) { + return Geometry::CreateEmpty(TYPE, has_z, has_m); } -//------------------- -// MultiPartGeometry -//------------------- +inline Geometry MultiLineString::Create(ArenaAllocator &alloc, vector &items, bool has_z, bool has_m) { + return CollectionGeometry::Create(alloc, TYPE, items, has_z, has_m); +} -inline bool MultiPartGeometry::IsEmpty() const { - for (const auto &part : *this) { - if (!part.IsEmpty()) { +inline bool MultiLineString::IsClosed(const Geometry &geom) { + if (MultiLineString::PartCount(geom) == 0) { + return false; + } + for (auto &part : MultiLineString::Parts(geom)) { + if (!LineString::IsClosed(part)) { return false; } } return true; } -inline Geometry &MultiPartGeometry::operator[](uint32_t index) { - return data.part_data[index]; -} -inline Geometry *MultiPartGeometry::begin() { - return data.part_data; -} -inline Geometry *MultiPartGeometry::end() { - return data.part_data + data_count; -} - -inline const Geometry &MultiPartGeometry::operator[](uint32_t index) const { - return data.part_data[index]; -} -inline const Geometry *MultiPartGeometry::begin() const { - return data.part_data; -} -inline const Geometry *MultiPartGeometry::end() const { - return data.part_data + data_count; -} +//------------------------------------------------------------------------------ +// MultiPolygon +//------------------------------------------------------------------------------ +struct MultiPolygon : public CollectionGeometry { + static Geometry Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m); + static Geometry CreateEmpty(bool has_z, bool has_m); + static Geometry Create(ArenaAllocator &alloc, vector &items, bool has_z, bool has_m); -//----------------- -// Polygon -//----------------- + // Constants + static const constexpr GeometryType TYPE = GeometryType::MULTIPOLYGON; +}; -inline LineString &Polygon::operator[](uint32_t index) { - return reinterpret_cast(data.part_data[index]); -} -inline LineString *Polygon::begin() { - return reinterpret_cast(data.part_data); -} -inline LineString *Polygon::end() { - return reinterpret_cast(data.part_data + data_count); +inline Geometry MultiPolygon::Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { + auto geom = Geometry::Create(alloc, TYPE, count, has_z, has_m); + for (uint32_t i = 0; i < count; i++) { + // Placement new + new (&MultiPolygon::Part(geom, i)) Geometry(GeometryType::POLYGON, has_z, has_m); + } + return geom; } -inline const LineString &Polygon::operator[](uint32_t index) const { - return reinterpret_cast(data.part_data[index]); -} -inline const LineString *Polygon::begin() const { - return reinterpret_cast(data.part_data); +inline Geometry MultiPolygon::CreateEmpty(bool has_z, bool has_m) { + return Geometry::CreateEmpty(TYPE, has_z, has_m); } -inline const LineString *Polygon::end() const { - return reinterpret_cast(data.part_data + data_count); + +inline Geometry MultiPolygon::Create(ArenaAllocator &alloc, vector &items, bool has_z, bool has_m) { + return CollectionGeometry::Create(alloc, TYPE, items, has_z, has_m); } -//----------------- -// Collection -//----------------- +//------------------------------------------------------------------------------ +// GeometryCollection +//------------------------------------------------------------------------------ +struct GeometryCollection : public CollectionGeometry { + static Geometry Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m); + static Geometry CreateEmpty(bool has_z, bool has_m); + static Geometry Create(ArenaAllocator &alloc, vector &items, bool has_z, bool has_m); -template -inline T &TypedCollectionGeometry::operator[](uint32_t index) { - return reinterpret_cast(data.part_data[index]); -} -template -inline T *TypedCollectionGeometry::begin() { - return reinterpret_cast(data.part_data); -} -template -inline T *TypedCollectionGeometry::end() { - return reinterpret_cast(data.part_data + data_count); -} + // Constants + static const constexpr GeometryType TYPE = GeometryType::GEOMETRYCOLLECTION; +}; -template -inline const T &TypedCollectionGeometry::operator[](uint32_t index) const { - return reinterpret_cast(data.part_data[index]); +inline Geometry GeometryCollection::Create(ArenaAllocator &alloc, uint32_t count, bool has_z, bool has_m) { + auto geom = Geometry::Create(alloc, TYPE, count, has_z, has_m); + for (uint32_t i = 0; i < count; i++) { + // Placement new + new (&GeometryCollection::Part(geom, i)) Geometry(GeometryType::GEOMETRYCOLLECTION, has_z, has_m); + } + return geom; } -template -inline const T *TypedCollectionGeometry::begin() const { - return reinterpret_cast(data.part_data); +inline Geometry GeometryCollection::CreateEmpty(bool has_z, bool has_m) { + return Geometry::CreateEmpty(TYPE, has_z, has_m); } -template -inline const T *TypedCollectionGeometry::end() const { - return reinterpret_cast(data.part_data + data_count); +inline Geometry GeometryCollection::Create(ArenaAllocator &alloc, vector &items, bool has_z, bool has_m) { + return CollectionGeometry::Create(alloc, TYPE, items, has_z, has_m); } //------------------------------------------------------------------------------ @@ -888,57 +951,6 @@ inline const T *TypedCollectionGeometry::end() const { //------------------------------------------------------------------------------ static_assert(std::is_standard_layout::value, "Geometry must be standard layout"); -static_assert(std::is_standard_layout::value, "BaseGeometry must be standard layout"); -static_assert(std::is_standard_layout::value, "SinglePartBase must be standard layout"); -static_assert(std::is_standard_layout::value, "Point must be standard layout"); -static_assert(std::is_standard_layout::value, "Point must be standard layout"); -static_assert(std::is_standard_layout::value, "Polygon must be standard layout"); -static_assert(std::is_standard_layout::value, "LineString must be standard layout"); -static_assert(std::is_standard_layout::value, "MultiPolygon must be standard layout"); -static_assert(std::is_standard_layout::value, "MultiLineString must be standard layout"); -static_assert(std::is_standard_layout::value, "MultiPoint must be standard layout"); -static_assert(std::is_standard_layout::value, "GeometryCollection must be standard layout"); - -//------------------------------------------------------------------------------ -// Utils -//------------------------------------------------------------------------------ - -struct Utils { - static string format_coord(double d); - static string format_coord(double x, double y); - static string format_coord(double x, double y, double z); - static string format_coord(double x, double y, double z, double m); - - static inline float DoubleToFloatDown(double d) { - if (d > static_cast(std::numeric_limits::max())) { - return std::numeric_limits::max(); - } - if (d <= static_cast(std::numeric_limits::lowest())) { - return std::numeric_limits::lowest(); - } - - auto f = static_cast(d); - if (static_cast(f) <= d) { - return f; - } - return std::nextafter(f, std::numeric_limits::lowest()); - } - - static inline float DoubleToFloatUp(double d) { - if (d >= static_cast(std::numeric_limits::max())) { - return std::numeric_limits::max(); - } - if (d < static_cast(std::numeric_limits::lowest())) { - return std::numeric_limits::lowest(); - } - - auto f = static_cast(d); - if (static_cast(f) >= d) { - return f; - } - return std::nextafter(f, std::numeric_limits::max()); - } -}; } // namespace core diff --git a/spatial/include/spatial/core/geometry/geometry_processor.hpp b/spatial/include/spatial/core/geometry/geometry_processor.hpp index ffd5cf28..3567bcae 100644 --- a/spatial/include/spatial/core/geometry/geometry_processor.hpp +++ b/spatial/include/spatial/core/geometry/geometry_processor.hpp @@ -2,7 +2,7 @@ #include "spatial/common.hpp" #include "spatial/core/geometry/geometry.hpp" -#include "spatial/core/geometry/cursor.hpp" +#include "spatial/core/util/cursor.hpp" #include "spatial/core/geometry/geometry_type.hpp" namespace spatial { diff --git a/spatial/include/spatial/core/geometry/geometry_type.hpp b/spatial/include/spatial/core/geometry/geometry_type.hpp index 0daaf377..855da92d 100644 --- a/spatial/include/spatial/core/geometry/geometry_type.hpp +++ b/spatial/include/spatial/core/geometry/geometry_type.hpp @@ -2,7 +2,7 @@ #include "spatial/common.hpp" #include "spatial/core/geometry/bbox.hpp" #include "spatial/core/geometry/geometry_properties.hpp" -#include "spatial/core/geometry/cursor.hpp" +#include "spatial/core/util/cursor.hpp" namespace spatial { @@ -18,6 +18,44 @@ enum class GeometryType : uint8_t { GEOMETRYCOLLECTION }; +struct GeometryTypes { + static bool IsSinglePart(GeometryType type) { + return type == GeometryType::POINT || type == GeometryType::LINESTRING; + } + + static bool IsMultiPart(GeometryType type) { + return type == GeometryType::POLYGON || type == GeometryType::MULTIPOINT || + type == GeometryType::MULTILINESTRING || type == GeometryType::MULTIPOLYGON || + type == GeometryType::GEOMETRYCOLLECTION; + } + + static bool IsCollection(GeometryType type) { + return type == GeometryType::MULTIPOINT || type == GeometryType::MULTILINESTRING || + type == GeometryType::MULTIPOLYGON || type == GeometryType::GEOMETRYCOLLECTION; + } + + static string ToString(GeometryType type) { + switch (type) { + case GeometryType::POINT: + return "POINT"; + case GeometryType::LINESTRING: + return "LINESTRING"; + case GeometryType::POLYGON: + return "POLYGON"; + case GeometryType::MULTIPOINT: + return "MULTIPOINT"; + case GeometryType::MULTILINESTRING: + return "MULTILINESTRING"; + case GeometryType::MULTIPOLYGON: + return "MULTIPOLYGON"; + case GeometryType::GEOMETRYCOLLECTION: + return "GEOMETRYCOLLECTION"; + default: + return StringUtil::Format("UNKNOWN(%d)", static_cast(type)); + } + } +}; + enum class SerializedGeometryType : uint32_t { POINT = 0, LINESTRING, diff --git a/spatial/include/spatial/core/geometry/geometry_writer.hpp b/spatial/include/spatial/core/geometry/geometry_writer.hpp index da333009..2cde435d 100644 --- a/spatial/include/spatial/core/geometry/geometry_writer.hpp +++ b/spatial/include/spatial/core/geometry/geometry_writer.hpp @@ -2,7 +2,8 @@ #include "spatial/common.hpp" #include "spatial/core/geometry/geometry.hpp" -#include "spatial/core/geometry/cursor.hpp" +#include "spatial/core/util/cursor.hpp" +#include "spatial/core/util/math.hpp" #include "spatial/core/geometry/geometry_type.hpp" #include "spatial/core/geometry/geometry_processor.hpp" namespace spatial { @@ -150,10 +151,10 @@ class GeometryWriter { if (properties.HasBBox()) { // Write the bbox after the first 8 bytes - buffer.WriteOffset(Utils::DoubleToFloatDown(stats.min_x), 8); - buffer.WriteOffset(Utils::DoubleToFloatDown(stats.min_y), 12); - buffer.WriteOffset(Utils::DoubleToFloatUp(stats.max_x), 16); - buffer.WriteOffset(Utils::DoubleToFloatUp(stats.max_y), 20); + buffer.WriteOffset(MathUtil::DoubleToFloatDown(stats.min_x), 8); + buffer.WriteOffset(MathUtil::DoubleToFloatDown(stats.min_y), 12); + buffer.WriteOffset(MathUtil::DoubleToFloatUp(stats.max_x), 16); + buffer.WriteOffset(MathUtil::DoubleToFloatUp(stats.max_y), 20); string_t blob = string_t {const_char_ptr_cast(buffer.GetPtr()), buffer.Size()}; return blob; } else { diff --git a/spatial/include/spatial/core/geometry/vertex_processor.hpp b/spatial/include/spatial/core/geometry/vertex_processor.hpp index 842db895..4d154cd8 100644 --- a/spatial/include/spatial/core/geometry/vertex_processor.hpp +++ b/spatial/include/spatial/core/geometry/vertex_processor.hpp @@ -2,7 +2,7 @@ #include "spatial/common.hpp" #include "spatial/core/geometry/geometry.hpp" -#include "spatial/core/geometry/cursor.hpp" +#include "spatial/core/util/cursor.hpp" #include "spatial/core/geometry/geometry_type.hpp" namespace spatial { diff --git a/spatial/include/spatial/core/geometry/wkb_reader.hpp b/spatial/include/spatial/core/geometry/wkb_reader.hpp index 34baec11..c50fdfc5 100644 --- a/spatial/include/spatial/core/geometry/wkb_reader.hpp +++ b/spatial/include/spatial/core/geometry/wkb_reader.hpp @@ -22,16 +22,16 @@ class WKBReader { uint32_t ReadInt(Cursor &cursor, bool little_endian); double ReadDouble(Cursor &cursor, bool little_endian); WKBType ReadType(Cursor &cursor, bool little_endian); - void ReadVertices(Cursor &cursor, bool little_endian, bool has_z, bool has_m, SinglePartGeometry &geometry); + void ReadVertices(Cursor &cursor, bool little_endian, bool has_z, bool has_m, Geometry &geometry); // Geometries - Point ReadPoint(Cursor &cursor, bool little_endian, bool has_z, bool has_m); - LineString ReadLineString(Cursor &cursor, bool little_endian, bool has_z, bool has_m); - Polygon ReadPolygon(Cursor &cursor, bool little_endian, bool has_z, bool has_m); - MultiPoint ReadMultiPoint(Cursor &cursor, bool little_endian); - MultiLineString ReadMultiLineString(Cursor &cursor, bool little_endian); - MultiPolygon ReadMultiPolygon(Cursor &cursor, bool little_endian); - GeometryCollection ReadGeometryCollection(Cursor &cursor, bool little_endian); + Geometry ReadPoint(Cursor &cursor, bool little_endian, bool has_z, bool has_m); + Geometry ReadLineString(Cursor &cursor, bool little_endian, bool has_z, bool has_m); + Geometry ReadPolygon(Cursor &cursor, bool little_endian, bool has_z, bool has_m); + Geometry ReadMultiPoint(Cursor &cursor, bool little_endian, bool has_z, bool has_m); + Geometry ReadMultiLineString(Cursor &cursor, bool little_endian, bool has_z, bool has_m); + Geometry ReadMultiPolygon(Cursor &cursor, bool little_endian, bool has_z, bool has_m); + Geometry ReadGeometryCollection(Cursor &cursor, bool little_endian, bool has_z, bool has_m); Geometry ReadGeometry(Cursor &cursor); public: diff --git a/spatial/include/spatial/core/geometry/wkt_reader.hpp b/spatial/include/spatial/core/geometry/wkt_reader.hpp index e545fb89..015ac9b2 100644 --- a/spatial/include/spatial/core/geometry/wkt_reader.hpp +++ b/spatial/include/spatial/core/geometry/wkt_reader.hpp @@ -25,13 +25,14 @@ class WKTReader { void Expect(char c); void ParseVertex(vector &coords); pair> ParseVertices(); - Point ParsePoint(); - LineString ParseLineString(); - Polygon ParsePolygon(); - MultiPoint ParseMultiPoint(); - MultiLineString ParseMultiLineString(); - MultiPolygon ParseMultiPolygon(); - GeometryCollection ParseGeometryCollection(); + + Geometry ParsePoint(); + Geometry ParseLineString(); + Geometry ParsePolygon(); + Geometry ParseMultiPoint(); + Geometry ParseMultiLineString(); + Geometry ParseMultiPolygon(); + Geometry ParseGeometryCollection(); void CheckZM(); Geometry ParseGeometry(); Geometry ParseWKT(); diff --git a/spatial/include/spatial/core/module.hpp b/spatial/include/spatial/core/module.hpp index 941e37a6..c6b69790 100644 --- a/spatial/include/spatial/core/module.hpp +++ b/spatial/include/spatial/core/module.hpp @@ -1,6 +1,5 @@ #pragma once #include "spatial/common.hpp" -#include "spatial/core/geometry/cursor.hpp" #include "spatial/core/geometry/geometry.hpp" namespace spatial { diff --git a/spatial/include/spatial/core/geometry/cursor.hpp b/spatial/include/spatial/core/util/cursor.hpp similarity index 100% rename from spatial/include/spatial/core/geometry/cursor.hpp rename to spatial/include/spatial/core/util/cursor.hpp diff --git a/spatial/include/spatial/core/util/math.hpp b/spatial/include/spatial/core/util/math.hpp new file mode 100644 index 00000000..ba916041 --- /dev/null +++ b/spatial/include/spatial/core/util/math.hpp @@ -0,0 +1,46 @@ +#include "spatial/common.hpp" + +namespace spatial { + +namespace core { + +struct MathUtil { + static string format_coord(double d); + static string format_coord(double x, double y); + static string format_coord(double x, double y, double z); + static string format_coord(double x, double y, double z, double m); + + static inline float DoubleToFloatDown(double d) { + if (d > static_cast(std::numeric_limits::max())) { + return std::numeric_limits::max(); + } + if (d <= static_cast(std::numeric_limits::lowest())) { + return std::numeric_limits::lowest(); + } + + auto f = static_cast(d); + if (static_cast(f) <= d) { + return f; + } + return std::nextafter(f, std::numeric_limits::lowest()); + } + + static inline float DoubleToFloatUp(double d) { + if (d >= static_cast(std::numeric_limits::max())) { + return std::numeric_limits::max(); + } + if (d < static_cast(std::numeric_limits::lowest())) { + return std::numeric_limits::lowest(); + } + + auto f = static_cast(d); + if (static_cast(f) >= d) { + return f; + } + return std::nextafter(f, std::numeric_limits::max()); + } +}; + +} // namespace core + +} // namespace spatial \ No newline at end of file diff --git a/spatial/include/spatial/gdal/functions.hpp b/spatial/include/spatial/gdal/functions.hpp index 9bf2945c..0a4bb2a5 100644 --- a/spatial/include/spatial/gdal/functions.hpp +++ b/spatial/include/spatial/gdal/functions.hpp @@ -3,6 +3,7 @@ #include "duckdb/function/table/arrow.hpp" #include "duckdb/parser/parsed_data/copy_info.hpp" #include "duckdb/function/copy_function.hpp" +#include "duckdb/function/replacement_scan.hpp" #include "spatial/common.hpp" @@ -26,8 +27,8 @@ struct GdalTableFunction : ArrowTableFunction { static unique_ptr Cardinality(ClientContext &context, const FunctionData *data); - static unique_ptr ReplacementScan(ClientContext &context, const string &table_name, - ReplacementScanData *data); + static unique_ptr ReplacementScan(ClientContext &context, ReplacementScanInput &input, + optional_ptr data); public: static void Register(DatabaseInstance &db); diff --git a/spatial/include/spatial/geos/functions/scalar.hpp b/spatial/include/spatial/geos/functions/scalar.hpp index 0f2d2826..62aff4eb 100644 --- a/spatial/include/spatial/geos/functions/scalar.hpp +++ b/spatial/include/spatial/geos/functions/scalar.hpp @@ -25,7 +25,6 @@ struct GEOSScalarFunctions { RegisterStEnvelope(db); RegisterStIntersection(db); RegisterStIntersects(db); - RegisterStIsClosed(db); RegisterStIsRing(db); RegisterStIsSimple(db); RegisterStIsValid(db); @@ -63,7 +62,6 @@ struct GEOSScalarFunctions { static void RegisterStEnvelope(DatabaseInstance &db); static void RegisterStIntersection(DatabaseInstance &db); static void RegisterStIntersects(DatabaseInstance &db); - static void RegisterStIsClosed(DatabaseInstance &db); static void RegisterStIsRing(DatabaseInstance &db); static void RegisterStIsSimple(DatabaseInstance &db); static void RegisterStIsValid(DatabaseInstance &db); diff --git a/spatial/src/spatial/core/CMakeLists.txt b/spatial/src/spatial/core/CMakeLists.txt index a97c59c9..a817dc0d 100644 --- a/spatial/src/spatial/core/CMakeLists.txt +++ b/spatial/src/spatial/core/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(geometry) add_subdirectory(functions) add_subdirectory(io) +add_subdirectory(util) set(EXTENSION_SOURCES ${EXTENSION_SOURCES} diff --git a/spatial/src/spatial/core/functions/aggregate/st_envelope_agg.cpp b/spatial/src/spatial/core/functions/aggregate/st_envelope_agg.cpp index 40dab936..e6a58c7d 100644 --- a/spatial/src/spatial/core/functions/aggregate/st_envelope_agg.cpp +++ b/spatial/src/spatial/core/functions/aggregate/st_envelope_agg.cpp @@ -75,8 +75,8 @@ struct EnvelopeAggFunction { finalize_data.ReturnNull(); } else { auto &arena = finalize_data.input.allocator; - auto box = Polygon::FromBox(arena, state.xmin, state.ymin, state.xmax, state.ymax); - target = Geometry(box).Serialize(finalize_data.result); + auto box = Polygon::CreateFromBox(arena, state.xmin, state.ymin, state.xmax, state.ymax); + target = Geometry::Serialize(box, finalize_data.result); } } diff --git a/spatial/src/spatial/core/functions/cast/geometry_cast.cpp b/spatial/src/spatial/core/functions/cast/geometry_cast.cpp index 67d4174e..3558c3db 100644 --- a/spatial/src/spatial/core/functions/cast/geometry_cast.cpp +++ b/spatial/src/spatial/core/functions/cast/geometry_cast.cpp @@ -22,8 +22,8 @@ static bool Point2DToGeometryCast(Vector &source, Vector &result, idx_t count, C auto &arena = lstate.arena; GenericExecutor::ExecuteUnary(source, result, count, [&](POINT_TYPE &point) { - auto geom = Point::FromVertex(arena, VertexXY {point.a_val, point.b_val}); - return Geometry(geom).Serialize(result); + auto geom = Point::CreateFromVertex(arena, VertexXY {point.a_val, point.b_val}); + return Geometry::Serialize(geom, result); }); return true; } @@ -43,12 +43,11 @@ static bool GeometryToPoint2DCast(Vector &source, Vector &result, idx_t count, C if (geom.GetType() != GeometryType::POINT) { throw ConversionException("Cannot cast non-point GEOMETRY to POINT_2D"); } - auto &point = geom.As(); - if (point.IsEmpty()) { + if (Point::IsEmpty(geom)) { // TODO: Maybe make this return NULL instead throw ConversionException("Cannot cast empty point GEOMETRY to POINT_2D"); } - auto vertex = point.Get(0); + auto vertex = Point::GetVertex(geom); return POINT_TYPE {vertex.x, vertex.y}; }); return true; @@ -72,9 +71,9 @@ static bool LineString2DToGeometryCast(Vector &source, Vector &result, idx_t cou for (idx_t i = 0; i < line.length; i++) { auto x = x_data[line.offset + i]; auto y = y_data[line.offset + i]; - geom.SetExact(i, VertexXY {x, y}); + LineString::SetVertex(geom, i, VertexXY {x, y}); } - return Geometry(geom).Serialize(result); + return Geometry::Serialize(geom, result); }); return true; } @@ -97,15 +96,15 @@ static bool GeometryToLineString2DCast(Vector &source, Vector &result, idx_t cou throw ConversionException("Cannot cast non-linestring GEOMETRY to LINESTRING_2D"); } - auto line = Geometry::Deserialize(arena, geom).As(); - auto line_size = line.Count(); + auto line = Geometry::Deserialize(arena, geom); + auto line_size = LineString::VertexCount(line); auto entry = list_entry_t(total_coords, line_size); total_coords += line_size; ListVector::Reserve(result, total_coords); for (idx_t i = 0; i < line_size; i++) { - auto vertex = line.Get(i); + auto vertex = LineString::GetVertex(line, i); x_data[entry.offset + i] = vertex.x; y_data[entry.offset + i] = vertex.y; } @@ -134,15 +133,15 @@ static bool Polygon2DToGeometryCast(Vector &source, Vector &result, idx_t count, for (idx_t i = 0; i < poly.length; i++) { auto ring = ring_entries[poly.offset + i]; - auto &ring_array = geom[i]; - ring_array.Resize(arena, ring.length); + auto &ring_array = Polygon::Part(geom, i); + LineString::Resize(ring_array, arena, ring.length); for (idx_t j = 0; j < ring.length; j++) { auto x = x_data[ring.offset + j]; auto y = y_data[ring.offset + j]; - ring_array.SetExact(j, VertexXY {x, y}); + LineString::SetVertex(ring_array, j, VertexXY {x, y}); } } - return Geometry(geom).Serialize(result); + return Geometry::Serialize(geom, result); }); return true; } @@ -163,14 +162,14 @@ static bool GeometryToPolygon2DCast(Vector &source, Vector &result, idx_t count, if (geom.GetType() != GeometryType::POLYGON) { throw ConversionException("Cannot cast non-polygon GEOMETRY to POLYGON_2D"); } - auto poly = Geometry::Deserialize(arena, geom).As(); - auto poly_size = poly.Count(); + auto poly = Geometry::Deserialize(arena, geom); + auto poly_size = Polygon::PartCount(poly); auto poly_entry = list_entry_t(total_rings, poly_size); ListVector::Reserve(result, total_rings + poly_size); for (idx_t ring_idx = 0; ring_idx < poly_size; ring_idx++) { - auto ring = poly[ring_idx]; + auto ring = Polygon::Part(poly, ring_idx); auto ring_size = ring.Count(); auto ring_entry = list_entry_t(total_coords, ring_size); @@ -185,7 +184,7 @@ static bool GeometryToPolygon2DCast(Vector &source, Vector &result, idx_t count, ring_entries[total_rings + ring_idx] = ring_entry; for (idx_t j = 0; j < ring_size; j++) { - auto vert = ring.Get(j); + auto vert = LineString::GetVertex(ring, j); x_data[ring_entry.offset + j] = vert.x; y_data[ring_entry.offset + j] = vert.y; } @@ -216,8 +215,8 @@ static bool Box2DToGeometryCast(Vector &source, Vector &result, idx_t count, Cas auto miny = box.b_val; auto maxx = box.c_val; auto maxy = box.d_val; - auto polygon = Polygon::FromBox(arena, minx, miny, maxx, maxy); - return Geometry(polygon).Serialize(result); + auto polygon = Polygon::CreateFromBox(arena, minx, miny, maxx, maxy); + return Geometry::Serialize(polygon, result); }); return true; } diff --git a/spatial/src/spatial/core/functions/cast/varchar_cast.cpp b/spatial/src/spatial/core/functions/cast/varchar_cast.cpp index 0297eb58..c11a6f7a 100644 --- a/spatial/src/spatial/core/functions/cast/varchar_cast.cpp +++ b/spatial/src/spatial/core/functions/cast/varchar_cast.cpp @@ -5,6 +5,7 @@ #include "spatial/core/functions/common.hpp" #include "spatial/core/geometry/geometry_processor.hpp" #include "spatial/core/geometry/wkt_reader.hpp" +#include "spatial/core/util/math.hpp" #include "duckdb/function/cast/cast_function_set.hpp" #include "duckdb/common/operator/cast_operators.hpp" #include "duckdb/common/vector_operations/generic_executor.hpp" @@ -29,7 +30,7 @@ void CoreVectorOperations::Point2DToVarchar(Vector &source, Vector &result, idx_ return StringVector::AddString(result, "POINT EMPTY"); } - return StringVector::AddString(result, StringUtil::Format("POINT (%s)", Utils::format_coord(x, y))); + return StringVector::AddString(result, StringUtil::Format("POINT (%s)", MathUtil::format_coord(x, y))); }); } @@ -52,7 +53,7 @@ void CoreVectorOperations::LineString2DToVarchar(Vector &source, Vector &result, string result_str = "LINESTRING ("; for (idx_t i = offset; i < offset + length; i++) { - result_str += Utils::format_coord(x_data[i], y_data[i]); + result_str += MathUtil::format_coord(x_data[i], y_data[i]); if (i < offset + length - 1) { result_str += ", "; } @@ -89,7 +90,7 @@ void CoreVectorOperations::Polygon2DToVarchar(Vector &source, Vector &result, id auto ring_length = ring_entry.length; result_str += "("; for (idx_t j = ring_offset; j < ring_offset + ring_length; j++) { - result_str += Utils::format_coord(x_data[j], y_data[j]); + result_str += MathUtil::format_coord(x_data[j], y_data[j]); if (j < ring_offset + ring_length - 1) { result_str += ", "; } @@ -112,8 +113,8 @@ void CoreVectorOperations::Box2DToVarchar(Vector &source, Vector &result, idx_t using VARCHAR_TYPE = PrimitiveType; GenericExecutor::ExecuteUnary(source, result, count, [&](BOX_TYPE &box) { return StringVector::AddString(result, - StringUtil::Format("BOX(%s, %s)", Utils::format_coord(box.a_val, box.b_val), - Utils::format_coord(box.c_val, box.d_val))); + StringUtil::Format("BOX(%s, %s)", MathUtil::format_coord(box.a_val, box.b_val), + MathUtil::format_coord(box.c_val, box.d_val))); }); } @@ -136,7 +137,7 @@ class GeometryTextProcessor final : GeometryProcessor { auto y = Load(dims[1] + i * strides[1]); auto z = Load(dims[2] + i * strides[2]); auto m = Load(dims[3] + i * strides[3]); - text += Utils::format_coord(x, y, z, m); + text += MathUtil::format_coord(x, y, z, m); if (i < count - 1) { text += ", "; } @@ -146,7 +147,7 @@ class GeometryTextProcessor final : GeometryProcessor { auto x = Load(dims[0] + i * strides[0]); auto y = Load(dims[1] + i * strides[1]); auto zm = Load(dims[2] + i * strides[2]); - text += Utils::format_coord(x, y, zm); + text += MathUtil::format_coord(x, y, zm); if (i < count - 1) { text += ", "; } @@ -156,7 +157,7 @@ class GeometryTextProcessor final : GeometryProcessor { auto x = Load(dims[0] + i * strides[0]); auto y = Load(dims[1] + i * strides[1]); auto m = Load(dims[3] + i * strides[3]); - text += Utils::format_coord(x, y, m); + text += MathUtil::format_coord(x, y, m); if (i < count - 1) { text += ", "; } @@ -165,7 +166,7 @@ class GeometryTextProcessor final : GeometryProcessor { for (uint32_t i = 0; i < count; i++) { auto x = Load(dims[0] + i * strides[0]); auto y = Load(dims[1] + i * strides[1]); - text += Utils::format_coord(x, y); + text += MathUtil::format_coord(x, y); if (i < count - 1) { text += ", "; @@ -323,7 +324,8 @@ static bool TextToGeometryCast(Vector &source, Vector &result, idx_t count, Cast UnaryExecutor::ExecuteWithNulls( source, result, count, [&](string_t &wkt, ValidityMask &mask, idx_t idx) { try { - return reader.Parse(wkt).Serialize(result); + auto geom = reader.Parse(wkt); + return Geometry::Serialize(geom, result); } catch (InvalidInputException &e) { if (success) { success = false; diff --git a/spatial/src/spatial/core/functions/cast/wkb_cast.cpp b/spatial/src/spatial/core/functions/cast/wkb_cast.cpp index 1da45e20..63e829d5 100644 --- a/spatial/src/spatial/core/functions/cast/wkb_cast.cpp +++ b/spatial/src/spatial/core/functions/cast/wkb_cast.cpp @@ -25,7 +25,8 @@ static bool WKBToGeometryCast(Vector &source, Vector &result, idx_t count, CastP UnaryExecutor::ExecuteWithNulls( source, result, count, [&](string_t input, ValidityMask &mask, idx_t idx) { try { - return reader.Deserialize(input).Serialize(result); + auto geom = reader.Deserialize(input); + return Geometry::Serialize(geom, result); } catch (SerializationException &e) { if (success) { success = false; diff --git a/spatial/src/spatial/core/functions/scalar/CMakeLists.txt b/spatial/src/spatial/core/functions/scalar/CMakeLists.txt index c8da1209..150ca0e9 100644 --- a/spatial/src/spatial/core/functions/scalar/CMakeLists.txt +++ b/spatial/src/spatial/core/functions/scalar/CMakeLists.txt @@ -25,6 +25,7 @@ set(EXTENSION_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/st_hilbert.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_intersects.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_intersects_extent.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/st_is_closed.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_length.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_makeenvelope.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_makeline.cpp diff --git a/spatial/src/spatial/core/functions/scalar/st_asgeojson.cpp b/spatial/src/spatial/core/functions/scalar/st_asgeojson.cpp index ee65ee81..90a17047 100644 --- a/spatial/src/spatial/core/functions/scalar/st_asgeojson.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_asgeojson.cpp @@ -58,7 +58,7 @@ class JSONAllocator { // GEOMETRY -> GEOJSON Fragment //------------------------------------------------------------------------------ -static void VerticesToGeoJSON(const SinglePartGeometry &vertices, yyjson_mut_doc *doc, yyjson_mut_val *arr) { +static void VerticesToGeoJSON(const Geometry &vertices, yyjson_mut_doc *doc, yyjson_mut_val *arr) { // TODO: If the vertexvector is empty, do we null, add an empty array or a pair of NaN? auto haz_z = vertices.GetProperties().HasZ(); auto has_m = vertices.GetProperties().HasM(); @@ -66,7 +66,7 @@ static void VerticesToGeoJSON(const SinglePartGeometry &vertices, yyjson_mut_doc if (haz_z && has_m) { for (uint32_t i = 0; i < vertices.Count(); i++) { auto coord = yyjson_mut_arr(doc); - auto vert = vertices.GetExact(i); + auto vert = SinglePartGeometry::GetVertex(vertices, i); yyjson_mut_arr_add_real(doc, coord, vert.x); yyjson_mut_arr_add_real(doc, coord, vert.y); yyjson_mut_arr_add_real(doc, coord, vert.z); @@ -75,7 +75,7 @@ static void VerticesToGeoJSON(const SinglePartGeometry &vertices, yyjson_mut_doc } else if (haz_z) { for (uint32_t i = 0; i < vertices.Count(); i++) { auto coord = yyjson_mut_arr(doc); - auto vert = vertices.GetExact(i); + auto vert = SinglePartGeometry::GetVertex(vertices, i); yyjson_mut_arr_add_real(doc, coord, vert.x); yyjson_mut_arr_add_real(doc, coord, vert.y); yyjson_mut_arr_add_real(doc, coord, vert.z); @@ -84,7 +84,7 @@ static void VerticesToGeoJSON(const SinglePartGeometry &vertices, yyjson_mut_doc } else if (has_m) { for (uint32_t i = 0; i < vertices.Count(); i++) { auto coord = yyjson_mut_arr(doc); - auto vert = vertices.GetExact(i); + auto vert = SinglePartGeometry::GetVertex(vertices, i); yyjson_mut_arr_add_real(doc, coord, vert.x); yyjson_mut_arr_add_real(doc, coord, vert.y); yyjson_mut_arr_append(arr, coord); @@ -92,7 +92,7 @@ static void VerticesToGeoJSON(const SinglePartGeometry &vertices, yyjson_mut_doc } else { for (uint32_t i = 0; i < vertices.Count(); i++) { auto coord = yyjson_mut_arr(doc); - auto vert = vertices.GetExact(i); + auto vert = SinglePartGeometry::GetVertex(vertices, i); yyjson_mut_arr_add_real(doc, coord, vert.x); yyjson_mut_arr_add_real(doc, coord, vert.y); yyjson_mut_arr_append(arr, coord); @@ -103,30 +103,30 @@ static void VerticesToGeoJSON(const SinglePartGeometry &vertices, yyjson_mut_doc struct ToGeoJSONFunctor { // Point - static void Apply(const Point &point, yyjson_mut_doc *doc, yyjson_mut_val *obj) { + static void Case(Geometry::Tags::Point, const Geometry &point, yyjson_mut_doc *doc, yyjson_mut_val *obj) { yyjson_mut_obj_add_str(doc, obj, "type", "Point"); auto coords = yyjson_mut_arr(doc); yyjson_mut_obj_add_val(doc, obj, "coordinates", coords); - if (!point.IsEmpty()) { + if (!Point::IsEmpty(point)) { auto has_z = point.GetProperties().HasZ(); auto has_m = point.GetProperties().HasM(); if (has_z && has_m) { - auto vert = point.GetExact(0); + auto vert = Point::GetVertex(point); yyjson_mut_arr_add_real(doc, coords, vert.x); yyjson_mut_arr_add_real(doc, coords, vert.y); yyjson_mut_arr_add_real(doc, coords, vert.z); } else if (has_z) { - auto vert = point.GetExact(0); + auto vert = Point::GetVertex(point); yyjson_mut_arr_add_real(doc, coords, vert.x); yyjson_mut_arr_add_real(doc, coords, vert.y); yyjson_mut_arr_add_real(doc, coords, vert.z); } else if (has_m) { - auto vert = point.GetExact(0); + auto vert = Point::GetVertex(point); yyjson_mut_arr_add_real(doc, coords, vert.x); yyjson_mut_arr_add_real(doc, coords, vert.y); } else { - auto vert = point.GetExact(0); + auto vert = Point::GetVertex(point); yyjson_mut_arr_add_real(doc, coords, vert.x); yyjson_mut_arr_add_real(doc, coords, vert.y); } @@ -134,7 +134,7 @@ struct ToGeoJSONFunctor { } // LineString - static void Apply(const LineString &line, yyjson_mut_doc *doc, yyjson_mut_val *obj) { + static void Case(Geometry::Tags::LineString, const Geometry &line, yyjson_mut_doc *doc, yyjson_mut_val *obj) { yyjson_mut_obj_add_str(doc, obj, "type", "LineString"); auto coords = yyjson_mut_arr(doc); @@ -143,12 +143,13 @@ struct ToGeoJSONFunctor { } // Polygon - static void Apply(const Polygon &poly, yyjson_mut_doc *doc, yyjson_mut_val *obj) { + static void Case(Geometry::Tags::Polygon, const Geometry &poly, yyjson_mut_doc *doc, yyjson_mut_val *obj) { yyjson_mut_obj_add_str(doc, obj, "type", "Polygon"); auto coords = yyjson_mut_arr(doc); yyjson_mut_obj_add_val(doc, obj, "coordinates", coords); - for (const auto &ring : poly) { + for (uint32_t i = 0; i < Polygon::PartCount(poly); i++) { + auto &ring = Polygon::Part(poly, i); auto ring_coords = yyjson_mut_arr(doc); VerticesToGeoJSON(ring, doc, ring_coords); yyjson_mut_arr_append(coords, ring_coords); @@ -156,24 +157,26 @@ struct ToGeoJSONFunctor { } // MultiPoint - static void Apply(const MultiPoint &mpoint, yyjson_mut_doc *doc, yyjson_mut_val *obj) { + static void Case(Geometry::Tags::MultiPoint, const Geometry &mpoint, yyjson_mut_doc *doc, yyjson_mut_val *obj) { yyjson_mut_obj_add_str(doc, obj, "type", "MultiPoint"); auto coords = yyjson_mut_arr(doc); yyjson_mut_obj_add_val(doc, obj, "coordinates", coords); - for (const auto &point : mpoint) { + for (uint32_t i = 0; i < MultiPoint::PartCount(mpoint); i++) { + auto &point = MultiPoint::Part(mpoint, i); VerticesToGeoJSON(point, doc, coords); } } // MultiLineString - static void Apply(const MultiLineString &mline, yyjson_mut_doc *doc, yyjson_mut_val *obj) { + static void Case(Geometry::Tags::MultiLineString, const Geometry &mline, yyjson_mut_doc *doc, yyjson_mut_val *obj) { yyjson_mut_obj_add_str(doc, obj, "type", "MultiLineString"); auto coords = yyjson_mut_arr(doc); yyjson_mut_obj_add_val(doc, obj, "coordinates", coords); - for (const auto &line : mline) { + for (uint32_t i = 0; i < MultiLineString::PartCount(mline); i++) { + auto &line = MultiLineString::Part(mline, i); auto line_coords = yyjson_mut_arr(doc); VerticesToGeoJSON(line, doc, line_coords); yyjson_mut_arr_append(coords, line_coords); @@ -181,15 +184,17 @@ struct ToGeoJSONFunctor { } // MultiPolygon - static void Apply(const MultiPolygon &mpoly, yyjson_mut_doc *doc, yyjson_mut_val *obj) { + static void Case(Geometry::Tags::MultiPolygon, const Geometry &mpoly, yyjson_mut_doc *doc, yyjson_mut_val *obj) { yyjson_mut_obj_add_str(doc, obj, "type", "MultiPolygon"); auto coords = yyjson_mut_arr(doc); yyjson_mut_obj_add_val(doc, obj, "coordinates", coords); - for (const auto &poly : mpoly) { + for (uint32_t i = 0; i < MultiPolygon::PartCount(mpoly); i++) { + auto &poly = MultiPolygon::Part(mpoly, i); auto poly_coords = yyjson_mut_arr(doc); - for (const auto &ring : poly) { + for (uint32_t j = 0; j < Polygon::PartCount(poly); j++) { + auto &ring = Polygon::Part(poly, j); auto ring_coords = yyjson_mut_arr(doc); VerticesToGeoJSON(ring, doc, ring_coords); yyjson_mut_arr_append(poly_coords, ring_coords); @@ -199,14 +204,16 @@ struct ToGeoJSONFunctor { } // GeometryCollection - static void Apply(const GeometryCollection &collection, yyjson_mut_doc *doc, yyjson_mut_val *obj) { + static void Case(Geometry::Tags::GeometryCollection, const Geometry &collection, yyjson_mut_doc *doc, + yyjson_mut_val *obj) { yyjson_mut_obj_add_str(doc, obj, "type", "GeometryCollection"); auto arr = yyjson_mut_arr(doc); yyjson_mut_obj_add_val(doc, obj, "geometries", arr); - for (auto &geom : collection) { + for (uint32_t i = 0; i < GeometryCollection::PartCount(collection); i++) { + auto &geom = GeometryCollection::Part(collection, i); auto geom_obj = yyjson_mut_obj(doc); - geom.Visit(doc, geom_obj); + Geometry::Match(geom, doc, geom_obj); yyjson_mut_arr_append(arr, geom_obj); } } @@ -222,13 +229,13 @@ static void GeometryToGeoJSONFragmentFunction(DataChunk &args, ExpressionState & JSONAllocator json_allocator(lstate.arena); UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { - auto geometry = Geometry::Deserialize(lstate.arena, input); + auto geom = Geometry::Deserialize(lstate.arena, input); auto doc = yyjson_mut_doc_new(json_allocator.GetYYJSONAllocator()); auto obj = yyjson_mut_obj(doc); yyjson_mut_doc_set_root(doc, obj); - geometry.Visit(doc, obj); + Geometry::Match(geom, doc, obj); size_t json_size = 0; // TODO: YYJSON_WRITE_PRETTY @@ -242,11 +249,11 @@ static void GeometryToGeoJSONFragmentFunction(DataChunk &args, ExpressionState & // GEOJSON Fragment -> GEOMETRY //------------------------------------------------------------------------------ -static Point PointFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, bool &has_z) { +static Geometry PointFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, bool &has_z) { auto len = yyjson_arr_size(coord_array); if (len == 0) { // empty point - return Point::Empty(has_z, false); + return Point::CreateEmpty(has_z, false); } if (len < 2) { throw InvalidInputException("GeoJSON input coordinates field is not an array of at least length 2: %s", @@ -273,18 +280,17 @@ static Point PointFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, co raw.GetString()); } auto z = yyjson_get_num(z_val); - return Point::FromVertex(arena, VertexXYZ {x, y, z}); + return Point::CreateFromVertex(arena, VertexXYZ {x, y, z}); } else { - return Point::FromVertex(arena, VertexXY {x, y}); + return Point::CreateFromVertex(arena, VertexXY {x, y}); } } -static LineString VerticesFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, - bool &has_z) { +static Geometry VerticesFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, bool &has_z) { auto len = yyjson_arr_size(coord_array); if (len == 0) { // Empty - return LineString::Empty(false, false); + return LineString::CreateEmpty(false, false); } else { // Sniff the coordinates to see if we have Z bool has_any_z = false; @@ -335,25 +341,25 @@ static LineString VerticesFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &a z = yyjson_get_num(z_val); } if (has_any_z) { - vertices.SetExact(idx, VertexXYZ {x, y, z}); + LineString::SetVertex(vertices, idx, {x, y, z}); } else { - vertices.SetExact(idx, VertexXY {x, y}); + LineString::SetVertex(vertices, idx, {x, y}); } } return vertices; } } -static LineString LineStringFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, - bool &has_z) { +static Geometry LineStringFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, + bool &has_z) { return VerticesFromGeoJSON(coord_array, arena, raw, has_z); } -static Polygon PolygonFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, bool &has_z) { +static Geometry PolygonFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, bool &has_z) { auto num_rings = yyjson_arr_size(coord_array); if (num_rings == 0) { // Empty - return Polygon::Empty(has_z, false); + return Polygon::CreateEmpty(has_z, false); } else { // Polygon auto polygon = Polygon::Create(arena, num_rings, has_z, false); @@ -364,19 +370,19 @@ static Polygon PolygonFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena throw InvalidInputException("GeoJSON input coordinates field is not an array of arrays: %s", raw.GetString()); } - polygon[idx] = VerticesFromGeoJSON(ring_val, arena, raw, has_z); + Polygon::Part(polygon, idx) = VerticesFromGeoJSON(ring_val, arena, raw, has_z); } return polygon; } } -static MultiPoint MultiPointFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, - bool &has_z) { +static Geometry MultiPointFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, + bool &has_z) { auto num_points = yyjson_arr_size(coord_array); if (num_points == 0) { // Empty - return MultiPoint::Empty(has_z, false); + return MultiPoint::CreateEmpty(has_z, false); } else { // MultiPoint auto multi_point = MultiPoint::Create(arena, num_points, has_z, false); @@ -391,18 +397,18 @@ static MultiPoint MultiPointFromGeoJSON(yyjson_val *coord_array, ArenaAllocator throw InvalidInputException( "GeoJSON input coordinates field is not an array of arrays of length >= 2: %s", raw.GetString()); } - multi_point[idx] = PointFromGeoJSON(point_val, arena, raw, has_z); + MultiPoint::Part(multi_point, idx) = PointFromGeoJSON(point_val, arena, raw, has_z); } return multi_point; } } -static MultiLineString MultiLineStringFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, - bool &has_z) { +static Geometry MultiLineStringFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, + bool &has_z) { auto num_linestrings = yyjson_arr_size(coord_array); if (num_linestrings == 0) { // Empty - return MultiLineString::Empty(has_z, false); + return MultiLineString::CreateEmpty(has_z, false); } else { // MultiLineString auto multi_linestring = MultiLineString::Create(arena, num_linestrings, has_z, false); @@ -413,19 +419,19 @@ static MultiLineString MultiLineStringFromGeoJSON(yyjson_val *coord_array, Arena throw InvalidInputException("GeoJSON input coordinates field is not an array of arrays: %s", raw.GetString()); } - multi_linestring[idx] = LineStringFromGeoJSON(linestring_val, arena, raw, has_z); + MultiLineString::Part(multi_linestring, idx) = LineStringFromGeoJSON(linestring_val, arena, raw, has_z); } return multi_linestring; } } -static MultiPolygon MultiPolygonFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, - bool &has_z) { +static Geometry MultiPolygonFromGeoJSON(yyjson_val *coord_array, ArenaAllocator &arena, const string_t &raw, + bool &has_z) { auto num_polygons = yyjson_arr_size(coord_array); if (num_polygons == 0) { // Empty - return MultiPolygon::Empty(has_z, false); + return MultiPolygon::CreateEmpty(has_z, false); } else { // MultiPolygon auto multi_polygon = MultiPolygon::Create(arena, num_polygons, has_z, false); @@ -436,7 +442,7 @@ static MultiPolygon MultiPolygonFromGeoJSON(yyjson_val *coord_array, ArenaAlloca throw InvalidInputException("GeoJSON input coordinates field is not an array of arrays: %s", raw.GetString()); } - multi_polygon[idx] = PolygonFromGeoJSON(polygon_val, arena, raw, has_z); + MultiPolygon::Part(multi_polygon, idx) = PolygonFromGeoJSON(polygon_val, arena, raw, has_z); } return multi_polygon; @@ -445,8 +451,8 @@ static MultiPolygon MultiPolygonFromGeoJSON(yyjson_val *coord_array, ArenaAlloca static Geometry FromGeoJSON(yyjson_val *root, ArenaAllocator &arena, const string_t &raw, bool &has_z); -static GeometryCollection GeometryCollectionFromGeoJSON(yyjson_val *root, ArenaAllocator &arena, const string_t &raw, - bool &has_z) { +static Geometry GeometryCollectionFromGeoJSON(yyjson_val *root, ArenaAllocator &arena, const string_t &raw, + bool &has_z) { auto geometries_val = yyjson_obj_get(root, "geometries"); if (!geometries_val) { throw InvalidInputException("GeoJSON input does not have a geometries field: %s", raw.GetString()); @@ -457,14 +463,14 @@ static GeometryCollection GeometryCollectionFromGeoJSON(yyjson_val *root, ArenaA auto num_geometries = yyjson_arr_size(geometries_val); if (num_geometries == 0) { // Empty - return GeometryCollection::Empty(has_z, false); + return GeometryCollection::CreateEmpty(has_z, false); } else { // GeometryCollection auto geometry_collection = GeometryCollection::Create(arena, num_geometries, has_z, false); size_t idx, max; yyjson_val *geometry_val; yyjson_arr_foreach(geometries_val, idx, max, geometry_val) { - geometry_collection[idx] = FromGeoJSON(geometry_val, arena, raw, has_z); + GeometryCollection::Part(geometry_collection, idx) = FromGeoJSON(geometry_val, arena, raw, has_z); } return geometry_collection; @@ -540,7 +546,7 @@ static void GeoJSONFragmentToGeometryFunction(DataChunk &args, ExpressionState & // Ensure the geometries has consistent Z values geom.SetVertexType(lstate.arena, has_z, false); } - return geom.Serialize(result); + return Geometry::Serialize(geom, result); } }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_collect.cpp b/spatial/src/spatial/core/functions/scalar/st_collect.cpp index f01d30a8..1b1e0deb 100644 --- a/spatial/src/spatial/core/functions/scalar/st_collect.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_collect.cpp @@ -27,6 +27,10 @@ static void CollectFunction(DataChunk &args, ExpressionState &state, Vector &res // First figure out if we have Z or M bool has_z = false; bool has_m = false; + bool all_points = true; + bool all_lines = true; + bool all_polygons = true; + for (idx_t i = offset; i < offset + length; i++) { auto mapped_idx = format.sel->get_index(i); if (format.validity.RowIsValid(mapped_idx)) { @@ -37,7 +41,6 @@ static void CollectFunction(DataChunk &args, ExpressionState &state, Vector &res } } - // TODO: Peek the types first vector geometries; for (idx_t i = offset; i < offset + length; i++) { auto mapped_idx = format.sel->get_index(i); @@ -45,62 +48,31 @@ static void CollectFunction(DataChunk &args, ExpressionState &state, Vector &res auto geometry_blob = ((geometry_t *)format.data)[mapped_idx]; auto geometry = Geometry::Deserialize(arena, geometry_blob); // Dont add empty geometries - if (!geometry.IsEmpty()) { - geometries.push_back(geometry); + if (!Geometry::IsEmpty(geometry)) { + all_points = all_points && geometry_blob.GetType() == GeometryType::POINT; + all_lines = all_lines && geometry_blob.GetType() == GeometryType::LINESTRING; + all_polygons = all_polygons && geometry_blob.GetType() == GeometryType::POLYGON; + + // Ensure all geometries have the same Z and M + geometry.SetVertexType(arena, has_z, has_m); + geometries.push_back(std::move(geometry)); } } } if (geometries.empty()) { - return Geometry(GeometryCollection::Empty(has_z, has_m)).Serialize(result); - } - - bool all_points = true; - bool all_lines = true; - bool all_polygons = true; - - for (auto &geometry : geometries) { - if (geometry.GetType() != GeometryType::POINT) { - all_points = false; - } - if (geometry.GetType() != GeometryType::LINESTRING) { - all_lines = false; - } - if (geometry.GetType() != GeometryType::POLYGON) { - all_polygons = false; - } + return Geometry::Serialize(GeometryCollection::CreateEmpty(has_z, has_m), result); } // TODO: Dont upcast the children, just append them. - if (all_points) { - auto collection = MultiPoint::Create(arena, geometries.size(), has_z, has_m); - for (idx_t i = 0; i < geometries.size(); i++) { - geometries[i].SetVertexType(arena, has_z, has_m); - collection[i] = geometries[i].As(); - } - return Geometry(collection).Serialize(result); + return Geometry::Serialize(MultiPoint::Create(arena, geometries, has_z, has_m), result); } else if (all_lines) { - auto collection = MultiLineString::Create(arena, geometries.size(), has_z, has_m); - for (idx_t i = 0; i < geometries.size(); i++) { - geometries[i].SetVertexType(arena, has_z, has_m); - collection[i] = geometries[i].As(); - } - return Geometry(collection).Serialize(result); + return Geometry::Serialize(MultiLineString::Create(arena, geometries, has_z, has_m), result); } else if (all_polygons) { - auto collection = MultiPolygon::Create(arena, geometries.size(), has_z, has_m); - for (idx_t i = 0; i < geometries.size(); i++) { - geometries[i].SetVertexType(arena, has_z, has_m); - collection[i] = geometries[i].As(); - } - return Geometry(collection).Serialize(result); + return Geometry::Serialize(MultiPolygon::Create(arena, geometries, has_z, has_m), result); } else { - auto collection = GeometryCollection::Create(arena, geometries.size(), has_z, has_m); - for (idx_t i = 0; i < geometries.size(); i++) { - geometries[i].SetVertexType(arena, has_z, has_m); - collection[i] = geometries[i]; - } - return Geometry(collection).Serialize(result); + return Geometry::Serialize(GeometryCollection::Create(arena, geometries, has_z, has_m), result); } }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_collectionextract.cpp b/spatial/src/spatial/core/functions/scalar/st_collectionextract.cpp index fa93bc2f..9fdee12d 100644 --- a/spatial/src/spatial/core/functions/scalar/st_collectionextract.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_collectionextract.cpp @@ -15,81 +15,6 @@ namespace core { //------------------------------------------------------------------------------ // GEOMETRY //------------------------------------------------------------------------------ -static void CollectPoints(Geometry &geom, vector &points) { - switch (geom.GetType()) { - case GeometryType::POINT: { - points.push_back(geom.As()); - break; - } - case GeometryType::MULTIPOINT: { - auto &multipoint = geom.As(); - for (auto &point : multipoint) { - points.push_back(point); - } - break; - } - case GeometryType::GEOMETRYCOLLECTION: { - auto &col = geom.As(); - for (auto &g : col) { - CollectPoints(g, points); - } - } - default: { - break; - } - } -} - -static void CollectLines(Geometry &geom, vector &lines) { - switch (geom.GetType()) { - case GeometryType::LINESTRING: { - lines.push_back(geom.As()); - break; - } - case GeometryType::MULTILINESTRING: { - auto &multilines = geom.As(); - for (auto &line : multilines) { - lines.push_back(line); - } - break; - } - case GeometryType::GEOMETRYCOLLECTION: { - auto &col = geom.As(); - for (auto &g : col) { - CollectLines(g, lines); - } - } - default: { - break; - } - } -} - -static void CollectPolygons(Geometry &geom, vector &polys) { - switch (geom.GetType()) { - case GeometryType::POLYGON: { - polys.push_back(geom.As()); - break; - } - case GeometryType::MULTIPOLYGON: { - auto &multipolys = geom.As(); - for (auto &poly : multipolys) { - polys.push_back(poly); - } - break; - } - case GeometryType::GEOMETRYCOLLECTION: { - auto &col = geom.As(); - for (auto &g : col) { - CollectPolygons(g, polys); - } - } - default: { - break; - } - } -} - // Collection extract with a specific dimension static void CollectionExtractTypeFunction(DataChunk &args, ExpressionState &state, Vector &result) { auto &lstate = GeometryFunctionLocalState::ResetAndGet(state); @@ -98,86 +23,82 @@ static void CollectionExtractTypeFunction(DataChunk &args, ExpressionState &stat auto &input = args.data[0]; auto &dim = args.data[1]; - BinaryExecutor::Execute( - input, dim, result, count, [&](geometry_t input, int32_t requested_type) { - auto props = input.GetProperties(); - auto geometry = Geometry::Deserialize(arena, input); - switch (requested_type) { - case 1: { - if (geometry.GetType() == GeometryType::MULTIPOINT || geometry.GetType() == GeometryType::POINT) { - return input; - } else if (geometry.IsCollection()) { - // if it is a geometry collection, we need to collect all points - if (geometry.GetType() == GeometryType::GEOMETRYCOLLECTION && !geometry.IsEmpty()) { - vector points; - CollectPoints(geometry, points); - uint32_t size = points.size(); + // Items vector + vector items; - auto mpoint = MultiPoint::Create(arena, size, props.HasZ(), props.HasM()); - for (uint32_t i = 0; i < size; i++) { - mpoint[i] = points[i]; - } - return Geometry(mpoint).Serialize(result); - } - // otherwise, we return an empty multipoint - return Geometry(MultiPoint::Empty(props.HasZ(), props.HasM())).Serialize(result); - } else { - // otherwise if its not a collection, we return an empty point - return Geometry(Point::Empty(props.HasZ(), props.HasM())).Serialize(result); - } - } - case 2: { - if (geometry.GetType() == GeometryType::MULTILINESTRING || - geometry.GetType() == GeometryType::LINESTRING) { - return input; - } else if (geometry.IsCollection()) { - // if it is a geometry collection, we need to collect all lines - if (geometry.GetType() == GeometryType::GEOMETRYCOLLECTION && !geometry.IsEmpty()) { - vector lines; - CollectLines(geometry, lines); - uint32_t size = lines.size(); + BinaryExecutor::Execute(input, dim, result, count, [&](geometry_t input, int32_t requested_type) { + // Reset the items vector + items.clear(); - auto mline = MultiLineString::Create(arena, size, props.HasZ(), props.HasM()); - for (uint32_t i = 0; i < size; i++) { - mline[i] = lines[i]; - } - return Geometry(mline).Serialize(result); - } - // otherwise, we return an empty multilinestring - return Geometry(MultiLineString::Empty(props.HasZ(), props.HasM())).Serialize(result); - } else { - // otherwise if its not a collection, we return an empty linestring - return Geometry(LineString::Empty(props.HasZ(), props.HasM())).Serialize(result); - } - } - case 3: { - if (geometry.GetType() == GeometryType::MULTIPOLYGON || geometry.GetType() == GeometryType::POLYGON) { - return input; - } else if (geometry.IsCollection()) { - // if it is a geometry collection, we need to collect all polygons - if (geometry.GetType() == GeometryType::GEOMETRYCOLLECTION && !geometry.IsEmpty()) { - vector polys; - CollectPolygons(geometry, polys); - uint32_t size = polys.size(); + // Deserialize the input geometry + auto props = input.GetProperties(); + auto geometry = Geometry::Deserialize(arena, input); - auto mpoly = MultiPolygon::Create(arena, size, props.HasZ(), props.HasM()); - for (uint32_t i = 0; i < size; i++) { - mpoly[i] = polys[i]; - } - return Geometry(mpoly).Serialize(result); - } - // otherwise, we return an empty multipolygon - return Geometry(MultiPolygon::Empty(props.HasZ(), props.HasM())).Serialize(result); - } else { - // otherwise if its not a collection, we return an empty polygon - return Geometry(Polygon::Empty(props.HasZ(), props.HasM())).Serialize(result); - } - } - default: - throw InvalidInputException("Invalid requested type parameter for collection extract, must be 1 " - "(POINT), 2 (LINESTRING) or 3 (POLYGON)"); - } - }); + // Switch on the requested type + switch (requested_type) { + case 1: { + if (geometry.GetType() == GeometryType::MULTIPOINT || geometry.GetType() == GeometryType::POINT) { + return input; + } else if (geometry.IsCollection()) { + // if it is a geometry collection, we need to collect all points + if (geometry.GetType() == GeometryType::GEOMETRYCOLLECTION && !GeometryCollection::IsEmpty(geometry)) { + Geometry::ExtractPoints(geometry, [&](const Geometry &point) { items.push_back(point); }); + auto mpoint = MultiPoint::Create(arena, items, props.HasZ(), props.HasM()); + return Geometry::Serialize(mpoint, result); + } + // otherwise, we return an empty multipoint + auto empty = MultiPoint::CreateEmpty(props.HasZ(), props.HasM()); + return Geometry::Serialize(empty, result); + } else { + // otherwise if its not a collection, we return an empty point + auto empty = Point::CreateEmpty(props.HasZ(), props.HasM()); + return Geometry::Serialize(empty, result); + } + } + case 2: { + if (geometry.GetType() == GeometryType::MULTILINESTRING || geometry.GetType() == GeometryType::LINESTRING) { + return input; + } else if (geometry.IsCollection()) { + // if it is a geometry collection, we need to collect all lines + if (geometry.GetType() == GeometryType::GEOMETRYCOLLECTION && !GeometryCollection::IsEmpty(geometry)) { + Geometry::ExtractLines(geometry, [&](const Geometry &line) { items.push_back(line); }); + auto mline = MultiLineString::Create(arena, items, props.HasZ(), props.HasM()); + return Geometry::Serialize(mline, result); + } + // otherwise, we return an empty multilinestring + auto empty = MultiLineString::CreateEmpty(props.HasZ(), props.HasM()); + return Geometry::Serialize(empty, result); + } else { + // otherwise if its not a collection, we return an empty linestring + auto empty = LineString::CreateEmpty(props.HasZ(), props.HasM()); + return Geometry::Serialize(empty, result); + } + } + case 3: { + if (geometry.GetType() == GeometryType::MULTIPOLYGON || geometry.GetType() == GeometryType::POLYGON) { + return input; + } else if (geometry.IsCollection()) { + // if it is a geometry collection, we need to collect all polygons + if (geometry.GetType() == GeometryType::GEOMETRYCOLLECTION && !GeometryCollection::IsEmpty(geometry)) { + Geometry::ExtractPolygons(geometry, [&](const Geometry &poly) { items.push_back(poly); }); + auto mpoly = MultiPolygon::Create(arena, items, props.HasZ(), props.HasM()); + return Geometry::Serialize(mpoly, result); + } + // otherwise, we return an empty multipolygon + auto empty = MultiPolygon::CreateEmpty(props.HasZ(), props.HasM()); + return Geometry::Serialize(empty, result); + } else { + // otherwise if its not a collection, we return an empty polygon + auto empty = Polygon::CreateEmpty(props.HasZ(), props.HasM()); + return Geometry::Serialize(empty, result); + } + } + default: + throw InvalidInputException("Invalid requested type parameter for collection extract, must be 1 " + "(POINT), 2 (LINESTRING) or 3 (POLYGON)"); + } + }); } // Note: We're being smart here and reusing the memory from the input geometry @@ -188,52 +109,40 @@ static void CollectionExtractAutoFunction(DataChunk &args, ExpressionState &stat auto count = args.size(); auto &input = args.data[0]; + vector items; + UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { if (input.GetType() == GeometryType::GEOMETRYCOLLECTION) { - auto props = input.GetProperties(); - auto geometry = Geometry::Deserialize(arena, input); + // Reset the items vector + items.clear(); - auto &collection = geometry.As(); - if (collection.IsEmpty()) { + auto props = input.GetProperties(); + auto collection = Geometry::Deserialize(arena, input); + if (GeometryCollection::IsEmpty(collection)) { return input; } // Find the highest dimension of the geometries in the collection // Empty geometries are ignored - auto dim = geometry.GetDimension(true); + auto dim = Geometry::GetDimension(collection, true); switch (dim) { // Point case case 0: { - vector points; - CollectPoints(geometry, points); - uint32_t size = points.size(); - auto mpoint = MultiPoint::Create(arena, size, props.HasZ(), props.HasM()); - for (uint32_t i = 0; i < size; i++) { - mpoint[i] = points[i]; - } - return Geometry(mpoint).Serialize(result); + Geometry::ExtractPoints(collection, [&](const Geometry &point) { items.push_back(point); }); + auto mpoint = MultiPoint::Create(arena, items, props.HasZ(), props.HasM()); + return Geometry::Serialize(mpoint, result); } // LineString case case 1: { - vector lines; - CollectLines(geometry, lines); - uint32_t size = lines.size(); - auto mline = MultiLineString::Create(arena, size, props.HasZ(), props.HasM()); - for (uint32_t i = 0; i < size; i++) { - mline[i] = lines[i]; - } - return Geometry(mline).Serialize(result); + Geometry::ExtractLines(collection, [&](const Geometry &line) { items.push_back(line); }); + auto mline = MultiLineString::Create(arena, items, props.HasZ(), props.HasM()); + return Geometry::Serialize(mline, result); } // Polygon case case 2: { - vector polys; - CollectPolygons(geometry, polys); - uint32_t size = polys.size(); - auto mpoly = MultiPolygon::Create(arena, size, props.HasZ(), props.HasM()); - for (uint32_t i = 0; i < size; i++) { - mpoly[i] = polys[i]; - } - return Geometry(mpoly).Serialize(result); + Geometry::ExtractPolygons(collection, [&](const Geometry &poly) { items.push_back(poly); }); + auto mpoly = MultiPolygon::Create(arena, items, props.HasZ(), props.HasM()); + return Geometry::Serialize(mpoly, result); } default: { throw InternalException("Invalid dimension in collection extract"); diff --git a/spatial/src/spatial/core/functions/scalar/st_dimension.cpp b/spatial/src/spatial/core/functions/scalar/st_dimension.cpp index d36f9ebb..1edcae24 100644 --- a/spatial/src/spatial/core/functions/scalar/st_dimension.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_dimension.cpp @@ -23,7 +23,7 @@ static void DimensionFunction(DataChunk &args, ExpressionState &state, Vector &r UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { auto geometry = Geometry::Deserialize(lstate.arena, input); - return geometry.GetDimension(false); + return Geometry::GetDimension(geometry, false); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_distance_sphere.cpp b/spatial/src/spatial/core/functions/scalar/st_distance_sphere.cpp index 61ecbe79..21f08fd6 100644 --- a/spatial/src/spatial/core/functions/scalar/st_distance_sphere.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_distance_sphere.cpp @@ -68,11 +68,11 @@ static void GeometryHaversineFunction(DataChunk &args, ExpressionState &state, V } auto left_geom = Geometry::Deserialize(lstate.arena, left); auto right_geom = Geometry::Deserialize(lstate.arena, right); - if (left_geom.IsEmpty() || right_geom.IsEmpty()) { + if (Point::IsEmpty(left_geom) || Point::IsEmpty(right_geom)) { throw InvalidInputException("ST_Distance_Sphere does not support EMPTY geometries"); } - auto v1 = left_geom.As().Get(0); - auto v2 = right_geom.As().Get(0); + auto v1 = Point::GetVertex(left_geom); + auto v2 = Point::GetVertex(right_geom); return HaversineFunction(v1.x, v1.y, v2.x, v2.y); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_dump.cpp b/spatial/src/spatial/core/functions/scalar/st_dump.cpp index 2cf5c8bb..bbe45614 100644 --- a/spatial/src/spatial/core/functions/scalar/st_dump.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_dump.cpp @@ -46,34 +46,11 @@ static void DumpFunction(DataChunk &args, ExpressionState &state, Vector &result auto current_path = std::get<1>(current); stack.pop_back(); - - if (current_geom.GetType() == GeometryType::MULTIPOINT) { - auto mpoint = current_geom.As(); - for (int32_t i = 0; i < mpoint.Count(); i++) { + if (current_geom.IsCollection()) { + for (int32_t i = 0; i < CollectionGeometry::PartCount(current_geom); i++) { auto path = current_path; path.push_back(i + 1); // path is 1-indexed - stack.emplace_back(mpoint[i], path); - } - } else if (current_geom.GetType() == GeometryType::MULTILINESTRING) { - auto mline = current_geom.As(); - for (int32_t i = 0; i < mline.Count(); i++) { - auto path = current_path; - path.push_back(i + 1); - stack.emplace_back(mline[i], path); - } - } else if (current_geom.GetType() == GeometryType::MULTIPOLYGON) { - auto mpoly = current_geom.As(); - for (int32_t i = 0; i < mpoly.Count(); i++) { - auto path = current_path; - path.push_back(i + 1); - stack.emplace_back(mpoly[i], path); - } - } else if (current_geom.GetType() == GeometryType::GEOMETRYCOLLECTION) { - auto collection = current_geom.As(); - for (int32_t i = 0; i < collection.Count(); i++) { - auto path = current_path; - path.push_back(i + 1); - stack.emplace_back(collection[i], path); + stack.emplace_back(CollectionGeometry::Part(current_geom, i), path); } } else { items.push_back(current); @@ -107,7 +84,7 @@ static void DumpFunction(DataChunk &args, ExpressionState &state, Vector &result for (idx_t i = 0; i < geom_length; i++) { // Write the geometry auto &item_blob = std::get<0>(items[i]); - geom_data[geom_offset + i] = item_blob.Serialize(*result_geom_vec); + geom_data[geom_offset + i] = Geometry::Serialize(item_blob, *result_geom_vec); // Now write the paths auto &path = std::get<1>(items[i]); diff --git a/spatial/src/spatial/core/functions/scalar/st_endpoint.cpp b/spatial/src/spatial/core/functions/scalar/st_endpoint.cpp index c31102dd..ce218afb 100644 --- a/spatial/src/spatial/core/functions/scalar/st_endpoint.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_endpoint.cpp @@ -71,16 +71,16 @@ static void GeometryEndPointFunction(DataChunk &args, ExpressionState &state, Ve return geometry_t {}; } - auto line = Geometry::Deserialize(lstate.arena, input).As(); - auto point_count = line.Count(); + auto line = Geometry::Deserialize(lstate.arena, input); + auto point_count = LineString::VertexCount(line); if (point_count == 0) { mask.SetInvalid(row_idx); return geometry_t {}; } - auto point = Point::FromReference(line, point_count - 1); - return Geometry(point).Serialize(result); + auto point = LineString::GetPointAsReference(line, point_count - 1); + return Geometry::Serialize(point, result); }); } //------------------------------------------------------------------------------ diff --git a/spatial/src/spatial/core/functions/scalar/st_exteriorring.cpp b/spatial/src/spatial/core/functions/scalar/st_exteriorring.cpp index 1b89ddb6..31445045 100644 --- a/spatial/src/spatial/core/functions/scalar/st_exteriorring.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_exteriorring.cpp @@ -102,15 +102,13 @@ static void GeometryExteriorRingFunction(DataChunk &args, ExpressionState &state validity.SetInvalid(idx); return geometry_t {}; } - - auto polygon = Geometry::Deserialize(arena, input).As(); - if (polygon.IsEmpty()) { - return Geometry(LineString::Empty(polygon.GetProperties().HasZ(), polygon.GetProperties().HasM())) - .Serialize(result); + auto polygon = Geometry::Deserialize(arena, input); + if (Polygon::IsEmpty(polygon)) { + auto empty = LineString::CreateEmpty(polygon.GetProperties().HasZ(), polygon.GetProperties().HasM()); + return Geometry::Serialize(empty, result); } - - auto &shell = polygon[0]; - return Geometry(shell).Serialize(result); + auto &shell = Polygon::ExteriorRing(polygon); + return Geometry::Serialize(shell, result); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_flipcoordinates.cpp b/spatial/src/spatial/core/functions/scalar/st_flipcoordinates.cpp index 00a6f526..af6839a3 100644 --- a/spatial/src/spatial/core/functions/scalar/st_flipcoordinates.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_flipcoordinates.cpp @@ -161,26 +161,27 @@ static void GeometryFlipCoordinatesFunction(DataChunk &args, ExpressionState &st auto input = args.data[0]; auto count = args.size(); - struct FlipOp { - static void Apply(SinglePartGeometry &geom, ArenaAllocator &arena) { - geom.MakeMutable(arena); - for (idx_t i = 0; i < geom.Count(); i++) { - auto vertex = geom.Get(i); + struct op { + static void Case(Geometry::Tags::SinglePartGeometry, Geometry &geom, ArenaAllocator &arena) { + SinglePartGeometry::MakeMutable(geom, arena); + for (idx_t i = 0; i < SinglePartGeometry::VertexCount(geom); i++) { + auto vertex = SinglePartGeometry::GetVertex(geom, i); std::swap(vertex.x, vertex.y); - geom.Set(i, vertex); + SinglePartGeometry::SetVertex(geom, i, vertex); } } - static void Apply(MultiPartGeometry &geom, ArenaAllocator &arena) { - for (auto &part : geom) { - part.Visit(arena); + static void Case(Geometry::Tags::MultiPartGeometry, Geometry &geom, ArenaAllocator &arena) { + for (uint32_t i = 0; i < MultiPartGeometry::PartCount(geom); i++) { + auto &part = MultiPartGeometry::Part(geom, i); + Geometry::Match(part, arena); } } }; UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { auto geom = Geometry::Deserialize(lstate.arena, input); - geom.Visit(lstate.arena); - return geom.Serialize(result); + Geometry::Match(geom, lstate.arena); + return Geometry::Serialize(geom, result); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_force.cpp b/spatial/src/spatial/core/functions/scalar/st_force.cpp index 7ccd89ab..07e73a50 100644 --- a/spatial/src/spatial/core/functions/scalar/st_force.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_force.cpp @@ -25,9 +25,9 @@ static void GeometryFunction(DataChunk &args, ExpressionState &state, Vector &re auto &m_values = args.data[2]; TernaryExecutor::Execute( input, z_values, m_values, result, count, [&](const geometry_t &blob, double default_z, double default_m) { - auto geom = Geometry::Deserialize(arena, blob); - geom.SetVertexType(arena, HAS_Z, HAS_M, default_z, default_m); - return geom.Serialize(result); + auto geom = Geometry::Deserialize(arena, blob); + geom.SetVertexType(arena, HAS_Z, HAS_M, default_z, default_m); + return Geometry::Serialize(geom, result); }); } else if (HAS_Z || HAS_M) { @@ -37,15 +37,15 @@ static void GeometryFunction(DataChunk &args, ExpressionState &state, Vector &re auto def_z = HAS_Z ? default_value : 0; auto def_m = HAS_M ? default_value : 0; - auto geom = Geometry::Deserialize(arena, blob); - geom.SetVertexType(arena, HAS_Z, HAS_M, def_z, def_m); - return geom.Serialize(result); + auto geom = Geometry::Deserialize(arena, blob); + geom.SetVertexType(arena, HAS_Z, HAS_M, def_z, def_m); + return Geometry::Serialize(geom, result); }); } else { UnaryExecutor::Execute(input, result, count, [&](const geometry_t &blob) { - auto geom = Geometry::Deserialize(arena, blob); - geom.SetVertexType(arena, HAS_Z, HAS_M); - return geom.Serialize(result); + auto geom = Geometry::Deserialize(arena, blob); + geom.SetVertexType(arena, HAS_Z, HAS_M); + return Geometry::Serialize(geom, result); }); } } diff --git a/spatial/src/spatial/core/functions/scalar/st_geomfromhexwkb.cpp b/spatial/src/spatial/core/functions/scalar/st_geomfromhexwkb.cpp index 43445fda..3192d5c7 100644 --- a/spatial/src/spatial/core/functions/scalar/st_geomfromhexwkb.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_geomfromhexwkb.cpp @@ -48,7 +48,8 @@ void GeometryFromHEXWKB(DataChunk &args, ExpressionState &state, Vector &result) blob_ptr[blob_idx++] = (byte_a << 4) + byte_b; } - return reader.Deserialize(blob_ptr, blob_size).Serialize(result); + auto geom = reader.Deserialize(blob_ptr, blob_size); + return Geometry::Serialize(geom, result); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_geomfromtext.cpp b/spatial/src/spatial/core/functions/scalar/st_geomfromtext.cpp index c52d41a9..50962670 100644 --- a/spatial/src/spatial/core/functions/scalar/st_geomfromtext.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_geomfromtext.cpp @@ -45,7 +45,7 @@ static void GeometryFromWKTFunction(DataChunk &args, ExpressionState &state, Vec [&](string_t &wkt, ValidityMask &mask, idx_t idx) { try { auto geom = reader.Parse(wkt); - return geom.Serialize(result); + return Geometry::Serialize(geom, result); } catch (InvalidInputException &error) { if (!info.ignore_invalid) { throw; diff --git a/spatial/src/spatial/core/functions/scalar/st_geomfromwkb.cpp b/spatial/src/spatial/core/functions/scalar/st_geomfromwkb.cpp index eb5c6da8..8605fa66 100644 --- a/spatial/src/spatial/core/functions/scalar/st_geomfromwkb.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_geomfromwkb.cpp @@ -273,8 +273,10 @@ static void GeometryFromWKBFunction(DataChunk &args, ExpressionState &state, Vec auto count = args.size(); WKBReader reader(arena); - UnaryExecutor::Execute( - input, result, count, [&](string_t input) { return reader.Deserialize(input).Serialize(result); }); + UnaryExecutor::Execute(input, result, count, [&](string_t input) { + auto geom = reader.Deserialize(input); + return Geometry::Serialize(geom, result); + }); } //------------------------------------------------------------------------------ diff --git a/spatial/src/spatial/geos/functions/scalar/st_is_closed.cpp b/spatial/src/spatial/core/functions/scalar/st_is_closed.cpp similarity index 57% rename from spatial/src/spatial/geos/functions/scalar/st_is_closed.cpp rename to spatial/src/spatial/core/functions/scalar/st_is_closed.cpp index c41749d0..88d24989 100644 --- a/spatial/src/spatial/geos/functions/scalar/st_is_closed.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_is_closed.cpp @@ -1,8 +1,8 @@ #include "spatial/common.hpp" #include "spatial/core/types.hpp" -#include "spatial/geos/functions/scalar.hpp" -#include "spatial/geos/functions/common.hpp" -#include "spatial/geos/geos_wrappers.hpp" +#include "spatial/core/functions/scalar.hpp" +#include "spatial/core/functions/common.hpp" +#include "spatial/core/geometry/geometry.hpp" #include "duckdb/parser/parsed_data/create_scalar_function_info.hpp" #include "duckdb/common/vector_operations/unary_executor.hpp" @@ -10,16 +10,27 @@ namespace spatial { -namespace geos { - -using namespace spatial::core; +namespace core { static void IsClosedFunction(DataChunk &args, ExpressionState &state, Vector &result) { - auto &lstate = GEOSFunctionLocalState::ResetAndGet(state); - auto &ctx = lstate.ctx.GetCtx(); + auto &lstate = GeometryFunctionLocalState::ResetAndGet(state); + auto &arena = lstate.arena; + + // TODO: We should support more than just LINESTRING and MULTILINESTRING (like PostGIS does) UnaryExecutor::Execute(args.data[0], result, args.size(), [&](geometry_t input) { - auto geom = lstate.ctx.Deserialize(input); - return GEOSisClosed_r(ctx, geom.get()); + struct op { + static bool Case(Geometry::Tags::LineString, const Geometry &geom) { + return LineString::IsClosed(geom); + } + static bool Case(Geometry::Tags::MultiLineString, const Geometry &geom) { + return MultiLineString::IsClosed(geom); + } + static bool Case(Geometry::Tags::AnyGeometry, const Geometry &) { + throw InvalidInputException("ST_IsClosed only accepts LINESTRING and MULTILINESTRING geometries"); + } + }; + auto geom = Geometry::Deserialize(arena, input); + return Geometry::Match(geom); }); } @@ -39,17 +50,17 @@ static constexpr DocTag DOC_TAGS[] = {{"ext", "spatial"}, {"category", "property //------------------------------------------------------------------------------ // Register Functions //------------------------------------------------------------------------------ -void GEOSScalarFunctions::RegisterStIsClosed(DatabaseInstance &db) { +void CoreScalarFunctions::RegisterStIsClosed(DatabaseInstance &db) { ScalarFunctionSet set("ST_IsClosed"); set.AddFunction(ScalarFunction({GeoTypes::GEOMETRY()}, LogicalType::BOOLEAN, IsClosedFunction, nullptr, nullptr, - nullptr, GEOSFunctionLocalState::Init)); + nullptr, GeometryFunctionLocalState::Init)); ExtensionUtil::RegisterFunction(db, set); DocUtil::AddDocumentation(db, "ST_IsClosed", DOC_DESCRIPTION, DOC_EXAMPLE, DOC_TAGS); } -} // namespace geos +} // namespace core } // namespace spatial diff --git a/spatial/src/spatial/core/functions/scalar/st_isempty.cpp b/spatial/src/spatial/core/functions/scalar/st_isempty.cpp index 2c658b23..011e3d0b 100644 --- a/spatial/src/spatial/core/functions/scalar/st_isempty.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_isempty.cpp @@ -52,8 +52,10 @@ static void GeometryIsEmptyFunction(DataChunk &args, ExpressionState &state, Vec auto &input = args.data[0]; auto count = args.size(); - UnaryExecutor::Execute( - input, result, count, [&](geometry_t input) { return Geometry::Deserialize(lstate.arena, input).IsEmpty(); }); + UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { + auto geom = Geometry::Deserialize(lstate.arena, input); + return Geometry::IsEmpty(geom); + }); if (count == 1) { result.SetVectorType(VectorType::CONSTANT_VECTOR); diff --git a/spatial/src/spatial/core/functions/scalar/st_length.cpp b/spatial/src/spatial/core/functions/scalar/st_length.cpp index 90b80b56..52d094f7 100644 --- a/spatial/src/spatial/core/functions/scalar/st_length.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_length.cpp @@ -54,34 +54,12 @@ static void GeometryLengthFunction(DataChunk &args, ExpressionState &state, Vect auto &input = args.data[0]; auto count = args.size(); - struct op { - static double Apply(const LineString &line) { - return line.Length(); - } - - static double Apply(const MultiLineString &mline) { - double sum = 0.0; - for (const auto &line : mline) { - sum += line.Length(); - } - return sum; - } - - static double Apply(const GeometryCollection &collection) { - double sum = 0.0; - for (const auto &geom : collection) { - sum += geom.Visit(); - } - return sum; - } - - static double Apply(const BaseGeometry &) { - return 0.0; - } - }; - - UnaryExecutor::Execute( - input, result, count, [&](geometry_t input) { return Geometry::Deserialize(arena, input).Visit(); }); + UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { + auto geom = Geometry::Deserialize(arena, input); + double length = 0.0; + Geometry::ExtractLines(geom, [&](const Geometry &line) { length += LineString::Length(line); }); + return length; + }); if (count == 1) { result.SetVectorType(VectorType::CONSTANT_VECTOR); diff --git a/spatial/src/spatial/core/functions/scalar/st_makeenvelope.cpp b/spatial/src/spatial/core/functions/scalar/st_makeenvelope.cpp index 101f79c9..2b3acf1f 100644 --- a/spatial/src/spatial/core/functions/scalar/st_makeenvelope.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_makeenvelope.cpp @@ -26,8 +26,8 @@ static void MakeEnvelopeFunction(DataChunk &args, ExpressionState &state, Vector GenericExecutor::ExecuteQuaternary( min_x_vec, min_y_vec, max_x_vec, max_y_vec, result, count, [&](DOUBLE_TYPE x_min, DOUBLE_TYPE y_min, DOUBLE_TYPE x_max, DOUBLE_TYPE y_max) { - auto box = Polygon::FromBox(lstate.arena, x_min.val, y_min.val, x_max.val, y_max.val); - return Geometry(box).Serialize(result); + auto box = Polygon::CreateFromBox(lstate.arena, x_min.val, y_min.val, x_max.val, y_max.val); + return Geometry::Serialize(box, result); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_makeline.cpp b/spatial/src/spatial/core/functions/scalar/st_makeline.cpp index 43f6509f..76e45305 100644 --- a/spatial/src/spatial/core/functions/scalar/st_makeline.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_makeline.cpp @@ -25,7 +25,7 @@ static void MakeLineListFunction(DataChunk &args, ExpressionState &state, Vector auto offset = geometry_list.offset; auto length = geometry_list.length; - auto line_geom = LineString::Create(arena, length, false, false); + auto line = LineString::Create(arena, length, false, false); uint32_t vertex_idx = 0; for (idx_t i = offset; i < offset + length; i++) { @@ -44,23 +44,21 @@ static void MakeLineListFunction(DataChunk &args, ExpressionState &state, Vector if (geometry_blob.GetProperties().HasZ() || geometry_blob.GetProperties().HasM()) { throw InvalidInputException("ST_MakeLine from list does not support Z or M geometries"); } - - auto point = Geometry::Deserialize(arena, geometry_blob).As(); - if (point.IsEmpty()) { + auto point = Geometry::Deserialize(arena, geometry_blob); + if (Point::IsEmpty(point)) { continue; } - auto vertex = point.Get(0); - line_geom.Set(vertex_idx++, vertex.x, vertex.y); + LineString::SetVertex(line, vertex_idx++, Point::GetVertex(point)); } // Shrink the vertex array to the actual size - line_geom.Resize(arena, vertex_idx); + LineString::Resize(line, arena, vertex_idx); - if (line_geom.Count() == 1) { + if (line.Count() == 1) { throw InvalidInputException("ST_MakeLine requires zero or two or more POINT geometries"); } - return Geometry(line_geom).Serialize(result); + return Geometry::Serialize(line, result); }); } @@ -79,30 +77,28 @@ static void MakeLineBinaryFunction(DataChunk &args, ExpressionState &state, Vect auto geometry_left = Geometry::Deserialize(arena, geom_blob_left); auto geometry_right = Geometry::Deserialize(arena, geom_blob_right); - if (geometry_left.IsEmpty() && geometry_right.IsEmpty()) { + if (Point::IsEmpty(geometry_left) && Point::IsEmpty(geometry_right)) { // Empty linestring - return Geometry(LineString::Empty(false, false)).Serialize(result); + auto empty = LineString::CreateEmpty(false, false); + return Geometry::Serialize(empty, result); } - if (geometry_left.IsEmpty() || geometry_right.IsEmpty()) { + if (Point::IsEmpty(geometry_left) || Point::IsEmpty(geometry_right)) { throw InvalidInputException("ST_MakeLine requires zero or two or more POINT geometries"); } auto has_z = geom_blob_left.GetProperties().HasZ() || geom_blob_right.GetProperties().HasZ(); auto has_m = geom_blob_left.GetProperties().HasM() || geom_blob_right.GetProperties().HasM(); - auto &point_left = geometry_left.As(); - auto &point_right = geometry_right.As(); - // TODO: Dont upcast the child geometries, just append and let the append function handle upcasting of the // target instead. - point_left.SetVertexType(arena, has_z, has_m); - point_right.SetVertexType(arena, has_z, has_m); + geometry_left.SetVertexType(arena, has_z, has_m); + geometry_right.SetVertexType(arena, has_z, has_m); - auto line_geom = LineString::Empty(has_z, has_m); - line_geom.Append(arena, point_left); - line_geom.Append(arena, point_right); - return Geometry(line_geom).Serialize(result); + auto line_geom = LineString::CreateEmpty(has_z, has_m); + LineString::Append(line_geom, arena, geometry_left); + LineString::Append(line_geom, arena, geometry_right); + return Geometry::Serialize(line_geom, result); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_makepolygon.cpp b/spatial/src/spatial/core/functions/scalar/st_makepolygon.cpp index e3e6c5f8..2300d60b 100644 --- a/spatial/src/spatial/core/functions/scalar/st_makepolygon.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_makepolygon.cpp @@ -33,13 +33,12 @@ static void MakePolygonFromRingsFunction(DataChunk &args, ExpressionState &state throw InvalidInputException("ST_MakePolygon does not support Z or M geometries"); } - auto shell_geom = Geometry::Deserialize(arena, line_blob); - auto &shell = shell_geom.As(); - if (shell.Count() < 4) { + auto shell = Geometry::Deserialize(arena, line_blob); + if (LineString::VertexCount(shell) < 4) { throw InvalidInputException("ST_MakePolygon shell requires at least 4 vertices"); } - if (!shell.IsClosed()) { + if (!LineString::IsClosed(shell)) { throw InvalidInputException( "ST_MakePolygon shell must be closed (first and last vertex must be equal)"); } @@ -48,7 +47,7 @@ static void MakePolygonFromRingsFunction(DataChunk &args, ExpressionState &state auto holes_offset = rings_list.offset; auto holes_length = rings_list.length; - vector rings; + vector rings; rings.push_back(shell); for (idx_t hole_idx = 0; hole_idx < holes_length; hole_idx++) { @@ -63,31 +62,29 @@ static void MakePolygonFromRingsFunction(DataChunk &args, ExpressionState &state if (geometry_blob.GetProperties().HasZ() || geometry_blob.GetProperties().HasM()) { throw InvalidInputException("ST_MakePolygon does not support Z or M geometries"); } - - auto hole_geometry = Geometry::Deserialize(arena, geometry_blob); - if (hole_geometry.GetType() != GeometryType::LINESTRING) { + if (geometry_blob.GetType() != GeometryType::LINESTRING) { throw InvalidInputException( StringUtil::Format("ST_MakePolygon hole #%lu is not a LINESTRING geometry", hole_idx + 1)); } - auto &hole = hole_geometry.As(); - if (hole.Count() < 4) { + auto hole = Geometry::Deserialize(arena, geometry_blob); + if (LineString::VertexCount(hole) < 4) { throw InvalidInputException( StringUtil::Format("ST_MakePolygon hole #%lu requires at least 4 vertices", hole_idx + 1)); } - if (!hole.IsClosed()) { + if (!LineString::IsClosed(hole)) { throw InvalidInputException(StringUtil::Format( "ST_MakePolygon hole #%lu must be closed (first and last vertex must be equal)", hole_idx + 1)); } rings.push_back(hole); } - + // TODO: Add constructor that takes a vector of rings auto polygon = Polygon::Create(arena, rings.size(), false, false); for (idx_t ring_idx = 0; ring_idx < rings.size(); ring_idx++) { - polygon[ring_idx] = std::move(rings[ring_idx]); + Polygon::Part(polygon, ring_idx) = std::move(rings[ring_idx]); } - return Geometry(polygon).Serialize(result); + return Geometry::Serialize(polygon, result); }); } @@ -102,23 +99,21 @@ static void MakePolygonFromShellFunction(DataChunk &args, ExpressionState &state throw InvalidInputException("ST_MakePolygon only accepts LINESTRING geometries"); } - auto line_geom = Geometry::Deserialize(arena, line_blob); - auto &line = line_geom.As(); + auto line = Geometry::Deserialize(arena, line_blob); - auto line_count = line.Count(); - if (line_count < 4) { + if (LineString::VertexCount(line) < 4) { throw InvalidInputException("ST_MakePolygon shell requires at least 4 vertices"); } - if (!line.IsClosed()) { + if (!LineString::IsClosed(line)) { throw InvalidInputException("ST_MakePolygon shell must be closed (first and last vertex must be equal)"); } auto props = line_blob.GetProperties(); auto polygon = Polygon::Create(arena, 1, props.HasZ(), props.HasM()); - polygon[0] = std::move(line); - return Geometry(polygon).Serialize(result); + Polygon::Part(polygon, 0) = std::move(line); + return Geometry::Serialize(polygon, result); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_ngeometries.cpp b/spatial/src/spatial/core/functions/scalar/st_ngeometries.cpp index 394ed5e6..c5cfe420 100644 --- a/spatial/src/spatial/core/functions/scalar/st_ngeometries.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_ngeometries.cpp @@ -22,17 +22,18 @@ static void GeometryNGeometriesFunction(DataChunk &args, ExpressionState &state, UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { struct op { - static int32_t Apply(const CollectionGeometry &collection) { - return static_cast(collection.Count()); + static int32_t Case(Geometry::Tags::CollectionGeometry, const Geometry &collection) { + return static_cast(CollectionGeometry::PartCount(collection)); } - static int32_t Apply(const Polygon &geom) { - return geom.IsEmpty() ? 0 : 1; + static int32_t Case(Geometry::Tags::Polygon, const Geometry &geom) { + return Polygon::IsEmpty(geom) ? 0 : 1; } - static int32_t Apply(const SinglePartGeometry &geom) { - return geom.IsEmpty() ? 0 : 1; + static int32_t Case(Geometry::Tags::SinglePartGeometry, const Geometry &geom) { + return SinglePartGeometry::IsEmpty(geom) ? 0 : 1; } }; - return Geometry::Deserialize(ctx.arena, input).Visit(); + auto geom = Geometry::Deserialize(ctx.arena, input); + return Geometry::Match(geom); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_ninteriorrings.cpp b/spatial/src/spatial/core/functions/scalar/st_ninteriorrings.cpp index 3f4fa4d7..b3a312dc 100644 --- a/spatial/src/spatial/core/functions/scalar/st_ninteriorrings.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_ninteriorrings.cpp @@ -40,7 +40,8 @@ static void GeometryInteriorRingsFunction(DataChunk &args, ExpressionState &stat validity.SetInvalid(idx); return 0; } - auto rings = Geometry::Deserialize(arena, input).As().Count(); + auto polygon = Geometry::Deserialize(arena, input); + auto rings = Polygon::PartCount(polygon); return rings == 0 ? 0 : static_cast(rings - 1); // -1 for the exterior ring }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_npoints.cpp b/spatial/src/spatial/core/functions/scalar/st_npoints.cpp index ecb259c0..0c4d7572 100644 --- a/spatial/src/spatial/core/functions/scalar/st_npoints.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_npoints.cpp @@ -76,20 +76,23 @@ static void GeometryNumPointsFunction(DataChunk &args, ExpressionState &state, V auto count = args.size(); struct op { - static uint32_t Apply(const SinglePartGeometry &geom) { + static uint32_t Case(Geometry::Tags::SinglePartGeometry, const Geometry &geom) { return geom.Count(); } - static uint32_t Apply(const MultiPartGeometry &geom) { + static uint32_t Case(Geometry::Tags::MultiPartGeometry, const Geometry &geom) { uint32_t count = 0; - for (const auto &part : geom) { - count += part.Visit(); + for (uint32_t i = 0; i < MultiPartGeometry::PartCount(geom); i++) { + auto part = MultiPartGeometry::Part(geom, i); + count += Geometry::Match(part); } return count; } }; - UnaryExecutor::Execute( - input, result, count, [&](geometry_t input) { return Geometry::Deserialize(arena, input).Visit(); }); + UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { + auto geom = Geometry::Deserialize(arena, input); + return Geometry::Match(geom); + }); } //------------------------------------------------------------------------------ diff --git a/spatial/src/spatial/core/functions/scalar/st_perimeter.cpp b/spatial/src/spatial/core/functions/scalar/st_perimeter.cpp index 82a81f99..cdd30c4f 100644 --- a/spatial/src/spatial/core/functions/scalar/st_perimeter.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_perimeter.cpp @@ -78,38 +78,16 @@ static void GeometryPerimeterFunction(DataChunk &args, ExpressionState &state, V auto &input = args.data[0]; auto count = args.size(); - struct op { - static double Apply(const Polygon &poly) { - double sum = 0; - for (const auto &ring : poly) { - sum += ring.Length(); + UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { + auto geom = Geometry::Deserialize(arena, input); + double perimeter = 0.0; + Geometry::ExtractPolygons(geom, [&](const Geometry &poly) { + for (auto &p : Polygon::Parts(poly)) { + perimeter += LineString::Length(p); } - return sum; - } - - static double Apply(const MultiPolygon &mline) { - double sum = 0.0; - for (const auto &poly : mline) { - sum += Apply(poly); - } - return sum; - } - - static double Apply(const GeometryCollection &collection) { - double sum = 0.0; - for (const auto &geom : collection) { - sum += geom.Visit(); - } - return sum; - } - - static double Apply(const BaseGeometry &) { - return 0.0; - } - }; - - UnaryExecutor::Execute( - input, result, count, [&](geometry_t input) { return Geometry::Deserialize(arena, input).Visit(); }); + }); + return perimeter; + }); if (count == 1) { result.SetVectorType(VectorType::CONSTANT_VECTOR); diff --git a/spatial/src/spatial/core/functions/scalar/st_point.cpp b/spatial/src/spatial/core/functions/scalar/st_point.cpp index 86535f1f..d6c79e74 100644 --- a/spatial/src/spatial/core/functions/scalar/st_point.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_point.cpp @@ -107,7 +107,7 @@ static void PointFunction(DataChunk &args, ExpressionState &state, Vector &resul auto count = args.size(); BinaryExecutor::Execute(x, y, result, count, [&](double x, double y) { - return Geometry(Point::FromVertex(arena, VertexXY {x, y})).Serialize(result); + return Geometry::Serialize(Point::CreateFromVertex(arena, VertexXY {x, y}), result); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_pointn.cpp b/spatial/src/spatial/core/functions/scalar/st_pointn.cpp index 1040a7cb..e4d33d57 100644 --- a/spatial/src/spatial/core/functions/scalar/st_pointn.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_pointn.cpp @@ -79,8 +79,8 @@ static void GeometryPointNFunction(DataChunk &args, ExpressionState &state, Vect mask.SetInvalid(row_idx); return geometry_t {}; } - auto line = Geometry::Deserialize(arena, input).As(); - auto point_count = line.Count(); + auto line = Geometry::Deserialize(arena, input); + auto point_count = LineString::VertexCount(line); if (point_count == 0 || index == 0 || index < -static_cast(point_count) || index > static_cast(point_count)) { @@ -89,10 +89,8 @@ static void GeometryPointNFunction(DataChunk &args, ExpressionState &state, Vect } auto actual_index = index < 0 ? point_count + index : index - 1; - - auto point = Point::FromReference(line, actual_index); - - return Geometry(point).Serialize(result); + auto point = LineString::GetPointAsReference(line, actual_index); + return Geometry::Serialize(point, result); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_quadkey.cpp b/spatial/src/spatial/core/functions/scalar/st_quadkey.cpp index 650ac718..c8c25032 100644 --- a/spatial/src/spatial/core/functions/scalar/st_quadkey.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_quadkey.cpp @@ -69,23 +69,19 @@ static void GeometryQuadKeyFunction(DataChunk &args, ExpressionState &state, Vec BinaryExecutor::Execute( geom, level, result, count, [&](geometry_t input, int32_t level) { + if (level < 1 || level > 23) { + throw InvalidInputException("ST_QuadKey: Level must be between 1 and 23"); + } if (input.GetType() != GeometryType::POINT) { throw InvalidInputException("ST_QuadKey: Only POINT geometries are supported"); } auto point = Geometry::Deserialize(arena, input); - if (point.IsEmpty()) { + if (Point::IsEmpty(point)) { throw InvalidInputException("ST_QuadKey: Empty geometries are not supported"); } - auto vertex = point.As().Get(0); - auto x = vertex.x; - auto y = vertex.y; - - if (level < 1 || level > 23) { - throw InvalidInputException("ST_QuadKey: Level must be between 1 and 23"); - } - + auto vertex = Point::GetVertex(point); char buffer[64]; - GetQuadKey(x, y, level, buffer); + GetQuadKey(vertex.x, vertex.y, level, buffer); return StringVector::AddString(result, buffer, level); }); } diff --git a/spatial/src/spatial/core/functions/scalar/st_startpoint.cpp b/spatial/src/spatial/core/functions/scalar/st_startpoint.cpp index c024485a..bbe1a545 100644 --- a/spatial/src/spatial/core/functions/scalar/st_startpoint.cpp +++ b/spatial/src/spatial/core/functions/scalar/st_startpoint.cpp @@ -64,24 +64,21 @@ static void GeometryStartPointFunction(DataChunk &args, ExpressionState &state, auto &geom_vec = args.data[0]; auto count = args.size(); - UnaryExecutor::ExecuteWithNulls( - geom_vec, result, count, [&](geometry_t input, ValidityMask &mask, idx_t row_idx) { - if (input.GetType() != GeometryType::LINESTRING) { - mask.SetInvalid(row_idx); - return geometry_t {}; - } - - auto line = Geometry::Deserialize(lstate.arena, input).As(); - - if (line.IsEmpty()) { - mask.SetInvalid(row_idx); - return geometry_t {}; - } - - auto point = Point::FromReference(line, 0); - - return Geometry(point).Serialize(result); - }); + UnaryExecutor::ExecuteWithNulls(geom_vec, result, count, + [&](geometry_t input, ValidityMask &mask, idx_t row_idx) { + if (input.GetType() != GeometryType::LINESTRING) { + mask.SetInvalid(row_idx); + return geometry_t {}; + } + + auto line = Geometry::Deserialize(lstate.arena, input); + if (LineString::IsEmpty(line)) { + mask.SetInvalid(row_idx); + return geometry_t {}; + } + auto point = LineString::GetPointAsReference(line, 0); + return Geometry::Serialize(point, result); + }); } //------------------------------------------------------------------------------ // Documentation diff --git a/spatial/src/spatial/core/geometry/geometry.cpp b/spatial/src/spatial/core/geometry/geometry.cpp index dcefe584..9f06451c 100644 --- a/spatial/src/spatial/core/geometry/geometry.cpp +++ b/spatial/src/spatial/core/geometry/geometry.cpp @@ -1,5 +1,6 @@ #include "spatial/common.hpp" #include "spatial/core/geometry/geometry.hpp" +#include "spatial/core/util/math.hpp" namespace spatial { @@ -8,142 +9,115 @@ namespace core { //------------------------------------------------------------------------------ // Single Part Geometry //------------------------------------------------------------------------------ -void SinglePartGeometry::Reference(const SinglePartGeometry &other, uint32_t offset, uint32_t count) { - properties = other.properties; - data.vertex_data = other.data.vertex_data + offset * properties.VertexSize(); - data_count = count; - is_readonly = true; -} - -void SinglePartGeometry::ReferenceData(const_data_ptr_t data_ptr, uint32_t count, bool has_z, bool has_m) { - properties.SetZ(has_z); - properties.SetM(has_m); - data.vertex_data = const_cast(data_ptr); - data_count = count; - is_readonly = true; -} - -void SinglePartGeometry::Copy(ArenaAllocator &alloc, const SinglePartGeometry &other, uint32_t offset, uint32_t count) { - properties = other.properties; - data_count = count; - data.vertex_data = alloc.AllocateAligned(properties.VertexSize() * count); - memcpy(data.vertex_data, other.data.vertex_data + offset * properties.VertexSize(), - properties.VertexSize() * count); - is_readonly = false; -} +void SinglePartGeometry::Resize(Geometry &geom, ArenaAllocator &alloc, uint32_t new_count) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.type)); -void SinglePartGeometry::CopyData(ArenaAllocator &alloc, const_data_ptr_t data_ptr, uint32_t count, bool has_z, - bool has_m) { - properties.SetZ(has_z); - properties.SetM(has_m); - data_count = count; - data.vertex_data = alloc.AllocateAligned(properties.VertexSize() * count); - memcpy(data.vertex_data, data_ptr, properties.VertexSize() * count); - is_readonly = false; -} - -void SinglePartGeometry::Resize(ArenaAllocator &alloc, uint32_t new_count) { - auto vertex_size = properties.VertexSize(); - if (new_count == data_count) { + auto vertex_size = geom.properties.VertexSize(); + if (new_count == geom.data_count) { return; } - if (data.vertex_data == nullptr) { - data.vertex_data = alloc.AllocateAligned(vertex_size * new_count); - data_count = new_count; - is_readonly = false; - memset(data.vertex_data, 0, vertex_size * new_count); + if (geom.data_ptr == nullptr) { + geom.data_ptr = alloc.AllocateAligned(vertex_size * new_count); + geom.data_count = new_count; + geom.is_readonly = false; + memset(geom.data_ptr, 0, vertex_size * new_count); return; } - if (!IsReadOnly()) { - data.vertex_data = alloc.Reallocate(data.vertex_data, data_count * vertex_size, vertex_size * new_count); - data_count = new_count; + if (!geom.is_readonly) { + geom.data_ptr = alloc.ReallocateAligned(geom.data_ptr, geom.data_count * vertex_size, vertex_size * new_count); + geom.data_count = new_count; } else { auto new_data = alloc.AllocateAligned(vertex_size * new_count); memset(new_data, 0, vertex_size * new_count); - auto copy_count = std::min(data_count, new_count); - memcpy(new_data, data.vertex_data, vertex_size * copy_count); - data.vertex_data = new_data; - data_count = new_count; - is_readonly = false; + auto copy_count = std::min(geom.data_count, new_count); + memcpy(new_data, geom.data_ptr, vertex_size * copy_count); + geom.data_ptr = new_data; + geom.data_count = new_count; + geom.is_readonly = false; } } -void SinglePartGeometry::Append(ArenaAllocator &alloc, const SinglePartGeometry &other) { - Append(alloc, &other, 1); +void SinglePartGeometry::Append(Geometry &geom, ArenaAllocator &alloc, const Geometry &other) { + Append(geom, alloc, &other, 1); } -void SinglePartGeometry::Append(ArenaAllocator &alloc, const SinglePartGeometry *others, uint32_t others_count) { - if (IsReadOnly()) { - MakeMutable(alloc); +void SinglePartGeometry::Append(Geometry &geom, ArenaAllocator &alloc, const Geometry *others, uint32_t others_count) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.type)); + if (geom.IsReadOnly()) { + MakeMutable(geom, alloc); } - auto old_count = data_count; + auto old_count = geom.data_count; auto new_count = old_count; for (uint32_t i = 0; i < others_count; i++) { new_count += others[i].Count(); - D_ASSERT(properties.HasZ() == others[i].properties.HasZ()); - D_ASSERT(properties.HasM() == others[i].properties.HasM()); + // The other geometries has to be single part + D_ASSERT(GeometryTypes::IsSinglePart(others[i].type)); + // And have the same z and m properties + D_ASSERT(geom.properties.HasZ() == others[i].properties.HasZ()); + D_ASSERT(geom.properties.HasM() == others[i].properties.HasM()); } - Resize(alloc, new_count); - auto vertex_size = properties.VertexSize(); + Resize(geom, alloc, new_count); + + auto vertex_size = geom.properties.VertexSize(); for (uint32_t i = 0; i < others_count; i++) { auto other = others[i]; - memcpy(data.vertex_data + old_count * vertex_size, other.data.vertex_data, vertex_size * other.data_count); + memcpy(geom.data_ptr + old_count * vertex_size, other.data_ptr, vertex_size * other.data_count); old_count += other.data_count; } - data_count = new_count; + geom.data_count = new_count; } -void SinglePartGeometry::SetVertexType(ArenaAllocator &alloc, bool has_z, bool has_m, double default_z, +void SinglePartGeometry::SetVertexType(Geometry &geom, ArenaAllocator &alloc, bool has_z, bool has_m, double default_z, double default_m) { - if (properties.HasZ() == has_z && properties.HasM() == has_m) { + if (geom.properties.HasZ() == has_z && geom.properties.HasM() == has_m) { return; } - if (IsReadOnly()) { - MakeMutable(alloc); + if (geom.is_readonly) { + MakeMutable(geom, alloc); } - auto used_to_have_z = properties.HasZ(); - auto used_to_have_m = properties.HasM(); - auto old_vertex_size = properties.VertexSize(); + auto used_to_have_z = geom.properties.HasZ(); + auto used_to_have_m = geom.properties.HasM(); + auto old_vertex_size = geom.properties.VertexSize(); - properties.SetZ(has_z); - properties.SetM(has_m); + geom.properties.SetZ(has_z); + geom.properties.SetM(has_m); - auto new_vertex_size = properties.VertexSize(); + auto new_vertex_size = geom.properties.VertexSize(); // Case 1: The new vertex size is larger than the old vertex size if (new_vertex_size > old_vertex_size) { - data.vertex_data = - alloc.ReallocateAligned(data.vertex_data, data_count * old_vertex_size, data_count * new_vertex_size); + geom.data_ptr = alloc.ReallocateAligned(geom.data_ptr, geom.data_count * old_vertex_size, + geom.data_count * new_vertex_size); // There are 5 cases here: if (used_to_have_m && has_m && !used_to_have_z && has_z) { // 1. We go from XYM to XYZM // This is special, because we need to slide the M value to the end of each vertex - for (int64_t i = data_count - 1; i >= 0; i--) { + for (int64_t i = geom.data_count - 1; i >= 0; i--) { auto old_offset = i * old_vertex_size; auto new_offset = i * new_vertex_size; auto old_m_offset = old_offset + sizeof(double) * 2; auto new_z_offset = new_offset + sizeof(double) * 2; auto new_m_offset = new_offset + sizeof(double) * 3; // Move the M value - memcpy(data.vertex_data + new_m_offset, data.vertex_data + old_m_offset, sizeof(double)); + memcpy(geom.data_ptr + new_m_offset, geom.data_ptr + old_m_offset, sizeof(double)); // Set the new Z value - memcpy(data.vertex_data + new_z_offset, &default_z, sizeof(double)); + memcpy(geom.data_ptr + new_z_offset, &default_z, sizeof(double)); // Move the X and Y values - memcpy(data.vertex_data + new_offset, data.vertex_data + old_offset, sizeof(double) * 2); + memcpy(geom.data_ptr + new_offset, geom.data_ptr + old_offset, sizeof(double) * 2); } } else if (!used_to_have_z && has_z && !used_to_have_m && has_m) { // 2. We go from XY to XYZM // This is special, because we need to add both the default Z and M values to the end of each vertex - for (int64_t i = data_count - 1; i >= 0; i--) { + for (int64_t i = geom.data_count - 1; i >= 0; i--) { auto old_offset = i * old_vertex_size; auto new_offset = i * new_vertex_size; - memcpy(data.vertex_data + new_offset, data.vertex_data + old_offset, sizeof(double) * 2); - memcpy(data.vertex_data + new_offset + sizeof(double) * 2, &default_z, sizeof(double)); - memcpy(data.vertex_data + new_offset + sizeof(double) * 3, &default_m, sizeof(double)); + memcpy(geom.data_ptr + new_offset, geom.data_ptr + old_offset, sizeof(double) * 2); + memcpy(geom.data_ptr + new_offset + sizeof(double) * 2, &default_z, sizeof(double)); + memcpy(geom.data_ptr + new_offset + sizeof(double) * 3, &default_m, sizeof(double)); } } else { // Otherwise: @@ -152,11 +126,11 @@ void SinglePartGeometry::SetVertexType(ArenaAllocator &alloc, bool has_z, bool h // 5. We go from XYZ to XYZM // These are all really the same, we just add the default to the end auto default_value = has_m ? default_m : default_z; - for (int64_t i = data_count - 1; i >= 0; i--) { + for (int64_t i = geom.data_count - 1; i >= 0; i--) { auto old_offset = i * old_vertex_size; auto new_offset = i * new_vertex_size; - memmove(data.vertex_data + new_offset, data.vertex_data + old_offset, old_vertex_size); - memcpy(data.vertex_data + new_offset + old_vertex_size, &default_value, sizeof(double)); + memmove(geom.data_ptr + new_offset, geom.data_ptr + old_offset, old_vertex_size); + memcpy(geom.data_ptr + new_offset + old_vertex_size, &default_value, sizeof(double)); } } } @@ -165,90 +139,94 @@ void SinglePartGeometry::SetVertexType(ArenaAllocator &alloc, bool has_z, bool h // This only happens when we go from XYZ -> XYM or XYM -> XYZ // In this case we just need to set the default on the third dimension auto default_value = has_m ? default_m : default_z; - for (uint32_t i = 0; i < data_count; i++) { + for (uint32_t i = 0; i < geom.data_count; i++) { auto offset = i * new_vertex_size + sizeof(double) * 2; - memcpy(data.vertex_data + offset, &default_value, sizeof(double)); + memcpy(geom.data_ptr + offset, &default_value, sizeof(double)); } } // Case 3: The new vertex size is smaller than the old vertex size. // In this case we need to allocate new memory and copy the data over to not lose any data else { - auto new_data = alloc.AllocateAligned(data_count * new_vertex_size); - memset(new_data, 0, data_count * new_vertex_size); + auto new_data = alloc.AllocateAligned(geom.data_count * new_vertex_size); + memset(new_data, 0, geom.data_count * new_vertex_size); // Special case: If we go from XYZM to XYM, we need to slide the M value to the end of each vertex if (used_to_have_z && used_to_have_m && !has_z && has_m) { - for (uint32_t i = 0; i < data_count; i++) { + for (uint32_t i = 0; i < geom.data_count; i++) { auto old_offset = i * old_vertex_size; auto new_offset = i * new_vertex_size; - memcpy(new_data + new_offset, data.vertex_data + old_offset, sizeof(double) * 2); + memcpy(new_data + new_offset, geom.data_ptr + old_offset, sizeof(double) * 2); auto m_offset = old_offset + sizeof(double) * 3; - memcpy(new_data + new_offset + sizeof(double) * 2, data.vertex_data + m_offset, sizeof(double)); + memcpy(new_data + new_offset + sizeof(double) * 2, geom.data_ptr + m_offset, sizeof(double)); } } else { // Otherwise, we just copy the data over - for (uint32_t i = 0; i < data_count; i++) { + for (uint32_t i = 0; i < geom.data_count; i++) { auto old_offset = i * old_vertex_size; auto new_offset = i * new_vertex_size; - memcpy(new_data + new_offset, data.vertex_data + old_offset, new_vertex_size); + memcpy(new_data + new_offset, geom.data_ptr + old_offset, new_vertex_size); } } - data.vertex_data = new_data; + geom.data_ptr = new_data; } } -void SinglePartGeometry::MakeMutable(ArenaAllocator &alloc) { - if (!IsReadOnly()) { +void SinglePartGeometry::MakeMutable(Geometry &geom, ArenaAllocator &alloc) { + if (!geom.is_readonly) { return; } - if (data_count == 0) { - data.vertex_data = nullptr; - is_readonly = false; + + if (geom.data_count == 0) { + geom.data_ptr = nullptr; + geom.is_readonly = false; return; } - auto new_data = alloc.AllocateAligned(ByteSize()); - memcpy(new_data, data.vertex_data, ByteSize()); - data.vertex_data = new_data; - is_readonly = false; + auto data_size = ByteSize(geom); + auto new_data = alloc.AllocateAligned(data_size); + memcpy(new_data, geom.data_ptr, data_size); + geom.data_ptr = new_data; + geom.is_readonly = false; } -bool SinglePartGeometry::IsClosed() const { - switch (Count()) { +bool SinglePartGeometry::IsClosed(const Geometry &geom) { + switch (geom.Count()) { case 0: return false; case 1: return true; default: - auto first = Get(0); - auto last = Get(Count() - 1); + VertexXY first = GetVertex(geom, 0); + VertexXY last = GetVertex(geom, geom.Count() - 1); // TODO: Approximate comparison? return first.x == last.x && first.y == last.y; } } -double SinglePartGeometry::Length() const { +double SinglePartGeometry::Length(const Geometry &geom) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.type)); double length = 0; - for (uint32_t i = 1; i < Count(); i++) { - auto p1 = Get(i - 1); - auto p2 = Get(i); + for (uint32_t i = 1; i < geom.data_count; i++) { + auto p1 = GetVertex(geom, i - 1); + auto p2 = GetVertex(geom, i); length += sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)); } return length; } -string SinglePartGeometry::ToString(uint32_t start, uint32_t count) const { - auto has_z = properties.HasZ(); - auto has_m = properties.HasM(); +string SinglePartGeometry::ToString(const Geometry &geom, uint32_t start, uint32_t count) { + D_ASSERT(GeometryTypes::IsSinglePart(geom.type)); + auto has_z = geom.properties.HasZ(); + auto has_m = geom.properties.HasM(); - D_ASSERT(type == GeometryType::POINT || type == GeometryType::LINESTRING); - auto type_name = type == GeometryType::POINT ? "POINT" : "LINESTRING"; + D_ASSERT(geom.type == GeometryType::POINT || geom.type == GeometryType::LINESTRING); + auto type_name = geom.type == GeometryType::POINT ? "POINT" : "LINESTRING"; if (has_z && has_m) { - string result = StringUtil::Format("%s XYZM ([%d-%d]/%d) [", type_name, start, start + count, data_count); + string result = StringUtil::Format("%s XYZM ([%d-%d]/%d) [", type_name, start, start + count, geom.data_count); for (uint32_t i = start; i < count; i++) { - auto vertex = GetExact(i); - result += StringUtil::Format("(%f, %f, %f, %f)", vertex.x, vertex.y, vertex.z, vertex.m); + auto vertex = GetVertex(geom, i); + result += "(" + MathUtil::format_coord(vertex.x, vertex.y, vertex.z, vertex.m) + ")"; if (i < count - 1) { result += ", "; } @@ -256,10 +234,10 @@ string SinglePartGeometry::ToString(uint32_t start, uint32_t count) const { result += "]"; return result; } else if (has_z) { - string result = StringUtil::Format("%s XYZ ([%d-%d]/%d) [", type_name, start, start + count, data_count); + string result = StringUtil::Format("%s XYZ ([%d-%d]/%d) [", type_name, start, start + count, geom.data_count); for (uint32_t i = start; i < count; i++) { - auto vertex = GetExact(i); - result += StringUtil::Format("(%f, %f, %f)", vertex.x, vertex.y, vertex.z); + auto vertex = GetVertex(geom, i); + result += "(" + MathUtil::format_coord(vertex.x, vertex.y, vertex.z) + ")"; if (i < count - 1) { result += ", "; } @@ -267,10 +245,10 @@ string SinglePartGeometry::ToString(uint32_t start, uint32_t count) const { result += "]"; return result; } else if (has_m) { - string result = StringUtil::Format("%s XYM ([%d-%d]/%d) [", type_name, start, start + count, data_count); + string result = StringUtil::Format("%s XYM ([%d-%d]/%d) [", type_name, start, start + count, geom.data_count); for (uint32_t i = start; i < count; i++) { - auto vertex = GetExact(i); - result += StringUtil::Format("(%f, %f, %f)", vertex.x, vertex.y, vertex.m); + auto vertex = GetVertex(geom, i); + result += "(" + MathUtil::format_coord(vertex.x, vertex.y, vertex.m) + ")"; if (i < count - 1) { result += ", "; } @@ -278,10 +256,10 @@ string SinglePartGeometry::ToString(uint32_t start, uint32_t count) const { result += "]"; return result; } else { - string result = StringUtil::Format("%s XY ([%d-%d]/%d) [", type_name, start, start + count, data_count); + string result = StringUtil::Format("%s XY ([%d-%d]/%d) [", type_name, start, start + count, geom.data_count); for (uint32_t i = start; i < count; i++) { - auto vertex = GetExact(i); - result += StringUtil::Format("(%f, %f)", vertex.x, vertex.y); + auto vertex = GetVertex(geom, i); + result += "(" + MathUtil::format_coord(vertex.x, vertex.y) + ")"; if (i < count - 1) { result += ", "; } @@ -291,22 +269,64 @@ string SinglePartGeometry::ToString(uint32_t start, uint32_t count) const { } } +//------------------------------------------------------------------------------ +// Geometry +//------------------------------------------------------------------------------ +void Geometry::SetVertexType(ArenaAllocator &alloc, bool has_z, bool has_m, double default_z, double default_m) { + struct op { + static void Case(Geometry::Tags::SinglePartGeometry, Geometry &geom, ArenaAllocator &alloc, bool has_z, + bool has_m, double default_z, double default_m) { + SinglePartGeometry::SetVertexType(geom, alloc, has_z, has_m, default_z, default_m); + } + static void Case(Geometry::Tags::MultiPartGeometry, Geometry &geom, ArenaAllocator &alloc, bool has_z, + bool has_m, double default_z, double default_m) { + geom.properties.SetZ(has_z); + geom.properties.SetM(has_m); + for (auto &p : MultiPartGeometry::Parts(geom)) { + p.SetVertexType(alloc, has_z, has_m, default_z, default_m); + } + } + }; + Geometry::Match(*this, alloc, has_z, has_m, default_z, default_m); +} + //------------------------------------------------------------------------------ // Multi Part Geometry //------------------------------------------------------------------------------ +/* +void MultiPartGeometry::Resize(Geometry& geom, ArenaAllocator &alloc, uint32_t new_count) { + D_ASSERT(GeometryTypes::IsMultiPart(geom.type)); + if (new_count == geom.data_count) { + return; + } + if (geom.data_ptr == nullptr) { + geom.data_ptr = alloc.AllocateAligned(sizeof(Geometry) * new_count); + // Need to create a new Geometry for each entry + for (uint32_t i = 0; i < new_count; i++) { + new (geom.data_ptr + i * sizeof(Geometry)) Geometry(); + } + } + else if(geom.IsReadOnly()) { + auto new_data = alloc.AllocateAligned(sizeof(Geometry) * new_count); + for(uint32_t i = 0; i < geom.data_count; i++) { + new (new_data + i * sizeof(Geometry)) Geometry(); + new_data[i] = geom.data_ptr[i]; + } -void MultiPartGeometry::Resize(ArenaAllocator &alloc, uint32_t new_count) { - if (new_count == data_count) { - return; - } - if (data.part_data == nullptr) { - data.part_data = reinterpret_cast(alloc.AllocateAligned(sizeof(Geometry) * new_count)); - } else { - data.part_data = reinterpret_cast(alloc.ReallocateAligned( - data_ptr_cast(data.part_data), data_count * sizeof(Geometry), new_count * sizeof(Geometry))); - } - data_count = new_count; + + geom.data_ptr = new_data; + } + else { + geom.data_ptr = alloc.ReallocateAligned( + geom.data_ptr, geom.data_count * sizeof(Geometry), new_count * sizeof(Geometry)); + // If we added new entries, we need to create a new Geometry for each entry + for (uint32_t i = geom.data_count; i < new_count; i++) { + new (geom.data_ptr + i * sizeof(Geometry)) Geometry(); + } + } + geom.data_count = new_count; } + */ /* string Point::ToString() const { @@ -484,52 +504,6 @@ string GeometryCollection::ToString() const { } */ -//------------------------------------------------------------------------------ -// Util -//------------------------------------------------------------------------------ -// We've got this exposed upstream, we just need to wait for the next release -extern "C" int geos_d2sfixed_buffered_n(double f, uint32_t precision, char *result); - -string Utils::format_coord(double d) { - char buf[25]; - auto len = geos_d2sfixed_buffered_n(d, 15, buf); - buf[len] = '\0'; - return string {buf}; -} - -string Utils::format_coord(double x, double y) { - char buf[51]; - auto res_x = geos_d2sfixed_buffered_n(x, 15, buf); - buf[res_x++] = ' '; - auto res_y = geos_d2sfixed_buffered_n(y, 15, buf + res_x); - buf[res_x + res_y] = '\0'; - return string {buf}; -} - -string Utils::format_coord(double x, double y, double zm) { - char buf[76]; - auto res_x = geos_d2sfixed_buffered_n(x, 15, buf); - buf[res_x++] = ' '; - auto res_y = geos_d2sfixed_buffered_n(y, 15, buf + res_x); - buf[res_x + res_y++] = ' '; - auto res_zm = geos_d2sfixed_buffered_n(zm, 15, buf + res_x + res_y); - buf[res_x + res_y + res_zm] = '\0'; - return string {buf}; -} - -string Utils::format_coord(double x, double y, double z, double m) { - char buf[101]; - auto res_x = geos_d2sfixed_buffered_n(x, 15, buf); - buf[res_x++] = ' '; - auto res_y = geos_d2sfixed_buffered_n(y, 15, buf + res_x); - buf[res_x + res_y++] = ' '; - auto res_z = geos_d2sfixed_buffered_n(z, 15, buf + res_x + res_y); - buf[res_x + res_y + res_z++] = ' '; - auto res_m = geos_d2sfixed_buffered_n(m, 15, buf + res_x + res_y + res_z); - buf[res_x + res_y + res_z + res_m] = '\0'; - return string {buf}; -} - } // namespace core } // namespace spatial diff --git a/spatial/src/spatial/core/geometry/geometry_serialization.cpp b/spatial/src/spatial/core/geometry/geometry_serialization.cpp index dd93a047..44933fe4 100644 --- a/spatial/src/spatial/core/geometry/geometry_serialization.cpp +++ b/spatial/src/spatial/core/geometry/geometry_serialization.cpp @@ -1,7 +1,8 @@ #include "spatial/common.hpp" -#include "spatial/core/geometry/cursor.hpp" +#include "spatial/core/util/cursor.hpp" #include "spatial/core/geometry/geometry.hpp" #include "spatial/core/geometry/geometry_processor.hpp" +#include "spatial/core/util/math.hpp" namespace spatial { @@ -37,14 +38,14 @@ namespace core { template struct GetRequiredSizeOp { - static uint32_t Apply(const SinglePartGeometry &geom) { + static uint32_t Case(Geometry::Tags::SinglePartGeometry, const Geometry &geom) { // 4 bytes for the type // 4 bytes for the length // sizeof(vertex) * count return 4 + 4 + (geom.Count() * sizeof(VERTEX)); } - static uint32_t Apply(const Polygon &polygon) { + static uint32_t Case(Geometry::Tags::Polygon, const Geometry &polygon) { // Polygons are special because they may pad between the rings and the ring data // 4 bytes for the type // 4 bytes for the number of rings @@ -52,35 +53,24 @@ struct GetRequiredSizeOp { // - sizeof(vertex) * count for each ring // (+ 4 bytes for padding if num_rings is odd) uint32_t size = 4 + 4; - for (const auto &ring : polygon) { + for (uint32_t i = 0; i < Polygon::PartCount(polygon); i++) { size += 4; - size += ring.Count() * sizeof(VERTEX); + size += Polygon::Part(polygon, i).Count() * sizeof(VERTEX); } - if (polygon.Count() % 2 == 1) { + if (Polygon::PartCount(polygon) % 2 == 1) { size += 4; } return size; } - template - static uint32_t Apply(const TypedCollectionGeometry &collection) { + static uint32_t Case(Geometry::Tags::CollectionGeometry, const Geometry &collection) { // 4 bytes for the type // 4 bytes for the number of items // recursive call for each item uint32_t size = 4 + 4; - for (const auto &item : collection) { - size += Apply(item); - } - return size; - } - - static uint32_t Apply(const GeometryCollection &collection) { - // 4 bytes for the type - // 4 bytes for the number of geometries - // sizeof(geometry) * count - uint32_t size = 4 + 4; - for (const auto &geom : collection) { - size += geom.Visit>(); + for (uint32_t i = 0; i < CollectionGeometry::PartCount(collection); i++) { + auto &part = CollectionGeometry::Part(collection, i); + size += Geometry::Match>(part); } return size; } @@ -90,23 +80,22 @@ template struct SerializeOp { static constexpr uint32_t MAX_DEPTH = 256; - static void SerializeVertices(const SinglePartGeometry &verts, Cursor &cursor, BoundingBox &bbox, - bool update_bounds) { + static void SerializeVertices(const Geometry &verts, Cursor &cursor, BoundingBox &bbox, bool update_bounds) { // Write the vertex data - auto byte_size = verts.ByteSize(); + auto byte_size = SinglePartGeometry::ByteSize(verts); memcpy(cursor.GetPtr(), verts.GetData(), byte_size); // Move the cursor forward cursor.Skip(byte_size); // Also update the bounds real quick if (update_bounds) { for (uint32_t i = 0; i < verts.Count(); i++) { - auto vertex = verts.GetExact(i); + auto vertex = SinglePartGeometry::GetVertex(verts, i); bbox.Stretch(vertex); } } } - static void Apply(const Point &point, Cursor &cursor, BoundingBox &bbox, uint32_t depth) { + static void Case(Geometry::Tags::Point, const Geometry &point, Cursor &cursor, BoundingBox &bbox, uint32_t depth) { D_ASSERT(point.GetProperties().HasZ() == VERTEX::HAS_Z); D_ASSERT(point.GetProperties().HasM() == VERTEX::HAS_M); @@ -121,7 +110,8 @@ struct SerializeOp { SerializeVertices(point, cursor, bbox, depth != 0); } - static void Apply(const LineString &linestring, Cursor &cursor, BoundingBox &bbox, uint32_t) { + static void Case(Geometry::Tags::LineString, const Geometry &linestring, Cursor &cursor, BoundingBox &bbox, + uint32_t) { D_ASSERT(linestring.GetProperties().HasZ() == VERTEX::HAS_Z); D_ASSERT(linestring.GetProperties().HasM() == VERTEX::HAS_M); @@ -135,7 +125,7 @@ struct SerializeOp { SerializeVertices(linestring, cursor, bbox, true); } - static void Apply(const Polygon &polygon, Cursor &cursor, BoundingBox &bbox, uint32_t) { + static void Case(Geometry::Tags::Polygon, const Geometry &polygon, Cursor &cursor, BoundingBox &bbox, uint32_t) { D_ASSERT(polygon.GetProperties().HasZ() == VERTEX::HAS_Z); D_ASSERT(polygon.GetProperties().HasM() == VERTEX::HAS_M); @@ -146,8 +136,8 @@ struct SerializeOp { cursor.Write(polygon.Count()); // Write ring lengths - for (const auto &ring : polygon) { - cursor.Write(ring.Count()); + for (uint32_t i = 0; i < Polygon::PartCount(polygon); i++) { + cursor.Write(Polygon::Part(polygon, i).Count()); } if (polygon.Count() % 2 == 1) { @@ -159,11 +149,12 @@ struct SerializeOp { for (uint32_t i = 0; i < polygon.Count(); i++) { // The first ring is always the shell, and must be the only ring contributing to the bounding box // or the geometry is invalid. - SerializeVertices(polygon[i], cursor, bbox, i == 0); + SerializeVertices(Polygon::Part(polygon, i), cursor, bbox, i == 0); } } - static void Apply(const MultiPoint &multipoint, Cursor &cursor, BoundingBox &bbox, uint32_t depth) { + static void Case(Geometry::Tags::MultiPoint, const Geometry &multipoint, Cursor &cursor, BoundingBox &bbox, + uint32_t depth) { D_ASSERT(multipoint.GetProperties().HasZ() == VERTEX::HAS_Z); D_ASSERT(multipoint.GetProperties().HasM() == VERTEX::HAS_M); @@ -174,12 +165,13 @@ struct SerializeOp { cursor.Write(multipoint.Count()); // Write point data - for (const auto &point : multipoint) { - Apply(point, cursor, bbox, depth + 1); + for (uint32_t i = 0; i < MultiPoint::PartCount(multipoint); i++) { + Case(Geometry::Tags::Point {}, MultiPoint::Part(multipoint, i), cursor, bbox, depth + 1); } } - static void Apply(const MultiLineString &multilinestring, Cursor &cursor, BoundingBox &bbox, uint32_t depth) { + static void Case(Geometry::Tags::MultiLineString, const Geometry &multilinestring, Cursor &cursor, + BoundingBox &bbox, uint32_t depth) { D_ASSERT(multilinestring.GetProperties().HasZ() == VERTEX::HAS_Z); D_ASSERT(multilinestring.GetProperties().HasM() == VERTEX::HAS_M); @@ -190,12 +182,13 @@ struct SerializeOp { cursor.Write(multilinestring.Count()); // Write linestring data - for (const auto &linestring : multilinestring) { - Apply(linestring, cursor, bbox, depth + 1); + for (uint32_t i = 0; i < MultiLineString::PartCount(multilinestring); i++) { + Case(Geometry::Tags::LineString {}, MultiLineString::Part(multilinestring, i), cursor, bbox, depth + 1); } } - static void Apply(const MultiPolygon &multipolygon, Cursor &cursor, BoundingBox &bbox, uint32_t depth) { + static void Case(Geometry::Tags::MultiPolygon, const Geometry &multipolygon, Cursor &cursor, BoundingBox &bbox, + uint32_t depth) { D_ASSERT(multipolygon.GetProperties().HasZ() == VERTEX::HAS_Z); D_ASSERT(multipolygon.GetProperties().HasM() == VERTEX::HAS_M); @@ -206,12 +199,13 @@ struct SerializeOp { cursor.Write(multipolygon.Count()); // Write polygon data - for (const auto &polygon : multipolygon) { - Apply(polygon, cursor, bbox, depth + 1); + for (uint32_t i = 0; i < MultiPolygon::PartCount(multipolygon); i++) { + Case(Geometry::Tags::Polygon {}, MultiPolygon::Part(multipolygon, i), cursor, bbox, depth + 1); } } - static void Apply(const GeometryCollection &collection, Cursor &cursor, BoundingBox &bbox, uint32_t depth) { + static void Case(Geometry::Tags::GeometryCollection, const Geometry &collection, Cursor &cursor, BoundingBox &bbox, + uint32_t depth) { D_ASSERT(collection.GetProperties().HasZ() == VERTEX::HAS_Z); D_ASSERT(collection.GetProperties().HasM() == VERTEX::HAS_M); @@ -227,30 +221,31 @@ struct SerializeOp { cursor.Write(collection.Count()); // write geometry data - for (const auto &geom : collection) { - geom.Visit>(cursor, bbox, depth + 1); + for (uint32_t i = 0; i < GeometryCollection::PartCount(collection); i++) { + auto &geom = GeometryCollection::Part(collection, i); + Geometry::Match>(geom, cursor, bbox, depth + 1); } } }; -geometry_t Geometry::Serialize(Vector &result) { - auto type = GetType(); - bool has_bbox = type != GeometryType::POINT && !IsEmpty(); +geometry_t Geometry::Serialize(const Geometry &geom, Vector &result) { + auto type = geom.GetType(); + bool has_bbox = type != GeometryType::POINT && !Geometry::IsEmpty(geom); - auto properties = GetProperties(); + auto properties = geom.GetProperties(); auto has_z = properties.HasZ(); auto has_m = properties.HasM(); properties.SetBBox(has_bbox); uint32_t geom_size = 0; if (has_z && has_m) { - geom_size = Visit>(); + geom_size = Geometry::Match>(geom); } else if (has_z) { - geom_size = Visit>(); + geom_size = Geometry::Match>(geom); } else if (has_m) { - geom_size = Visit>(); + geom_size = Geometry::Match>(geom); } else { - geom_size = Visit>(); + geom_size = Geometry::Match>(geom); } auto header_size = 4; @@ -277,13 +272,13 @@ geometry_t Geometry::Serialize(Vector &result) { cursor.Skip(bbox_size); if (has_z && has_m) { - Visit>(cursor, bbox, 0); + Geometry::Match>(geom, cursor, bbox, 0); } else if (has_z) { - Visit>(cursor, bbox, 0); + Geometry::Match>(geom, cursor, bbox, 0); } else if (has_m) { - Visit>(cursor, bbox, 0); + Geometry::Match>(geom, cursor, bbox, 0); } else { - Visit>(cursor, bbox, 0); + Geometry::Match>(geom, cursor, bbox, 0); } // Now write the bounding box @@ -291,17 +286,17 @@ geometry_t Geometry::Serialize(Vector &result) { cursor.SetPtr(bbox_ptr); // We serialize the bounding box as floats to save space, but ensure that the bounding box is // still large enough to contain the original double values by rounding up and down - cursor.Write(Utils::DoubleToFloatDown(bbox.minx)); - cursor.Write(Utils::DoubleToFloatDown(bbox.miny)); - cursor.Write(Utils::DoubleToFloatUp(bbox.maxx)); - cursor.Write(Utils::DoubleToFloatUp(bbox.maxy)); + cursor.Write(MathUtil::DoubleToFloatDown(bbox.minx)); + cursor.Write(MathUtil::DoubleToFloatDown(bbox.miny)); + cursor.Write(MathUtil::DoubleToFloatUp(bbox.maxx)); + cursor.Write(MathUtil::DoubleToFloatUp(bbox.maxy)); if (has_z) { - cursor.Write(Utils::DoubleToFloatDown(bbox.minz)); - cursor.Write(Utils::DoubleToFloatUp(bbox.maxz)); + cursor.Write(MathUtil::DoubleToFloatDown(bbox.minz)); + cursor.Write(MathUtil::DoubleToFloatUp(bbox.maxz)); } if (has_m) { - cursor.Write(Utils::DoubleToFloatDown(bbox.minm)); - cursor.Write(Utils::DoubleToFloatUp(bbox.maxm)); + cursor.Write(MathUtil::DoubleToFloatDown(bbox.minm)); + cursor.Write(MathUtil::DoubleToFloatUp(bbox.maxm)); } } blob.Finalize(); @@ -315,9 +310,9 @@ class GeometryDeserializer final : GeometryProcessor { ArenaAllocator &allocator; Geometry ProcessPoint(const VertexData &vertices) override { - auto point = Point::Empty(HasZ(), HasM()); + auto point = Point::CreateEmpty(HasZ(), HasM()); if (!vertices.IsEmpty()) { - point.ReferenceData(vertices.data[0], vertices.count); + Point::ReferenceData(point, vertices.data[0], vertices.count); } return point; } @@ -325,7 +320,7 @@ class GeometryDeserializer final : GeometryProcessor { Geometry ProcessLineString(const VertexData &vertices) override { auto line_string = LineString::Create(allocator, vertices.count, HasZ(), HasM()); if (!vertices.IsEmpty()) { - line_string.ReferenceData(vertices.data[0], vertices.count); + LineString::ReferenceData(line_string, vertices.data[0], vertices.count); } return line_string; } @@ -335,7 +330,8 @@ class GeometryDeserializer final : GeometryProcessor { for (auto i = 0; i < state.RingCount(); i++) { auto vertices = state.Next(); if (!vertices.IsEmpty()) { - polygon[i].ReferenceData(vertices.data[0], vertices.count); + auto &part = Polygon::Part(polygon, i); + LineString::ReferenceData(part, vertices.data[0], vertices.count); } } return polygon; @@ -346,28 +342,28 @@ class GeometryDeserializer final : GeometryProcessor { case GeometryType::MULTIPOINT: { auto multi_point = MultiPoint::Create(allocator, state.ItemCount(), HasZ(), HasM()); for (auto i = 0; i < state.ItemCount(); i++) { - multi_point[i] = state.Next().As(); + MultiPoint::Part(multi_point, i) = state.Next(); } return multi_point; } case GeometryType::MULTILINESTRING: { auto multi_line_string = MultiLineString::Create(allocator, state.ItemCount(), HasZ(), HasM()); for (auto i = 0; i < state.ItemCount(); i++) { - multi_line_string[i] = state.Next().As(); + MultiLineString::Part(multi_line_string, i) = state.Next(); } return multi_line_string; } case GeometryType::MULTIPOLYGON: { auto multi_polygon = MultiPolygon::Create(allocator, state.ItemCount(), HasZ(), HasM()); for (auto i = 0; i < state.ItemCount(); i++) { - multi_polygon[i] = state.Next().As(); + MultiPolygon::Part(multi_polygon, i) = state.Next(); } return multi_polygon; } case GeometryType::GEOMETRYCOLLECTION: { auto collection = GeometryCollection::Create(allocator, state.ItemCount(), HasZ(), HasM()); for (auto i = 0; i < state.ItemCount(); i++) { - collection[i] = state.Next(); + GeometryCollection::Part(collection, i) = state.Next(); } return collection; } diff --git a/spatial/src/spatial/core/geometry/wkb_reader.cpp b/spatial/src/spatial/core/geometry/wkb_reader.cpp index b7aea485..713646fb 100644 --- a/spatial/src/spatial/core/geometry/wkb_reader.cpp +++ b/spatial/src/spatial/core/geometry/wkb_reader.cpp @@ -68,7 +68,7 @@ WKBReader::WKBType WKBReader::ReadType(Cursor &cursor, bool little_endian) { return {geometry_type, has_z, has_m}; } -Point WKBReader::ReadPoint(Cursor &cursor, bool little_endian, bool has_z, bool has_m) { +Geometry WKBReader::ReadPoint(Cursor &cursor, bool little_endian, bool has_z, bool has_m) { uint32_t dims = 2 + has_z + has_m; bool all_nan = true; double coords[4]; @@ -79,94 +79,96 @@ Point WKBReader::ReadPoint(Cursor &cursor, bool little_endian, bool has_z, bool } } if (all_nan) { - return Point::Empty(has_z, has_m); + return Point::CreateEmpty(has_z, has_m); } else { - return Point::CopyFromData(arena, data_ptr_cast(coords), 1, has_z, has_m); + return Point::CreateFromCopy(arena, data_ptr_cast(coords), 1, has_z, has_m); } } -void WKBReader::ReadVertices(Cursor &cursor, bool little_endian, bool has_z, bool has_m, SinglePartGeometry &geometry) { +void WKBReader::ReadVertices(Cursor &cursor, bool little_endian, bool has_z, bool has_m, Geometry &geometry) { for (uint32_t i = 0; i < geometry.Count(); i++) { if (has_z && has_m) { auto x = ReadDouble(cursor, little_endian); auto y = ReadDouble(cursor, little_endian); auto z = ReadDouble(cursor, little_endian); auto m = ReadDouble(cursor, little_endian); - geometry.SetExact(i, VertexXYZM {x, y, z, m}); + SinglePartGeometry::SetVertex(geometry, i, VertexXYZM {x, y, z, m}); } else if (has_z) { auto x = ReadDouble(cursor, little_endian); auto y = ReadDouble(cursor, little_endian); auto z = ReadDouble(cursor, little_endian); - geometry.SetExact(i, VertexXYZ {x, y, z}); + SinglePartGeometry::SetVertex(geometry, i, VertexXYZ {x, y, z}); } else if (has_m) { auto x = ReadDouble(cursor, little_endian); auto y = ReadDouble(cursor, little_endian); auto m = ReadDouble(cursor, little_endian); - geometry.SetExact(i, VertexXYM {x, y, m}); + SinglePartGeometry::SetVertex(geometry, i, VertexXYM {x, y, m}); } else { auto x = ReadDouble(cursor, little_endian); auto y = ReadDouble(cursor, little_endian); - geometry.SetExact(i, VertexXY {x, y}); + SinglePartGeometry::SetVertex(geometry, i, VertexXY {x, y}); } } } -LineString WKBReader::ReadLineString(Cursor &cursor, bool little_endian, bool has_z, bool has_m) { +Geometry WKBReader::ReadLineString(Cursor &cursor, bool little_endian, bool has_z, bool has_m) { auto count = ReadInt(cursor, little_endian); auto vertices = LineString::Create(arena, count, has_z, has_m); ReadVertices(cursor, little_endian, has_z, has_m, vertices); return vertices; } -Polygon WKBReader::ReadPolygon(Cursor &cursor, bool little_endian, bool has_z, bool has_m) { +Geometry WKBReader::ReadPolygon(Cursor &cursor, bool little_endian, bool has_z, bool has_m) { auto ring_count = ReadInt(cursor, little_endian); auto polygon = Polygon::Create(arena, ring_count, has_z, has_m); for (uint32_t i = 0; i < ring_count; i++) { auto point_count = ReadInt(cursor, little_endian); - polygon[i].Resize(arena, point_count); - ReadVertices(cursor, little_endian, has_z, has_m, polygon[i]); + Polygon::Part(polygon, i) = LineString::Create(arena, point_count, has_z, has_m); + ReadVertices(cursor, little_endian, has_z, has_m, Polygon::Part(polygon, i)); } return polygon; } -MultiPoint WKBReader::ReadMultiPoint(Cursor &cursor, bool little_endian) { +Geometry WKBReader::ReadMultiPoint(Cursor &cursor, bool little_endian, bool has_z, bool has_m) { uint32_t count = ReadInt(cursor, little_endian); - auto multi_point = MultiPoint::Create(arena, count, false, false); + auto multi_point = MultiPoint::Create(arena, count, has_z, has_m); for (uint32_t i = 0; i < count; i++) { bool point_order = cursor.Read(); auto point_type = ReadType(cursor, point_order); - multi_point[i] = ReadPoint(cursor, point_order, point_type.has_z, point_type.has_m); + MultiPoint::Part(multi_point, i) = ReadPoint(cursor, point_order, point_type.has_z, point_type.has_m); } return multi_point; } -MultiLineString WKBReader::ReadMultiLineString(Cursor &cursor, bool little_endian) { +Geometry WKBReader::ReadMultiLineString(Cursor &cursor, bool little_endian, bool has_z, bool has_m) { uint32_t count = ReadInt(cursor, little_endian); - auto multi_line_string = MultiLineString::Create(arena, count, false, false); + auto multi_line_string = MultiLineString::Create(arena, count, has_z, has_m); for (uint32_t i = 0; i < count; i++) { bool line_order = cursor.Read(); auto line_type = ReadType(cursor, line_order); - multi_line_string[i] = ReadLineString(cursor, line_order, line_type.has_z, line_type.has_m); + MultiLineString::Part(multi_line_string, i) = + ReadLineString(cursor, line_order, line_type.has_z, line_type.has_m); } return multi_line_string; } -MultiPolygon WKBReader::ReadMultiPolygon(Cursor &cursor, bool little_endian) { +Geometry WKBReader::ReadMultiPolygon(Cursor &cursor, bool little_endian, bool has_z, bool has_m) { uint32_t count = ReadInt(cursor, little_endian); - auto multi_polygon = MultiPolygon::Create(arena, count, false, false); + auto multi_polygon = MultiPolygon::Create(arena, count, has_z, has_m); for (uint32_t i = 0; i < count; i++) { bool polygon_order = cursor.Read(); auto polygon_type = ReadType(cursor, polygon_order); - multi_polygon[i] = ReadPolygon(cursor, polygon_order, polygon_type.has_z, polygon_type.has_m); + MultiPolygon::Part(multi_polygon, i) = + ReadPolygon(cursor, polygon_order, polygon_type.has_z, polygon_type.has_m); } return multi_polygon; } -GeometryCollection WKBReader::ReadGeometryCollection(Cursor &cursor, bool byte_order) { - uint32_t count = ReadInt(cursor, byte_order); - auto geometry_collection = GeometryCollection::Create(arena, count, false, false); +Geometry WKBReader::ReadGeometryCollection(Cursor &cursor, bool little_endian, bool has_z, bool has_m) { + uint32_t count = ReadInt(cursor, little_endian); + auto geometry_collection = GeometryCollection::Create(arena, count, has_z, has_m); for (uint32_t i = 0; i < count; i++) { - geometry_collection[i] = ReadGeometry(cursor); + GeometryCollection::Part(geometry_collection, i) = ReadGeometry(cursor); } return geometry_collection; } @@ -182,13 +184,13 @@ Geometry WKBReader::ReadGeometry(Cursor &cursor) { case GeometryType::POLYGON: return ReadPolygon(cursor, little_endian, type.has_z, type.has_m); case GeometryType::MULTIPOINT: - return ReadMultiPoint(cursor, little_endian); + return ReadMultiPoint(cursor, little_endian, type.has_z, type.has_m); case GeometryType::MULTILINESTRING: - return ReadMultiLineString(cursor, little_endian); + return ReadMultiLineString(cursor, little_endian, type.has_z, type.has_m); case GeometryType::MULTIPOLYGON: - return ReadMultiPolygon(cursor, little_endian); + return ReadMultiPolygon(cursor, little_endian, type.has_z, type.has_m); case GeometryType::GEOMETRYCOLLECTION: - return ReadGeometryCollection(cursor, little_endian); + return ReadGeometryCollection(cursor, little_endian, type.has_z, type.has_m); default: throw NotImplementedException("WKB Reader: Geometry type %u not supported", type.type); } diff --git a/spatial/src/spatial/core/geometry/wkt_reader.cpp b/spatial/src/spatial/core/geometry/wkt_reader.cpp index 99740969..d1d25fd2 100644 --- a/spatial/src/spatial/core/geometry/wkt_reader.cpp +++ b/spatial/src/spatial/core/geometry/wkt_reader.cpp @@ -125,25 +125,25 @@ pair> WKTReader::ParseVertices() { return {count, coords}; } -Point WKTReader::ParsePoint() { +Geometry WKTReader::ParsePoint() { if (MatchCI("EMPTY")) { - return Point::Empty(has_z, has_m); + return Point::CreateEmpty(has_z, has_m); } Expect('('); vector coords; ParseVertex(coords); Expect(')'); - return Point::CopyFromData(arena, data_ptr_cast(coords.data()), 1, has_z, has_m); + return Point::CreateFromCopy(arena, data_ptr_cast(coords.data()), 1, has_z, has_m); } -LineString WKTReader::ParseLineString() { +Geometry WKTReader::ParseLineString() { auto verts = ParseVertices(); - return LineString::CopyFromData(arena, data_ptr_cast(verts.second.data()), verts.first, has_z, has_m); + return LineString::CreateFromCopy(arena, data_ptr_cast(verts.second.data()), verts.first, has_z, has_m); } -Polygon WKTReader::ParsePolygon() { +Geometry WKTReader::ParsePolygon() { if (MatchCI("EMPTY")) { - return Polygon::Empty(has_z, has_m); + return Polygon::CreateEmpty(has_z, has_m); } Expect('('); vector>> rings; @@ -154,19 +154,20 @@ Polygon WKTReader::ParsePolygon() { Expect(')'); auto result = Polygon::Create(arena, rings.size(), has_z, has_m); for (uint32_t i = 0; i < rings.size(); i++) { - result[i].CopyData(arena, data_ptr_cast(rings[i].second.data()), rings[i].first); + auto &ring = Polygon::Part(result, i); + LineString::CopyData(ring, arena, data_ptr_cast(rings[i].second.data()), rings[i].first); } return result; } -MultiPoint WKTReader::ParseMultiPoint() { +Geometry WKTReader::ParseMultiPoint() { if (MatchCI("EMPTY")) { - return MultiPoint::Empty(has_z, has_m); + return MultiPoint::CreateEmpty(has_z, has_m); } // Multipoints are special in that parens around each point is optional. Expect('('); vector coords; - vector points; + vector points; bool optional_paren = false; if (Match('(')) { @@ -177,7 +178,7 @@ MultiPoint WKTReader::ParseMultiPoint() { Expect(')'); optional_paren = false; } - points.push_back(Point::CopyFromData(arena, data_ptr_cast(coords.data()), 1, has_z, has_m)); + points.push_back(Point::CreateFromCopy(arena, data_ptr_cast(coords.data()), 1, has_z, has_m)); coords.clear(); while (Match(',')) { if (Match('(')) { @@ -188,23 +189,23 @@ MultiPoint WKTReader::ParseMultiPoint() { Expect(')'); optional_paren = false; } - points.push_back(Point::CopyFromData(arena, data_ptr_cast(coords.data()), 1, has_z, has_m)); + points.push_back(Point::CreateFromCopy(arena, data_ptr_cast(coords.data()), 1, has_z, has_m)); coords.clear(); } Expect(')'); auto result = MultiPoint::Create(arena, points.size(), has_z, has_m); for (uint32_t i = 0; i < points.size(); i++) { - result[i] = points[i]; + MultiPoint::Part(result, i) = points[i]; } return result; } -MultiLineString WKTReader::ParseMultiLineString() { +Geometry WKTReader::ParseMultiLineString() { if (MatchCI("EMPTY")) { - return MultiLineString::Empty(has_z, has_m); + return MultiLineString::CreateEmpty(has_z, has_m); } Expect('('); - vector lines; + vector lines; lines.push_back(ParseLineString()); while (Match(',')) { lines.push_back(ParseLineString()); @@ -212,17 +213,17 @@ MultiLineString WKTReader::ParseMultiLineString() { Expect(')'); auto result = MultiLineString::Create(arena, lines.size(), has_z, has_m); for (uint32_t i = 0; i < lines.size(); i++) { - result[i] = lines[i]; + MultiLineString::Part(result, i) = lines[i]; } return result; } -MultiPolygon WKTReader::ParseMultiPolygon() { +Geometry WKTReader::ParseMultiPolygon() { if (MatchCI("EMPTY")) { - return MultiPolygon::Empty(has_z, has_m); + return MultiPolygon::CreateEmpty(has_z, has_m); } Expect('('); - vector polygons; + vector polygons; polygons.push_back(ParsePolygon()); while (Match(',')) { polygons.push_back(ParsePolygon()); @@ -230,14 +231,14 @@ MultiPolygon WKTReader::ParseMultiPolygon() { Expect(')'); auto result = MultiPolygon::Create(arena, polygons.size(), has_z, has_m); for (uint32_t i = 0; i < polygons.size(); i++) { - result[i] = polygons[i]; + MultiPolygon::Part(result, i) = polygons[i]; } return result; } -GeometryCollection WKTReader::ParseGeometryCollection() { +Geometry WKTReader::ParseGeometryCollection() { if (MatchCI("EMPTY")) { - return GeometryCollection::Empty(has_z, has_m); + return GeometryCollection::CreateEmpty(has_z, has_m); } Expect('('); vector geometries; @@ -248,7 +249,7 @@ GeometryCollection WKTReader::ParseGeometryCollection() { Expect(')'); auto result = GeometryCollection::Create(arena, geometries.size(), has_z, has_m); for (uint32_t i = 0; i < geometries.size(); i++) { - result[i] = geometries[i]; + GeometryCollection::Part(result, i) = geometries[i]; } return result; } diff --git a/spatial/src/spatial/core/io/osm/st_read_osm.cpp b/spatial/src/spatial/core/io/osm/st_read_osm.cpp index 11055c9c..72b3b713 100644 --- a/spatial/src/spatial/core/io/osm/st_read_osm.cpp +++ b/spatial/src/spatial/core/io/osm/st_read_osm.cpp @@ -839,8 +839,9 @@ static idx_t GetBatchIndex(ClientContext &context, const FunctionData *bind_data return state.block->block_idx; } -static unique_ptr ReadOsmPBFReplacementScan(ClientContext &context, const string &table_name, - ReplacementScanData *data) { +static unique_ptr ReadOsmPBFReplacementScan(ClientContext &context, ReplacementScanInput &input, + optional_ptr data) { + auto &table_name = input.table_name; // Check if the table name ends with .osm.pbf if (!StringUtil::EndsWith(StringUtil::Lower(table_name), ".osm.pbf")) { return nullptr; diff --git a/spatial/src/spatial/core/io/shapefile/read_shapefile.cpp b/spatial/src/spatial/core/io/shapefile/read_shapefile.cpp index fcc182ea..9a15e4c1 100644 --- a/spatial/src/spatial/core/io/shapefile/read_shapefile.cpp +++ b/spatial/src/spatial/core/io/shapefile/read_shapefile.cpp @@ -208,7 +208,7 @@ static unique_ptr InitGlobal(ClientContext &context, T struct ConvertPoint { static Geometry Convert(SHPObjectPtr &shape, ArenaAllocator &arena) { - return Point::FromVertex(arena, VertexXY {shape->padfX[0], shape->padfY[0]}); + return Point::CreateFromVertex(arena, VertexXY {shape->padfX[0], shape->padfY[0]}); } }; @@ -218,7 +218,7 @@ struct ConvertLineString { // Single LineString auto line = LineString::Create(arena, shape->nVertices, false, false); for (int i = 0; i < shape->nVertices; i++) { - line.SetExact(i, VertexXY {shape->padfX[i], shape->padfY[i]}); + LineString::SetVertex(line, i, {shape->padfX[i], shape->padfY[i]}); } return line; } else { @@ -228,11 +228,11 @@ struct ConvertLineString { for (int i = 0; i < shape->nParts; i++) { auto end = i == shape->nParts - 1 ? shape->nVertices : shape->panPartStart[i + 1]; auto line_size = end - start; - auto &line = multi_line_string[i]; - line.Resize(arena, line_size); + auto &line = MultiLineString::Part(multi_line_string, i); + LineString::Resize(line, arena, line_size); for (int j = 0; j < line_size; j++) { auto offset = start + j; - line.SetExact(j, VertexXY {shape->padfX[offset], shape->padfY[offset]}); + LineString::SetVertex(line, j, {shape->padfX[offset], shape->padfY[offset]}); } start = end; } @@ -266,12 +266,12 @@ struct ConvertPolygon { auto start = shape->panPartStart[0]; for (int i = 0; i < shape->nParts; i++) { auto end = i == shape->nParts - 1 ? shape->nVertices : shape->panPartStart[i + 1]; - auto &ring = polygon[i]; + auto &ring = Polygon::Part(polygon, i); auto ring_size = end - start; - ring.Resize(arena, ring_size); + LineString::Resize(ring, arena, ring_size); for (int j = 0; j < ring_size; j++) { auto offset = start + j; - ring.Set(j, shape->padfX[offset], shape->padfY[offset]); + LineString::SetVertex(ring, j, {shape->padfX[offset], shape->padfY[offset]}); } start = end; } @@ -289,15 +289,15 @@ struct ConvertPolygon { for (auto ring_idx = part_start; ring_idx < part_end; ring_idx++) { auto start = shape->panPartStart[ring_idx]; auto end = ring_idx == shape->nParts - 1 ? shape->nVertices : shape->panPartStart[ring_idx + 1]; - auto &ring = polygon[ring_idx - part_start]; + auto &ring = Polygon::Part(polygon, ring_idx - part_start); auto ring_size = end - start; - ring.Resize(arena, ring_size); + LineString::Resize(ring, arena, ring_size); for (int j = 0; j < ring_size; j++) { auto offset = start + j; - ring.SetExact(j, VertexXY {shape->padfX[offset], shape->padfY[offset]}); + LineString::SetVertex(ring, j, {shape->padfX[offset], shape->padfY[offset]}); } } - multi_polygon[polygon_idx] = polygon; + MultiPolygon::Part(multi_polygon, polygon_idx) = std::move(polygon); } return multi_polygon; } @@ -308,7 +308,8 @@ struct ConvertMultiPoint { static Geometry Convert(SHPObjectPtr &shape, ArenaAllocator &arena) { auto multi_point = MultiPoint::Create(arena, shape->nVertices, false, false); for (int i = 0; i < shape->nVertices; i++) { - multi_point[i] = Point::FromVertex(arena, VertexXY {shape->padfX[i], shape->padfY[i]}); + auto point = Point::CreateFromVertex(arena, VertexXY {shape->padfX[i], shape->padfY[i]}); + MultiPoint::Part(multi_point, i) = std::move(point); } return multi_point; } @@ -323,7 +324,7 @@ static void ConvertGeomLoop(Vector &result, int record_start, idx_t count, SHPHa FlatVector::SetNull(result, result_idx, true); } else { // TODO: Handle Z and M - FlatVector::GetData(result)[result_idx] = Geometry(OP::Convert(shape, arena)).Serialize(result); + FlatVector::GetData(result)[result_idx] = Geometry::Serialize(OP::Convert(shape, arena), result); } } } @@ -540,8 +541,9 @@ static unique_ptr GetCardinality(ClientContext &context, const F return result; } -static unique_ptr GetReplacementScan(ClientContext &context, const string &table_name, - ReplacementScanData *data) { +static unique_ptr GetReplacementScan(ClientContext &context, ReplacementScanInput &input, + optional_ptr data) { + auto &table_name = input.table_name; // Check if the table name ends with .shp if (!StringUtil::EndsWith(StringUtil::Lower(table_name), ".shp")) { return nullptr; diff --git a/spatial/src/spatial/core/io/shapefile/read_shapefile_meta.cpp b/spatial/src/spatial/core/io/shapefile/read_shapefile_meta.cpp index da3fe1ee..d42342a9 100644 --- a/spatial/src/spatial/core/io/shapefile/read_shapefile_meta.cpp +++ b/spatial/src/spatial/core/io/shapefile/read_shapefile_meta.cpp @@ -47,8 +47,11 @@ static ShapeTypeEntry shape_type_map[] = { static unique_ptr ShapeFileMetaBind(ClientContext &context, TableFunctionBindInput &input, vector &return_types, vector &names) { auto result = make_uniq(); - auto files = MultiFileReader::GetFileList(context, input.inputs[0], "ShapeFiles", FileGlobOptions::ALLOW_EMPTY); - for (auto &file : files) { + + auto multi_file_reader = MultiFileReader::Create(input.table_function); + auto file_list = multi_file_reader->CreateFileList(context, input.inputs[0], FileGlobOptions::ALLOW_EMPTY); + + for (auto &file : file_list->Files()) { if (StringUtil::EndsWith(StringUtil::Lower(file), ".shp")) { result->files.push_back(file); } diff --git a/spatial/src/spatial/core/io/shapefile/shapefile_common.cpp b/spatial/src/spatial/core/io/shapefile/shapefile_common.cpp index 00db00bc..75676992 100644 --- a/spatial/src/spatial/core/io/shapefile/shapefile_common.cpp +++ b/spatial/src/spatial/core/io/shapefile/shapefile_common.cpp @@ -18,7 +18,7 @@ namespace core { static SAFile DuckDBShapefileOpen(void *userData, const char *filename, const char *access_mode) { try { auto &fs = *reinterpret_cast(userData); - auto file_handle = fs.OpenFile(filename, FileFlags::FILE_FLAGS_READ); + auto file_handle = fs.OpenFile(filename, FileFlags::FILE_FLAGS_READ | FileFlags::FILE_FLAGS_NULL_IF_NOT_EXISTS); if (!file_handle) { return nullptr; } @@ -87,7 +87,10 @@ static int DuckDBShapefileClose(SAFile file) { static int DuckDBShapefileRemove(void *userData, const char *filename) { try { auto &fs = *reinterpret_cast(userData); - auto file = fs.OpenFile(filename, FileFlags::FILE_FLAGS_WRITE); + auto file = fs.OpenFile(filename, FileFlags::FILE_FLAGS_WRITE | FileFlags::FILE_FLAGS_NULL_IF_NOT_EXISTS); + if (!file) { + return -1; + } auto file_type = fs.GetFileType(*file); if (file_type == FileType::FILE_TYPE_DIR) { fs.RemoveDirectory(filename); diff --git a/spatial/src/spatial/core/util/CMakeLists.txt b/spatial/src/spatial/core/util/CMakeLists.txt new file mode 100644 index 00000000..fcbaa0c3 --- /dev/null +++ b/spatial/src/spatial/core/util/CMakeLists.txt @@ -0,0 +1,5 @@ +set(EXTENSION_SOURCES + ${EXTENSION_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/math.cpp + PARENT_SCOPE +) \ No newline at end of file diff --git a/spatial/src/spatial/core/util/math.cpp b/spatial/src/spatial/core/util/math.cpp new file mode 100644 index 00000000..2f7cf705 --- /dev/null +++ b/spatial/src/spatial/core/util/math.cpp @@ -0,0 +1,52 @@ +#include "spatial/core/util/math.hpp" + +namespace spatial { + +namespace core { + +// We've got this exposed upstream, we just need to wait for the next release +extern "C" int geos_d2sfixed_buffered_n(double f, uint32_t precision, char *result); + +string MathUtil::format_coord(double d) { + char buf[25]; + auto len = geos_d2sfixed_buffered_n(d, 15, buf); + buf[len] = '\0'; + return string {buf}; +} + +string MathUtil::format_coord(double x, double y) { + char buf[51]; + auto res_x = geos_d2sfixed_buffered_n(x, 15, buf); + buf[res_x++] = ' '; + auto res_y = geos_d2sfixed_buffered_n(y, 15, buf + res_x); + buf[res_x + res_y] = '\0'; + return string {buf}; +} + +string MathUtil::format_coord(double x, double y, double zm) { + char buf[76]; + auto res_x = geos_d2sfixed_buffered_n(x, 15, buf); + buf[res_x++] = ' '; + auto res_y = geos_d2sfixed_buffered_n(y, 15, buf + res_x); + buf[res_x + res_y++] = ' '; + auto res_zm = geos_d2sfixed_buffered_n(zm, 15, buf + res_x + res_y); + buf[res_x + res_y + res_zm] = '\0'; + return string {buf}; +} + +string MathUtil::format_coord(double x, double y, double z, double m) { + char buf[101]; + auto res_x = geos_d2sfixed_buffered_n(x, 15, buf); + buf[res_x++] = ' '; + auto res_y = geos_d2sfixed_buffered_n(y, 15, buf + res_x); + buf[res_x + res_y++] = ' '; + auto res_z = geos_d2sfixed_buffered_n(z, 15, buf + res_x + res_y); + buf[res_x + res_y + res_z++] = ' '; + auto res_m = geos_d2sfixed_buffered_n(m, 15, buf + res_x + res_y + res_z); + buf[res_x + res_y + res_z + res_m] = '\0'; + return string {buf}; +} + +} // namespace core + +} // namespace spatial \ No newline at end of file diff --git a/spatial/src/spatial/gdal/file_handler.cpp b/spatial/src/spatial/gdal/file_handler.cpp index faa87029..cb50144d 100644 --- a/spatial/src/spatial/gdal/file_handler.cpp +++ b/spatial/src/spatial/gdal/file_handler.cpp @@ -169,8 +169,7 @@ class DuckDBFileSystemHandler : public VSIFilesystemHandler { // Check if the file is a directory #ifdef _WIN32 - if (!FileSystem::IsRemoteFile(file_name) && fs.DirectoryExists(file_name_str) && - (flags.OpenForReading())) { + if (!FileSystem::IsRemoteFile(file_name) && fs.DirectoryExists(file_name_str) && (flags.OpenForReading())) { // We can't open a directory for reading on windows without special flags // so just open nul instead, gdal will reject it when it tries to read auto file = fs.OpenFile("nul", flags); @@ -179,17 +178,15 @@ class DuckDBFileSystemHandler : public VSIFilesystemHandler { #endif // If the file is remote and NOT in write mode, we can cache it. - if (FileSystem::IsRemoteFile(file_name_str) && - !flags.OpenForWriting() && - !flags.OpenForAppending()) { + if (FileSystem::IsRemoteFile(file_name_str) && !flags.OpenForWriting() && !flags.OpenForAppending()) { // Pass the direct IO flag to the file system since we use GDAL's caching instead flags |= FileFlags::FILE_FLAGS_DIRECT_IO; - auto file = fs.OpenFile(file_name, flags | FileCompressionType::AUTO_DETECT); - return VSICreateCachedFile(new DuckDBFileHandle(std::move(file))); + auto file = fs.OpenFile(file_name, flags | FileCompressionType::AUTO_DETECT); + return VSICreateCachedFile(new DuckDBFileHandle(std::move(file))); } else { - auto file = fs.OpenFile(file_name, flags | FileCompressionType::AUTO_DETECT); + auto file = fs.OpenFile(file_name, flags | FileCompressionType::AUTO_DETECT); return new DuckDBFileHandle(std::move(file)); } } catch (std::exception &ex) { @@ -238,11 +235,11 @@ class DuckDBFileSystemHandler : public VSIFilesystemHandler { unique_ptr file; try { - file = fs.OpenFile(file_name, FileFlags::FILE_FLAGS_READ | FileCompressionType::AUTO_DETECT); + file = fs.OpenFile(file_name, FileFlags::FILE_FLAGS_READ | FileCompressionType::AUTO_DETECT | + FileFlags::FILE_FLAGS_NULL_IF_NOT_EXISTS); } catch (std::exception &ex) { return -1; } - if (!file) { return -1; } diff --git a/spatial/src/spatial/gdal/functions/st_read.cpp b/spatial/src/spatial/gdal/functions/st_read.cpp index 9edf968a..dce7df55 100644 --- a/spatial/src/spatial/gdal/functions/st_read.cpp +++ b/spatial/src/spatial/gdal/functions/st_read.cpp @@ -5,6 +5,7 @@ #include "duckdb/planner/filter/conjunction_filter.hpp" #include "duckdb/planner/filter/constant_filter.hpp" #include "duckdb/planner/table_filter.hpp" +#include "duckdb/parser/tableref.hpp" #include "duckdb/function/function.hpp" #include "duckdb/function/replacement_scan.hpp" @@ -570,7 +571,8 @@ void GdalTableFunction::Scan(ClientContext &context, TableFunctionInput &input, validity.SetInvalid(out_idx); return core::geometry_t {}; } - return state.wkb_reader.Deserialize(input).Serialize(geom_vec); + auto geom = state.wkb_reader.Deserialize(input); + return core::Geometry::Serialize(geom, geom_vec); }); output.data[col_idx].ReferenceAndSetType(geom_vec); } @@ -592,9 +594,9 @@ unique_ptr GdalTableFunction::Cardinality(ClientContext &context return result; } -unique_ptr GdalTableFunction::ReplacementScan(ClientContext &, const string &table_name, - ReplacementScanData *) { - +unique_ptr GdalTableFunction::ReplacementScan(ClientContext &, ReplacementScanInput &input, + optional_ptr) { + auto &table_name = input.table_name; auto lower_name = StringUtil::Lower(table_name); // Check if the table name ends with some common geospatial file extensions if (StringUtil::EndsWith(lower_name, ".gpkg") || StringUtil::EndsWith(lower_name, ".fgb")) { diff --git a/spatial/src/spatial/gdal/functions/st_read_meta.cpp b/spatial/src/spatial/gdal/functions/st_read_meta.cpp index 2293072c..f950fe77 100644 --- a/spatial/src/spatial/gdal/functions/st_read_meta.cpp +++ b/spatial/src/spatial/gdal/functions/st_read_meta.cpp @@ -56,12 +56,10 @@ static LogicalType LAYER_TYPE = LogicalType::STRUCT({ static unique_ptr Bind(ClientContext &context, TableFunctionBindInput &input, vector &return_types, vector &names) { - - auto file_name = input.inputs[0].GetValue(); auto result = make_uniq(); - result->file_names = - MultiFileReader::GetFileList(context, input.inputs[0], "gdal metadata", FileGlobOptions::ALLOW_EMPTY); + auto multi_file_reader = MultiFileReader::Create(input.table_function); + result->file_names = multi_file_reader->CreateFileList(context, input.inputs[0], FileGlobOptions::ALLOW_EMPTY)->GetAllFiles(); names.push_back("file_name"); return_types.push_back(LogicalType::VARCHAR); diff --git a/spatial/src/spatial/geographiclib/functions/st_area_spheroid.cpp b/spatial/src/spatial/geographiclib/functions/st_area_spheroid.cpp index f82a0b09..ec1c25a7 100644 --- a/spatial/src/spatial/geographiclib/functions/st_area_spheroid.cpp +++ b/spatial/src/spatial/geographiclib/functions/st_area_spheroid.cpp @@ -80,14 +80,14 @@ static void GeodesicPolygon2DFunction(DataChunk &args, ExpressionState &state, V //------------------------------------------------------------------------------ // GEOMETRY //------------------------------------------------------------------------------ -static double PolygonArea(const Polygon &poly, GeographicLib::PolygonArea &comp) { +static double PolygonArea(const Geometry &poly, GeographicLib::PolygonArea &comp) { double total_area = 0; for (uint32_t ring_idx = 0; ring_idx < poly.Count(); ring_idx++) { comp.Clear(); - auto &ring = poly[ring_idx]; + auto &ring = Polygon::Part(poly, ring_idx); // Note: the last point is the same as the first point, but geographiclib doesn't know that, for (uint32_t coord_idx = 0; coord_idx < ring.Count() - 1; coord_idx++) { - auto coord = ring.Get(coord_idx); + auto coord = LineString::GetVertex(ring, coord_idx); comp.AddPoint(coord.x, coord.y); } double ring_area; @@ -115,34 +115,12 @@ static void GeodesicGeometryFunction(DataChunk &args, ExpressionState &state, Ve const GeographicLib::Geodesic &geod = GeographicLib::Geodesic::WGS84(); auto comp = GeographicLib::PolygonArea(geod, false); - struct op { - static double Apply(const Polygon &poly, GeographicLib::PolygonArea &comp) { - return PolygonArea(poly, comp); - } - - static double Apply(const MultiPolygon &mpoly, GeographicLib::PolygonArea &comp) { - double total_area = 0; - for (auto &poly : mpoly) { - total_area += PolygonArea(poly, comp); - } - return total_area; - } - - static double Apply(const GeometryCollection &coll, GeographicLib::PolygonArea &comp) { - double total_area = 0; - for (auto &item : coll) { - total_area += item.Visit(comp); - } - return total_area; - } - - static double Apply(const BaseGeometry &, GeographicLib::PolygonArea &) { - return 0.0; - } - }; - - UnaryExecutor::Execute( - input, result, count, [&](geometry_t input) { return Geometry::Deserialize(arena, input).Visit(comp); }); + UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { + auto geom = Geometry::Deserialize(arena, input); + double area = 0; + Geometry::ExtractPolygons(geom, [&](const Geometry &geom) { area += PolygonArea(geom, comp); }); + return area; + }); if (count == 1) { result.SetVectorType(VectorType::CONSTANT_VECTOR); diff --git a/spatial/src/spatial/geographiclib/functions/st_length_spheroid.cpp b/spatial/src/spatial/geographiclib/functions/st_length_spheroid.cpp index 20f0b59e..446aae19 100644 --- a/spatial/src/spatial/geographiclib/functions/st_length_spheroid.cpp +++ b/spatial/src/spatial/geographiclib/functions/st_length_spheroid.cpp @@ -58,12 +58,12 @@ static void GeodesicLineString2DFunction(DataChunk &args, ExpressionState &state //------------------------------------------------------------------------------ // GEOMETRY //------------------------------------------------------------------------------ -static double LineLength(const LineString &line, GeographicLib::PolygonArea &comp) { +static double LineLength(const Geometry &line, GeographicLib::PolygonArea &comp) { comp.Clear(); - for (uint32_t i = 0; i < line.Count(); i++) { - auto vert = line.Get(i); - comp.AddPoint(vert.x, vert.y); - } + for(uint32_t i = 0; i < LineString::VertexCount(line); i++) { + auto vert = LineString::GetVertex(line, i); + comp.AddPoint(vert.x, vert.y); + } double _area; double linestring_length; comp.Compute(false, true, linestring_length, _area); @@ -80,34 +80,12 @@ static void GeodesicGeometryFunction(DataChunk &args, ExpressionState &state, Ve const GeographicLib::Geodesic &geod = GeographicLib::Geodesic::WGS84(); auto comp = GeographicLib::PolygonArea(geod, true); - struct op { - static double Apply(const LineString &line, GeographicLib::PolygonArea &comp) { - return LineLength(line, comp); - } - - static double Apply(const MultiLineString &mline, GeographicLib::PolygonArea &comp) { - double sum = 0.0; - for (const auto &line : mline) { - sum += LineLength(line, comp); - } - return sum; - } - - static double Apply(const GeometryCollection &collection, GeographicLib::PolygonArea &comp) { - double sum = 0.0; - for (const auto &geom : collection) { - sum += geom.Visit(comp); - } - return sum; - } - - static double Apply(const BaseGeometry &, GeographicLib::PolygonArea &) { - return 0.0; - } - }; - - UnaryExecutor::Execute( - input, result, count, [&](geometry_t input) { return Geometry::Deserialize(arena, input).Visit(comp); }); + UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { + auto geom = Geometry::Deserialize(arena, input); + double length = 0.0; + Geometry::ExtractLines(geom, [&](const Geometry &line) { length += LineLength(line, comp); }); + return length; + }); if (count == 1) { result.SetVectorType(VectorType::CONSTANT_VECTOR); diff --git a/spatial/src/spatial/geographiclib/functions/st_perimeter_spheroid.cpp b/spatial/src/spatial/geographiclib/functions/st_perimeter_spheroid.cpp index 8c3bb52b..e9e219bc 100644 --- a/spatial/src/spatial/geographiclib/functions/st_perimeter_spheroid.cpp +++ b/spatial/src/spatial/geographiclib/functions/st_perimeter_spheroid.cpp @@ -70,15 +70,15 @@ static void GeodesicPolygon2DFunction(DataChunk &args, ExpressionState &state, V //------------------------------------------------------------------------------ // GEOMETRY //------------------------------------------------------------------------------ -static double PolygonPerimeter(const Polygon &poly, GeographicLib::PolygonArea &comp) { +static double PolygonPerimeter(const Geometry &poly, GeographicLib::PolygonArea &comp) { double total_perimeter = 0; - for (auto &ring : poly) { + for (auto &ring : Polygon::Parts(poly)) { comp.Clear(); // Note: the last point is the same as the first point, but geographiclib doesn't know that, // so skip it. for (uint32_t coord_idx = 0; coord_idx < ring.Count() - 1; coord_idx++) { - auto coord = ring.Get(coord_idx); + auto coord = LineString::GetVertex(ring, coord_idx); comp.AddPoint(coord.x, coord.y); } double _ring_area; @@ -99,34 +99,12 @@ static void GeodesicGeometryFunction(DataChunk &args, ExpressionState &state, Ve const GeographicLib::Geodesic &geod = GeographicLib::Geodesic::WGS84(); auto comp = GeographicLib::PolygonArea(geod, false); - struct op { - static double Apply(const Polygon &poly, GeographicLib::PolygonArea &comp) { - return PolygonPerimeter(poly, comp); - } - - static double Apply(const MultiPolygon &mpoly, GeographicLib::PolygonArea &comp) { - double total_perimeter = 0; - for (auto &poly : mpoly) { - total_perimeter += PolygonPerimeter(poly, comp); - } - return total_perimeter; - } - - static double Apply(const GeometryCollection &coll, GeographicLib::PolygonArea &comp) { - double total_perimeter = 0; - for (auto &item : coll) { - total_perimeter += item.Visit(comp); - } - return total_perimeter; - } - - static double Apply(const BaseGeometry &, GeographicLib::PolygonArea &) { - return 0.0; - } - }; - - UnaryExecutor::Execute( - input, result, count, [&](geometry_t input) { return Geometry::Deserialize(arena, input).Visit(comp); }); + UnaryExecutor::Execute(input, result, count, [&](geometry_t input) { + auto geom = Geometry::Deserialize(arena, input); + auto length = 0.0; + Geometry::ExtractPolygons(geom, [&](const Geometry &poly) { length += PolygonPerimeter(poly, comp); }); + return length; + }); if (count == 1) { result.SetVectorType(VectorType::CONSTANT_VECTOR); diff --git a/spatial/src/spatial/geos/functions/scalar/CMakeLists.txt b/spatial/src/spatial/geos/functions/scalar/CMakeLists.txt index 91c507c5..dabb15e3 100644 --- a/spatial/src/spatial/geos/functions/scalar/CMakeLists.txt +++ b/spatial/src/spatial/geos/functions/scalar/CMakeLists.txt @@ -17,7 +17,6 @@ set(EXTENSION_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/st_equals.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_intersection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_intersects.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/st_is_closed.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_is_ring.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_is_simple.cpp ${CMAKE_CURRENT_SOURCE_DIR}/st_is_valid.cpp diff --git a/spatial/src/spatial/geos/functions/scalar/st_is_valid.cpp b/spatial/src/spatial/geos/functions/scalar/st_is_valid.cpp index d21018dd..1463bfc4 100644 --- a/spatial/src/spatial/geos/functions/scalar/st_is_valid.cpp +++ b/spatial/src/spatial/geos/functions/scalar/st_is_valid.cpp @@ -18,30 +18,33 @@ static bool IsValidForGeos(Geometry &geometry) { switch (geometry.GetType()) { case GeometryType::LINESTRING: // Every linestring needs 0 or at least 2 points - return geometry.As().Count() != 1; + return LineString::VertexCount(geometry) != 1; case GeometryType::POLYGON: { // Every ring needs 0 or at least 4 points - auto &polygon = geometry.As(); - for (const auto &ring : polygon) { - if (ring.Count() > 0 && ring.Count() < 4) { + for (uint32_t i = 0; i < Polygon::PartCount(geometry); i++) { + auto &ring = Polygon::Part(geometry, i); + if (LineString::VertexCount(ring) < 4) { return false; } } return true; } case GeometryType::MULTILINESTRING: { - for (const auto &linestring : geometry.As()) { - if (linestring.Count() == 1) { + for (uint32_t i = 0; i < MultiLineString::PartCount(geometry); i++) { + auto &linestring = MultiLineString::Part(geometry, i); + if (LineString::VertexCount(linestring) == 1) { return false; } } return true; } case GeometryType::MULTIPOLYGON: { - for (const auto &polygon : geometry.As()) { - for (const auto &ring : polygon) { - if (ring.Count() > 0 && ring.Count() < 4) { + for (uint32_t i = 0; i < MultiPolygon::PartCount(geometry); i++) { + auto &polygon = MultiPolygon::Part(geometry, i); + for (uint32_t j = 0; j < Polygon::PartCount(polygon); j++) { + auto &ring = Polygon::Part(polygon, j); + if (LineString::VertexCount(ring) < 4) { return false; } } @@ -49,7 +52,8 @@ static bool IsValidForGeos(Geometry &geometry) { return true; } case GeometryType::GEOMETRYCOLLECTION: { - for (auto &geom : geometry.As()) { + for (uint32_t i = 0; i < GeometryCollection::PartCount(geometry); i++) { + auto &geom = GeometryCollection::Part(geometry, i); if (!IsValidForGeos(geom)) { return false; } diff --git a/spatial/src/spatial/geos/geos_wrappers.cpp b/spatial/src/spatial/geos/geos_wrappers.cpp index 2cd233f3..2605a261 100644 --- a/spatial/src/spatial/geos/geos_wrappers.cpp +++ b/spatial/src/spatial/geos/geos_wrappers.cpp @@ -1,7 +1,8 @@ #include "spatial/common.hpp" #include "spatial/geos/geos_wrappers.hpp" #include "spatial/core/geometry/geometry.hpp" -#include "spatial/core/geometry/cursor.hpp" +#include "spatial/core/util/cursor.hpp" +#include "spatial/core/util/math.hpp" #include "spatial/core/geometry/geometry_processor.hpp" namespace spatial { @@ -556,22 +557,22 @@ geometry_t SerializeGEOSGeometry(Vector &result, const GEOSGeometry *geom, GEOSC if (has_bbox) { double minx, maxx, miny, maxy; GEOSGeom_getExtent_r(ctx, geom, &minx, &miny, &maxx, &maxy); - writer.Write(Utils::DoubleToFloatDown(minx)); - writer.Write(Utils::DoubleToFloatDown(miny)); - writer.Write(Utils::DoubleToFloatUp(maxx)); - writer.Write(Utils::DoubleToFloatUp(maxy)); + writer.Write(MathUtil::DoubleToFloatDown(minx)); + writer.Write(MathUtil::DoubleToFloatDown(miny)); + writer.Write(MathUtil::DoubleToFloatUp(maxx)); + writer.Write(MathUtil::DoubleToFloatUp(maxy)); // well, this sucks. GEOS doesnt have a native way to get the Z and M value extents. if (has_z || has_m) { double minz, maxz, minm, maxm; GetExtendedExtent(geom, &minz, &maxz, &minm, &maxm, ctx); if (has_z) { - writer.Write(Utils::DoubleToFloatDown(minz)); - writer.Write(Utils::DoubleToFloatUp(maxz)); + writer.Write(MathUtil::DoubleToFloatDown(minz)); + writer.Write(MathUtil::DoubleToFloatUp(maxz)); } if (has_m) { - writer.Write(Utils::DoubleToFloatDown(minm)); - writer.Write(Utils::DoubleToFloatUp(maxm)); + writer.Write(MathUtil::DoubleToFloatDown(minm)); + writer.Write(MathUtil::DoubleToFloatUp(maxm)); } } } diff --git a/spatial/src/spatial/proj/functions.cpp b/spatial/src/spatial/proj/functions.cpp index f7e7cf84..990c2109 100644 --- a/spatial/src/spatial/proj/functions.cpp +++ b/spatial/src/spatial/proj/functions.cpp @@ -233,18 +233,18 @@ static void Point2DTransformFunction(DataChunk &args, ExpressionState &state, Ve } struct TransformOp { - static void Apply(SinglePartGeometry &geom, PJ *crs, ArenaAllocator &arena) { - geom.MakeMutable(arena); + static void Case(Geometry::Tags::SinglePartGeometry, Geometry &geom, PJ *crs, ArenaAllocator &arena) { + SinglePartGeometry::MakeMutable(geom, arena); for (uint32_t i = 0; i < geom.Count(); i++) { - auto vertex = geom.Get(i); + auto vertex = SinglePartGeometry::GetVertex(geom, i); auto transformed = proj_trans(crs, PJ_FWD, proj_coord(vertex.x, vertex.y, 0, 0)).xy; // we own the array, so we can use SetUnsafe - geom.Set(i, transformed.x, transformed.y); + SinglePartGeometry::SetVertex(geom, i, {transformed.x, transformed.y}); } } - static void Apply(MultiPartGeometry &geom, PJ *crs, ArenaAllocator &arena) { - for (auto &part : geom) { - part.Visit(crs, arena); + static void Case(Geometry::Tags::MultiPartGeometry, Geometry &geom, PJ *crs, ArenaAllocator &arena) { + for (auto &part : MultiPartGeometry::Parts(geom)) { + Geometry::Match(part, crs, arena); } } }; @@ -296,8 +296,8 @@ static void GeometryTransformFunction(DataChunk &args, ExpressionState &state, V UnaryExecutor::Execute(geom_vec, result, count, [&](geometry_t input_geom) { auto geom = Geometry::Deserialize(arena, input_geom); - geom.Visit(crs.get(), arena); - return geom.Serialize(result); + Geometry::Match(geom, crs.get(), arena); + return Geometry::Serialize(geom, result); }); } else { // General case: projections are not constant @@ -322,8 +322,8 @@ static void GeometryTransformFunction(DataChunk &args, ExpressionState &state, V } auto geom = Geometry::Deserialize(arena, input_geom); - geom.Visit(crs.get(), arena); - return geom.Serialize(result); + Geometry::Match(geom, crs.get(), arena); + return Geometry::Serialize(geom, result); }); } }