diff --git a/spatial/include/spatial/core/functions/aggregate.hpp b/spatial/include/spatial/core/functions/aggregate.hpp
index d6ab6717..f1233e2b 100644
--- a/spatial/include/spatial/core/functions/aggregate.hpp
+++ b/spatial/include/spatial/core/functions/aggregate.hpp
@@ -9,10 +9,12 @@ struct CoreAggregateFunctions {
public:
static void Register(DatabaseInstance &db) {
RegisterStEnvelopeAgg(db);
+ RegisterStSvgAgg(db);
}
private:
static void RegisterStEnvelopeAgg(DatabaseInstance &db);
+ static void RegisterStSvgAgg(DatabaseInstance &db);
};
} // namespace core
diff --git a/spatial/include/spatial/core/functions/scalar.hpp b/spatial/include/spatial/core/functions/scalar.hpp
index eb73f4ee..1066a4be 100644
--- a/spatial/include/spatial/core/functions/scalar.hpp
+++ b/spatial/include/spatial/core/functions/scalar.hpp
@@ -13,6 +13,7 @@ struct CoreScalarFunctions {
RegisterStAsText(db);
RegisterStAsWKB(db);
RegisterStAsHEXWKB(db);
+ RegisterStAsSVG(db);
RegisterStCentroid(db);
RegisterStCollect(db);
RegisterStCollectionExtract(db);
@@ -64,6 +65,9 @@ struct CoreScalarFunctions {
// ST_AsHextWKB
static void RegisterStAsHEXWKB(DatabaseInstance &db);
+ // ST_AsSVG
+ static void RegisterStAsSVG(DatabaseInstance &db);
+
// ST_AsWKB
static void RegisterStAsWKB(DatabaseInstance &db);
diff --git a/spatial/src/spatial/core/functions/aggregate/CMakeLists.txt b/spatial/src/spatial/core/functions/aggregate/CMakeLists.txt
index 3047e80a..a42328df 100644
--- a/spatial/src/spatial/core/functions/aggregate/CMakeLists.txt
+++ b/spatial/src/spatial/core/functions/aggregate/CMakeLists.txt
@@ -1,5 +1,6 @@
set(EXTENSION_SOURCES
${EXTENSION_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/st_envelope_agg.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/st_svg_agg.cpp
PARENT_SCOPE
)
\ No newline at end of file
diff --git a/spatial/src/spatial/core/functions/aggregate/st_svg_agg.cpp b/spatial/src/spatial/core/functions/aggregate/st_svg_agg.cpp
new file mode 100644
index 00000000..adcf4eaf
--- /dev/null
+++ b/spatial/src/spatial/core/functions/aggregate/st_svg_agg.cpp
@@ -0,0 +1,46 @@
+#include "duckdb/common/types/null_value.hpp"
+#include "duckdb/execution/expression_executor.hpp"
+#include "duckdb/planner/expression/bound_constant_expression.hpp"
+
+#include "spatial/common.hpp"
+#include "spatial/core/functions/aggregate.hpp"
+#include "duckdb/function/scalar_macro_function.hpp"
+
+#include "duckdb/catalog/default/default_functions.hpp"
+
+namespace spatial {
+
+namespace core {
+
+void CoreAggregateFunctions::RegisterStSvgAgg(DatabaseInstance &db) {
+
+ DefaultMacro macro = {nullptr};
+ macro.schema = DEFAULT_SCHEMA;
+ macro.name = "st_svg_agg";
+ macro.parameters[0] = "geom";
+ macro.parameters[1] = "style";
+ macro.macro = R"--(
+ format('',
+ st_xmin(st_envelope_agg(geom)) - (st_xmax(st_envelope_agg(geom)) - st_xmin(st_envelope_agg(geom))) * padding,
+ st_ymin(st_envelope_agg(geom)) - (st_ymax(st_envelope_agg(geom)) - st_ymin(st_envelope_agg(geom))) * padding,
+ (st_xmax(st_envelope_agg(geom)) - st_xmin(st_envelope_agg(geom))) + (st_xmax(st_envelope_agg(geom)) - st_xmin(st_envelope_agg(geom))) * padding,
+ (st_xmax(st_envelope_agg(geom)) - st_xmin(st_envelope_agg(geom))) + (st_ymax(st_envelope_agg(geom)) - st_ymin(st_envelope_agg(geom))) * padding,
+ width,
+ height,
+ string_agg(ST_AsSVG(geom, style), ''));
+ )--";
+
+ auto info = DefaultFunctionGenerator::CreateInternalMacroInfo(macro);
+ info->function->default_parameters["order_by"] = make_uniq(Value::BIGINT(0));
+ info->function->default_parameters["padding"] = make_uniq(Value::DOUBLE(0.1));
+ info->function->default_parameters["width"] = make_uniq(Value::BIGINT(100));
+ info->function->default_parameters["height"] = make_uniq(Value::BIGINT(100));
+ info->parameter_names = {"geom", "style", "order_by", "padding", "width", "height"};
+ ExtensionUtil::RegisterFunction(db, *info);
+
+ // ExtensionUtil::RegisterFunction(db, string_agg);
+}
+
+} // namespace core
+
+} // namespace spatial
diff --git a/spatial/src/spatial/core/functions/scalar/CMakeLists.txt b/spatial/src/spatial/core/functions/scalar/CMakeLists.txt
index deb31072..f7c95191 100644
--- a/spatial/src/spatial/core/functions/scalar/CMakeLists.txt
+++ b/spatial/src/spatial/core/functions/scalar/CMakeLists.txt
@@ -3,6 +3,7 @@ set(EXTENSION_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/st_area.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_asgeojson.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_ashexwkb.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/st_assvg.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_astext.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_aswkb.cpp
${CMAKE_CURRENT_SOURCE_DIR}/st_centroid.cpp
diff --git a/spatial/src/spatial/core/functions/scalar/st_assvg.cpp b/spatial/src/spatial/core/functions/scalar/st_assvg.cpp
new file mode 100644
index 00000000..c79ba2fb
--- /dev/null
+++ b/spatial/src/spatial/core/functions/scalar/st_assvg.cpp
@@ -0,0 +1,107 @@
+#include "duckdb/common/vector_operations/binary_executor.hpp"
+#include "duckdb/parser/parsed_data/create_scalar_function_info.hpp"
+
+#include "spatial/common.hpp"
+#include "spatial/core/functions/scalar.hpp"
+#include "spatial/core/functions/common.hpp"
+#include "spatial/core/geometry/geometry_factory.hpp"
+#include "spatial/core/types.hpp"
+
+namespace spatial {
+
+namespace core {
+
+//------------------------------------------------------------------------------
+// GEOMETRY -> SVG
+//------------------------------------------------------------------------------
+
+static void SetDefaultStyle(case_insensitive_map_t style, GeometryType type) {
+ switch(type) {
+ case GeometryType::POINT:
+ style["r"] = "0.1";
+ break;
+ case GeometryType::LINESTRING:
+ style["fill"] = "none";
+ style["stroke"] = "black";
+ style["stroke-width"] = "0.01";
+ break;
+ default:
+ break;
+ }
+}
+
+static string GeometryToSVG(const Geometry &geom, const case_insensitive_map_t &style) {
+
+ string attributes;
+ for (auto &entry : style) {
+ attributes += StringUtil::Format("%s=\"%s\" ", entry.first.c_str(), entry.second.c_str());
+ }
+
+ switch(geom.Type()) {
+ case GeometryType::POINT: {
+ auto point = geom.GetPoint();
+ auto vertex = point.GetVertex();
+ return StringUtil::Format(R"()", vertex.x, vertex.y, attributes);
+ }
+ case GeometryType::LINESTRING: {
+ auto linestring = geom.GetLineString();
+ string result = StringUtil::Format("";
+ return result;
+ }
+ default:
+ throw NotImplementedException("SVG for geometry type not implemented");
+ }
+}
+
+static void GeometryAsSVGFunction(DataChunk &args, ExpressionState &state, Vector &result) {
+ D_ASSERT(args.data.size() == 2);
+ auto &geom_vec = args.data[0];
+ auto &map_vec = args.data[1];
+ auto &key_vec = MapVector::GetKeys(map_vec);
+ auto &value_vec = MapVector::GetValues(map_vec);
+ auto key_data = FlatVector::GetData(key_vec);
+ auto value_data = FlatVector::GetData(value_vec);
+
+ auto count = args.size();
+
+ auto &lstate = GeometryFunctionLocalState::ResetAndGet(state);
+
+ case_insensitive_map_t style;
+
+ BinaryExecutor::Execute(geom_vec, map_vec, result, count, [&](string_t input, list_entry_t properties) {
+ // Deserialize the geometry
+ auto geometry = lstate.factory.Deserialize(input);
+
+ // Reset the style
+ style.clear();
+
+ // Set defaults
+ SetDefaultStyle(style, geometry.Type());
+
+ // Set the style from the properties
+ for(auto i = properties.offset; i < properties.offset + properties.length; i++) {
+ style[key_data[i].GetString()] = value_data[i].GetString();
+ }
+
+ // Convert the geometry to SVG
+ return StringVector::AddString(result, GeometryToSVG(geometry, style));
+ });
+}
+
+//------------------------------------------------------------------------------
+// Register functions
+//------------------------------------------------------------------------------
+void CoreScalarFunctions::RegisterStAsSVG(DatabaseInstance &db) {
+ ScalarFunction func("ST_AsSVG", {GeoTypes::GEOMETRY(), LogicalType::MAP(LogicalType::VARCHAR, LogicalType::VARCHAR)}, LogicalType::VARCHAR, GeometryAsSVGFunction, nullptr,
+ nullptr, nullptr, GeometryFunctionLocalState::Init);
+ ExtensionUtil::RegisterFunction(db, func);
+}
+
+} // namespace core
+
+} // namespace spatial
\ No newline at end of file