Skip to content

Commit 27bd2a1

Browse files
authored
fix: Apply precision when converting sfc objects to geoarrow (#63)
Closes #55 ``` r library(sf) #> Linking to GEOS 3.11.0, GDAL 3.5.3, PROJ 9.1.0; sf_use_s2() is TRUE library(geoarrow) sfc = st_point(c(1/3, 1/6)) |> st_sfc() |> st_set_precision(100) brew_geoarrow = sfc |> geoarrow::as_geoarrow_array() brew_geoarrow$children$x$buffers[[2]] |> as.vector() #> [1] 0.33 # Unfortunately geoarrow.wkb will still be inappropriately precisioned # because this isn't handled in wk sfc |>wk::as_wkb() #> <wk_wkb[1] with CRS=NA> #> [1] <POINT (0.3333333 0.1666667)> ``` <sup>Created on 2025-03-07 with [reprex v2.1.1](https://reprex.tidyverse.org)</sup>
1 parent 8016166 commit 27bd2a1

File tree

2 files changed

+101
-8
lines changed

2 files changed

+101
-8
lines changed

src/r-sf-compat.c

+60-8
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,29 @@
44

55
#include "geoarrow/geoarrow.h"
66

7+
// From sf/src/wkb.cpp to apply the precision from attr(sfc, "precision")
8+
static double make_precise(double d, double precision) {
9+
if (precision == 0.0) return d;
10+
if (precision < 0.0) { // round to float, 4-byte precision
11+
float f = d;
12+
return (double)f;
13+
}
14+
return round(d * precision) / precision;
15+
}
16+
17+
static void make_buffer_precise(double* ptr, int size_elements, double precision) {
18+
if (precision == 0) {
19+
return;
20+
}
21+
22+
for (int i = 0; i < size_elements; i++) {
23+
ptr[i] = make_precise(ptr[i], precision);
24+
}
25+
}
26+
727
static inline int builder_append_sfg(SEXP item, struct GeoArrowBuilder* builder,
8-
int level, int32_t* current_offsets) {
28+
int level, int32_t* current_offsets,
29+
double precision) {
930
switch (TYPEOF(item)) {
1031
// Level of nesting
1132
case VECSXP: {
@@ -18,7 +39,8 @@ static inline int builder_append_sfg(SEXP item, struct GeoArrowBuilder* builder,
1839
GEOARROW_RETURN_NOT_OK(
1940
GeoArrowBuilderOffsetAppend(builder, level, current_offsets + level, 1));
2041
for (int32_t i = 0; i < n; i++) {
21-
builder_append_sfg(VECTOR_ELT(item, i), builder, level + 1, current_offsets);
42+
builder_append_sfg(VECTOR_ELT(item, i), builder, level + 1, current_offsets,
43+
precision);
2244
}
2345
break;
2446
}
@@ -47,8 +69,17 @@ static inline int builder_append_sfg(SEXP item, struct GeoArrowBuilder* builder,
4769
break;
4870
}
4971

72+
// Copy the buffer
5073
GEOARROW_RETURN_NOT_OK(
5174
GeoArrowBuilderAppendBuffer(builder, first_coord_buffer + i, view));
75+
76+
// Apply precision from sfc
77+
double* ordinates =
78+
(double*)(builder->view.buffers[first_coord_buffer + i].data.as_uint8 +
79+
builder->view.buffers[first_coord_buffer + i].size_bytes);
80+
ordinates -= n_col;
81+
make_buffer_precise(ordinates, n_col, precision);
82+
5283
view.data += view.size_bytes;
5384
}
5485

@@ -73,7 +104,8 @@ static inline int builder_append_sfg(SEXP item, struct GeoArrowBuilder* builder,
73104
return GEOARROW_OK;
74105
}
75106

76-
static inline int builder_append_sfc_point(SEXP sfc, struct GeoArrowBuilder* builder) {
107+
static inline int builder_append_sfc_point(SEXP sfc, struct GeoArrowBuilder* builder,
108+
double precision) {
77109
R_xlen_t n = Rf_xlength(sfc);
78110

79111
for (int i = 0; i < builder->view.coords.n_values; i++) {
@@ -93,7 +125,7 @@ static inline int builder_append_sfc_point(SEXP sfc, struct GeoArrowBuilder* bui
93125
break;
94126
}
95127

96-
builder->view.coords.values[j][i] = item[j];
128+
builder->view.coords.values[j][i] = make_precise(item[j], precision);
97129
}
98130

99131
// Fill dimensions in builder but not in sfc with nan
@@ -107,9 +139,10 @@ static inline int builder_append_sfc_point(SEXP sfc, struct GeoArrowBuilder* bui
107139
return GEOARROW_OK;
108140
}
109141

110-
static int builder_append_sfc(SEXP sfc, struct GeoArrowBuilder* builder) {
142+
static int builder_append_sfc(SEXP sfc, struct GeoArrowBuilder* builder,
143+
double precision) {
111144
if (Rf_inherits(sfc, "sfc_POINT")) {
112-
return builder_append_sfc_point(sfc, builder);
145+
return builder_append_sfc_point(sfc, builder, precision);
113146
}
114147

115148
R_xlen_t n = Rf_xlength(sfc);
@@ -131,7 +164,8 @@ static int builder_append_sfc(SEXP sfc, struct GeoArrowBuilder* builder) {
131164
// Append elements
132165
for (R_xlen_t i = 0; i < n; i++) {
133166
SEXP item = VECTOR_ELT(sfc, i);
134-
GEOARROW_RETURN_NOT_OK(builder_append_sfg(item, builder, 0, current_offsets));
167+
GEOARROW_RETURN_NOT_OK(
168+
builder_append_sfg(item, builder, 0, current_offsets, precision));
135169
}
136170

137171
builder->view.length = n;
@@ -164,6 +198,24 @@ SEXP geoarrow_c_as_nanoarrow_array_sfc(SEXP sfc, SEXP schema_xptr, SEXP array_xp
164198
SEXP builder_xptr = PROTECT(R_MakeExternalPtr(builder, R_NilValue, R_NilValue));
165199
R_RegisterCFinalizer(builder_xptr, &finalize_builder_xptr);
166200

201+
// Get the precision from the sf object so we can apply it if needed
202+
double precision = 0;
203+
SEXP precision_sym = PROTECT(Rf_install("precision"));
204+
SEXP precision_sexp = Rf_getAttrib(sfc, precision_sym);
205+
UNPROTECT(1);
206+
if (precision_sexp != R_NilValue && Rf_length(precision_sexp) == 1) {
207+
switch (TYPEOF(precision_sexp)) {
208+
case INTSXP:
209+
precision = (double)INTEGER(precision_sexp)[0];
210+
break;
211+
case REALSXP:
212+
precision = REAL(precision_sexp)[0];
213+
break;
214+
default:
215+
break;
216+
}
217+
}
218+
167219
struct GeoArrowError error;
168220
error.message[0] = '\0';
169221

@@ -174,7 +226,7 @@ SEXP geoarrow_c_as_nanoarrow_array_sfc(SEXP sfc, SEXP schema_xptr, SEXP array_xp
174226
}
175227

176228
// Build the offset buffers from the various layers of nesting
177-
result = builder_append_sfc(sfc, builder);
229+
result = builder_append_sfc(sfc, builder, precision);
178230
if (result != GEOARROW_OK) {
179231
Rf_error("builder_append_sfc() failed to allocate memory for offset buffers");
180232
}

tests/testthat/test-pkg-sf.R

+41
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,26 @@ test_that("as_nanoarrow_array() works for sfc_POINT", {
238238
)
239239
})
240240

241+
test_that("as_nanoarrow_array() works for sfc_POINT with precision", {
242+
skip_if_not_installed("sf")
243+
244+
sfc <- sf::st_sfc(
245+
sf::st_point(c(1.33333333, 2.3333333)),
246+
sf::st_point(c(3.33333333, 4.3333333))
247+
)
248+
sf::st_precision(sfc) <- 100
249+
250+
array <- as_nanoarrow_array(sfc)
251+
expect_identical(
252+
as.raw(array$children[[1]]$buffers[[2]]),
253+
as.raw(nanoarrow::as_nanoarrow_buffer(c(1.33, 3.33)))
254+
)
255+
expect_identical(
256+
as.raw(array$children[[2]]$buffers[[2]]),
257+
as.raw(nanoarrow::as_nanoarrow_buffer(c(2.33, 4.33)))
258+
)
259+
})
260+
241261
test_that("as_nanoarrow_array() works for sfc_POINT with mixed XYZ dimensions", {
242262
skip_if_not_installed("sf")
243263

@@ -302,6 +322,27 @@ test_that("as_nanoarrow_array() works for sfc_LINESTRING", {
302322
)
303323
})
304324

325+
test_that("as_nanoarrow_array() works for sfc_LINESTRING with precision", {
326+
skip_if_not_installed("sf")
327+
328+
sfc <- sf::st_sfc(
329+
sf::st_linestring(
330+
rbind(c(1.3333333, 2.33333333), c(3.3333333, 4.3333333))
331+
)
332+
)
333+
sf::st_precision(sfc) <- 100
334+
335+
array <- as_nanoarrow_array(sfc)
336+
expect_identical(
337+
as.raw(array$children[[1]]$children[[1]]$buffers[[2]]),
338+
as.raw(nanoarrow::as_nanoarrow_buffer(c(1.33, 3.33)))
339+
)
340+
expect_identical(
341+
as.raw(array$children[[1]]$children[[2]]$buffers[[2]]),
342+
as.raw(nanoarrow::as_nanoarrow_buffer(c(2.33, 4.33)))
343+
)
344+
})
345+
305346
test_that("as_nanoarrow_array() works for sfc_LINESTRING with mixed XYZ dimensions", {
306347
skip_if_not_installed("sf")
307348

0 commit comments

Comments
 (0)