Skip to content

Commit

Permalink
Merge pull request #145 from Maxxen/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxxen authored Oct 16, 2023
2 parents 6d726d8 + 9d8cab3 commit 8733659
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 1 deletion.
8 changes: 8 additions & 0 deletions spatial/include/spatial/core/functions/scalar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ struct CoreScalarFunctions {
RegisterStIntersectsExtent(db);
RegisterStIsEmpty(db);
RegisterStLength(db);
RegisterStMakeEnvelope(db);
RegisterStMakeLine(db);
RegisterStMakePolygon(db);
RegisterStNGeometries(db);
RegisterStNInteriorRings(db);
RegisterStNPoints(db);
Expand Down Expand Up @@ -114,9 +116,15 @@ struct CoreScalarFunctions {
// ST_Length
static void RegisterStLength(DatabaseInstance &db);

// ST_MakeEnvelope
static void RegisterStMakeEnvelope(DatabaseInstance &db);

// ST_MakeLine
static void RegisterStMakeLine(DatabaseInstance &db);

// ST_MakePolygon
static void RegisterStMakePolygon(DatabaseInstance &db);

// ST_NGeometries
static void RegisterStNGeometries(DatabaseInstance &db);

Expand Down
2 changes: 2 additions & 0 deletions spatial/src/spatial/core/functions/scalar/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ set(EXTENSION_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/st_intersects.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_intersects_extent.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_length.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_makeenvelope.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_makeline.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_makepolygon.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_ngeometries.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_ninteriorrings.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_npoints.cpp
Expand Down
55 changes: 55 additions & 0 deletions spatial/src/spatial/core/functions/scalar/st_makeenvelope.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include "spatial/common.hpp"
#include "spatial/core/types.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/generic_executor.hpp"

namespace spatial {

namespace core {

static void MakeEnvelopeFunction(DataChunk &args, ExpressionState &state, Vector &result) {
auto &lstate = GeometryFunctionLocalState::ResetAndGet(state);
auto count = args.size();

auto &min_x_vec = args.data[0];
auto &min_y_vec = args.data[1];
auto &max_x_vec = args.data[2];
auto &max_y_vec = args.data[3];

using DOUBLE_TYPE = PrimitiveType<double>;
using GEOMETRY_TYPE = PrimitiveType<string_t>;

GenericExecutor::ExecuteQuaternary<DOUBLE_TYPE, DOUBLE_TYPE, DOUBLE_TYPE, DOUBLE_TYPE, GEOMETRY_TYPE>(
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) {
uint32_t capacity = 5;
auto envelope_geom = lstate.factory.CreatePolygon(1, &capacity);
// Create the exterior ring in CCW order
envelope_geom.Shell().Add(Vertex(x_min.val, y_min.val));
envelope_geom.Shell().Add(Vertex(x_min.val, y_max.val));
envelope_geom.Shell().Add(Vertex(x_max.val, y_max.val));
envelope_geom.Shell().Add(Vertex(x_max.val, y_min.val));
envelope_geom.Shell().Add(Vertex(x_min.val, y_min.val));

return lstate.factory.Serialize(result, Geometry(envelope_geom));
});
}

void CoreScalarFunctions::RegisterStMakeEnvelope(DatabaseInstance &db) {

ScalarFunctionSet set("ST_MakeEnvelope");

set.AddFunction(ScalarFunction({LogicalType::DOUBLE, LogicalType::DOUBLE, LogicalType::DOUBLE, LogicalType::DOUBLE},
GeoTypes::GEOMETRY(), MakeEnvelopeFunction, nullptr, nullptr, nullptr,
GeometryFunctionLocalState::Init));

ExtensionUtil::RegisterFunction(db, set);
}

} // namespace core

} // namespace spatial
143 changes: 143 additions & 0 deletions spatial/src/spatial/core/functions/scalar/st_makepolygon.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#include "spatial/common.hpp"
#include "spatial/core/types.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"
#include "duckdb/common/vector_operations/binary_executor.hpp"

namespace spatial {

namespace core {

static void MakePolygonFromRingsFunction(DataChunk &args, ExpressionState &state, Vector &result) {
auto &lstate = GeometryFunctionLocalState::ResetAndGet(state);
auto count = args.size();

auto &child_vec = ListVector::GetEntry(args.data[1]);
UnifiedVectorFormat format;
child_vec.ToUnifiedFormat(count, format);

BinaryExecutor::Execute<string_t, list_entry_t, string_t>(
args.data[0], args.data[1], result, count, [&](string_t line_blob, list_entry_t &rings_list) {
// First, setup the shell
auto shell_geom = lstate.factory.Deserialize(line_blob);
if (shell_geom.Type() != GeometryType::LINESTRING) {
throw InvalidInputException("ST_MakePolygon only accepts LINESTRING geometries");
}

auto &shell = shell_geom.GetLineString();
auto shell_vert_count = shell.Count();
if (shell_vert_count < 4) {
throw InvalidInputException("ST_MakePolygon shell requires at least 4 vertices");
}

auto &shell_verts = shell.Vertices();
if (shell_verts[0] != shell_verts[shell_vert_count - 1]) {
throw InvalidInputException(
"ST_MakePolygon shell must be closed (first and last vertex must be equal)");
}

// Validate and count the hole ring sizes
auto holes_offset = rings_list.offset;
auto holes_length = rings_list.length;

vector<uint32_t> rings_counts;
vector<LineString> rings;
rings_counts.push_back(shell_vert_count);
rings.push_back(shell);

for (idx_t hole_idx = 0; hole_idx < holes_length; hole_idx++) {
auto mapped_idx = format.sel->get_index(holes_offset + hole_idx);
if (!format.validity.RowIsValid(mapped_idx)) {
continue;
}

auto geometry_blob = UnifiedVectorFormat::GetData<string_t>(format)[mapped_idx];
auto hole_geometry = lstate.factory.Deserialize(geometry_blob);

if (hole_geometry.Type() != GeometryType::LINESTRING) {
throw InvalidInputException(
StringUtil::Format("ST_MakePolygon hole #%lu is not a LINESTRING geometry", hole_idx + 1));
}
auto &hole = hole_geometry.GetLineString();
auto hole_count = hole.Count();
if (hole_count < 4) {
throw InvalidInputException(
StringUtil::Format("ST_MakePolygon hole #%lu requires at least 4 vertices", hole_idx + 1));
}

auto &ring_verts = hole.Vertices();
if (ring_verts[0] != ring_verts[hole_count - 1]) {
throw InvalidInputException(StringUtil::Format(
"ST_MakePolygon hole #%lu must be closed (first and last vertex must be equal)", hole_idx + 1));
}

rings_counts.push_back(hole_count);
rings.push_back(hole);
}

auto polygon = lstate.factory.CreatePolygon(rings_counts.size(), rings_counts.data());

for (auto ring_idx = 0; ring_idx < rings.size(); ring_idx++) {
auto &new_ring = rings[ring_idx];
auto &poly_ring = polygon.Ring(ring_idx);
for (auto &v : new_ring.Vertices()) {
poly_ring.Add(v);
}
}

return lstate.factory.Serialize(result, Geometry(polygon));
});
}

static void MakePolygonFromShellFunction(DataChunk &args, ExpressionState &state, Vector &result) {
auto &lstate = GeometryFunctionLocalState::ResetAndGet(state);
auto count = args.size();

UnaryExecutor::Execute<string_t, string_t>(args.data[0], result, count, [&](string_t &line_blob) {
auto line_geom = lstate.factory.Deserialize(line_blob);

if (line_geom.Type() != GeometryType::LINESTRING) {
throw InvalidInputException("ST_MakePolygon only accepts LINESTRING geometries");
}

auto &line = line_geom.GetLineString();
auto line_count = line.Count();
if (line_count < 4) {
throw InvalidInputException("ST_MakePolygon shell requires at least 4 vertices");
}

auto &line_verts = line.Vertices();
if (line_verts[0] != line_verts[line_count - 1]) {
throw InvalidInputException("ST_MakePolygon shell must be closed (first and last vertex must be equal)");
}

auto polygon = lstate.factory.CreatePolygon(1, &line_count);
for (auto &v : line_verts) {
polygon.Shell().Add(v);
}

return lstate.factory.Serialize(result, Geometry(polygon));
});
}

void CoreScalarFunctions::RegisterStMakePolygon(DatabaseInstance &db) {

ScalarFunctionSet set("ST_MakePolygon");

set.AddFunction(ScalarFunction({GeoTypes::GEOMETRY(), LogicalType::LIST(GeoTypes::GEOMETRY())},
GeoTypes::GEOMETRY(), MakePolygonFromRingsFunction, nullptr, nullptr, nullptr,
GeometryFunctionLocalState::Init));

set.AddFunction(ScalarFunction({GeoTypes::GEOMETRY()}, GeoTypes::GEOMETRY(), MakePolygonFromShellFunction, nullptr,
nullptr, nullptr, GeometryFunctionLocalState::Init));

ExtensionUtil::RegisterFunction(db, set);
}

} // namespace core

} // namespace spatial
13 changes: 13 additions & 0 deletions spatial/src/spatial/gdal/file_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,34 @@ static void *DuckDBOpen(void *, const char *file_name, const char *access) {
if (len > 1 && access[1] == '+') {
flags |= FileFlags::FILE_FLAGS_WRITE;
}
if (len > 2 && access[2] == '+') {
// might be "rb+"
flags |= FileFlags::FILE_FLAGS_WRITE;
}
} else if (access[0] == 'w') {
flags = FileFlags::FILE_FLAGS_WRITE | FileFlags::FILE_FLAGS_FILE_CREATE_NEW;
if (len > 1 && access[1] == '+') {
flags |= FileFlags::FILE_FLAGS_READ;
}
if (len > 2 && access[2] == '+') {
// might be "wb+"
flags |= FileFlags::FILE_FLAGS_READ;
}
} else if (access[0] == 'a') {
flags = FileFlags::FILE_FLAGS_APPEND;
if (len > 1 && access[1] == '+') {
flags |= FileFlags::FILE_FLAGS_READ;
}
if (len > 2 && access[2] == '+') {
// might be "ab+"
flags |= FileFlags::FILE_FLAGS_READ;
}
} else {
throw InternalException("Unknown file access type");
}

try {
string path(file_name);
auto file = fs.OpenFile(file_name, flags);
return file.release();
} catch (std::exception &ex) {
Expand Down
5 changes: 4 additions & 1 deletion spatial/src/spatial/gdal/functions/st_write.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,11 @@ static unique_ptr<GlobalFunctionData> InitGlobal(ClientContext &context, Functio
if (!gdal_data.target_srs.empty()) {
srs.SetFromUserInput(gdal_data.target_srs.c_str());
}
// Not all GDAL drivers check if the SRS is empty (cough cough GeoJSONSeq)
// so we have to pass nullptr if we want the default behavior.
OGRSpatialReference *srs_ptr = gdal_data.target_srs.empty() ? nullptr : &srs;

auto layer = dataset->CreateLayer(gdal_data.layer_name.c_str(), &srs, wkbUnknown, lco);
auto layer = dataset->CreateLayer(gdal_data.layer_name.c_str(), srs_ptr, wkbUnknown, lco);
if (!layer) {
throw IOException("Could not create layer");
}
Expand Down
5 changes: 5 additions & 0 deletions spatial/src/spatial/gdal/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ void GdalModule::Register(DatabaseInstance &db) {
OGRRegisterAllInternal();

// Set GDAL error handler

CPLSetErrorHandler([](CPLErr e, int code, const char *msg) {
// DuckDB doesnt do warnings, so we only throw on errors
if (e != CE_Failure && e != CE_Fatal) {
return;
}
switch (code) {
case CPLE_NoWriteAccess:
throw PermissionException("GDAL Error (%d): %s", code, msg);
Expand Down
15 changes: 15 additions & 0 deletions test/sql/gdal/st_write_basic.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require spatial

statement ok
COPY (SELECT ST_GeomFromText('POINT (1 1)'))
TO '__TEST_DIR__/test_seq.json'
WITH (FORMAT GDAL, DRIVER 'GeoJSONSeq');

statement ok
COPY (SELECT ST_GeomFromText('POINT (1 1)'))
TO '__TEST_DIR__/test_seq.shp'
WITH (FORMAT GDAL, DRIVER 'ESRI ShapeFile');

# MVT is broken due to threading issues
#statement ok
#COPY (SELECT ST_GeomFromText('POINT (1 1)')) TO '__TEST_DIR__/test_mvt.mvt' WITH (FORMAT GDAL, DRIVER 'MVT');
11 changes: 11 additions & 0 deletions test/sql/geometry/st_makeenvelope.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require spatial

query I
SELECT ST_AsText(ST_MakeEnvelope(5, 5, 10, 10));
----
POLYGON ((5 5, 5 10, 10 10, 10 5, 5 5))

query I
SELECT ST_AsText(ST_MakeEnvelope(5, NULL, 10, 10));
----
NULL
Loading

0 comments on commit 8733659

Please sign in to comment.