diff --git a/NEWS b/NEWS index 91f1d8c..a2a0272 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,7 @@ - PC_Lib_Version(), PC_Script_Version() (#40) - PC_Sort(pcpatch,text[]) (#106) - PC_IsSorted(pcpatch,text[],boolean) (#106) + - PC_Range(pcpatch, int, int) returns pcpatch (#152) - Enhancements - Support sigbits encoding for 64bit integers (#61) - Warn about truncated values (#68) diff --git a/README.md b/README.md index f69b605..8974815 100644 --- a/README.md +++ b/README.md @@ -471,6 +471,9 @@ Now that you have created two tables, you'll see entries for them in the `pointc > Returns a copy of the input patch lexicographically sorted along the given dimensions. +**PC_Range(p pcpatch, start int4, n int4)** returns **pcpatch** + +> Returns a patch containing *n* points. These points are selected from the *start*-th point with 1-based indexing. ## PostGIS Integration ## diff --git a/lib/cunit/cu_pc_patch.c b/lib/cunit/cu_pc_patch.c index 897bad4..8e33028 100644 --- a/lib/cunit/cu_pc_patch.c +++ b/lib/cunit/cu_pc_patch.c @@ -1,7 +1,7 @@ /*********************************************************************** * cu_pc_schema.c * -* Testing for the schema API functions +* Testing for the schema API functions * * Portions Copyright (c) 2012, OpenGeo * @@ -594,7 +594,7 @@ test_patch_compress_from_ght_to_lazperf() pc_patch_free((PCPATCH *)patch_ght); pc_patch_free((PCPATCH *)patch_lazperf); } -#endif /* defined(HAVE_LIBGHT) && defined(HAVE_LAZPERF) */ +#endif /* defined(HAVE_LIBGHT) && defined(HAVE_LAZPERF) */ static void test_patch_pointn_last_first() @@ -765,6 +765,232 @@ test_patch_pointn_ght_compression() pc_pointlist_free(li); } +static void +test_patch_range_compression_none() +{ + int i; + int npts = 20; + PCPOINTLIST *pl; + PCPATCH *pa; + PCPATCH *par; + char *str; + + pl = pc_pointlist_make(npts); + + for ( i = 0; i < npts; i++ ) + { + PCPOINT *pt = pc_point_make(simpleschema); + pc_point_set_double_by_name(pt, "X", i); + pc_point_set_double_by_name(pt, "Y", i); + pc_point_set_double_by_name(pt, "Z", i * 0.1); + pc_point_set_double_by_name(pt, "Intensity", 100 - i); + pc_pointlist_add_point(pl, pt); + } + + pa = (PCPATCH*)pc_patch_uncompressed_from_pointlist(pl); + par = pc_patch_range(pa, 16, 4); + str = pc_patch_to_string(par); + + CU_ASSERT_STRING_EQUAL(str, + "{\"pcid\":0,\"pts\":[[15,15,1.5,85],[16,16,1.6,84],[17,17,1.7,83],[18,18,1.8,82]]}"); + + pcfree(str); + pc_patch_free(par); + pc_patch_free(pa); + pc_pointlist_free(pl); +} + +static void +test_patch_range_compression_none_with_full_range() +{ + int i; + int npts = 4; + PCPOINTLIST *pl; + PCPATCH *pa; + PCPATCH *par; + char *str; + + pl = pc_pointlist_make(npts); + + for ( i = 0; i < npts; i++ ) + { + PCPOINT *pt = pc_point_make(simpleschema); + pc_point_set_double_by_name(pt, "X", i); + pc_point_set_double_by_name(pt, "Y", i); + pc_point_set_double_by_name(pt, "Z", i * 0.1); + pc_point_set_double_by_name(pt, "Intensity", 100 - i); + pc_pointlist_add_point(pl, pt); + } + + pa = (PCPATCH*)pc_patch_uncompressed_from_pointlist(pl); + par = pc_patch_range(pa, 1, npts); + CU_ASSERT(pa == par); + + str = pc_patch_to_string(par); + CU_ASSERT_STRING_EQUAL(str, + "{\"pcid\":0,\"pts\":[[0,0,0,100],[1,1,0.1,99],[2,2,0.2,98],[3,3,0.3,97]]}"); + + pcfree(str); + pc_patch_free(pa); + pc_pointlist_free(pl); +} + +static void +test_patch_range_compression_none_with_bad_arguments(int first, int count) +{ + int i; + int npts = 20; + PCPOINTLIST *pl; + PCPATCH *pa; + PCPATCH *par; + + pl = pc_pointlist_make(npts); + + for ( i = 0; i < npts; i++ ) + { + PCPOINT *pt = pc_point_make(simpleschema); + pc_point_set_double_by_name(pt, "X", i); + pc_point_set_double_by_name(pt, "Y", i); + pc_point_set_double_by_name(pt, "Z", i * 0.1); + pc_point_set_double_by_name(pt, "Intensity", 100 - i); + pc_pointlist_add_point(pl, pt); + } + + pa = (PCPATCH*)pc_patch_uncompressed_from_pointlist(pl); + par = pc_patch_range(pa, first, count); + CU_ASSERT(par == NULL); + + pc_patch_free(pa); + pc_pointlist_free(pl); +} + +static void +test_patch_range_compression_none_with_zero_count() +{ + test_patch_range_compression_none_with_bad_arguments(1, 0); +} + +static void +test_patch_range_compression_none_with_zero_first() +{ + test_patch_range_compression_none_with_bad_arguments(0, 1); +} + +static void +test_patch_range_compression_none_with_out_of_bounds_first() +{ + test_patch_range_compression_none_with_bad_arguments(21, 1); +} + +static void +test_patch_range_compression_lazperf() +{ + int i; + int npts = 20; + PCPOINTLIST *pl; + PCPATCH *pa; + PCPATCH *par; + char *str; + + pl = pc_pointlist_make(npts); + + for ( i = 0; i < npts; i++ ) + { + PCPOINT *pt = pc_point_make(simpleschema); + pc_point_set_double_by_name(pt, "X", i); + pc_point_set_double_by_name(pt, "Y", i); + pc_point_set_double_by_name(pt, "Z", i * 0.1); + pc_point_set_double_by_name(pt, "Intensity", 100 - i); + pc_pointlist_add_point(pl, pt); + } + + pa = (PCPATCH*)pc_patch_lazperf_from_pointlist(pl); + par = pc_patch_range(pa, 16, 4); + str = pc_patch_to_string(par); + + CU_ASSERT_STRING_EQUAL(str, + "{\"pcid\":0,\"pts\":[[15,15,1.5,85],[16,16,1.6,84],[17,17,1.7,83],[18,18,1.8,82]]}"); + + pcfree(str); + pc_patch_free(par); + pc_patch_free(pa); + pc_pointlist_free(pl); +} + +static void +test_patch_range_compression_dimensional(enum DIMCOMPRESSIONS dimcomp) +{ + int i; + PCPOINTLIST *pl; + PCPATCH *pa; + PCPATCH *par; + PCPATCH_DIMENSIONAL *pad; + PCPOINT *pt; + char *str; + int npts = PCDIMSTATS_MIN_SAMPLE+1; // force to keep custom compression + + // build a dimensional patch + pl = pc_pointlist_make(npts); + + for ( i = npts; i >= 0; i-- ) + { + pt = pc_point_make(simpleschema); + pc_point_set_double_by_name(pt, "X", i); + pc_point_set_double_by_name(pt, "Y", i); + pc_point_set_double_by_name(pt, "Z", i); + pc_point_set_double_by_name(pt, "Intensity", 10); + pc_pointlist_add_point(pl, pt); + } + + pad = pc_patch_dimensional_from_pointlist(pl); + + // set dimensional compression for each dimension + PCDIMSTATS *stats = pc_dimstats_make(simpleschema); + pc_dimstats_update(stats, pad); + for ( i = 0; ischema->ndims; i++ ) + stats->stats[i].recommended_compression = dimcomp; + + // compress patch + pa = (PCPATCH*) pc_patch_dimensional_compress(pad, stats); + + par = pc_patch_range(pa, 16, 4); + str = pc_patch_to_string(par); + + CU_ASSERT_STRING_EQUAL(str, + "{\"pcid\":0,\"pts\":[[9986,9986,9986,10],[9985,9985,9985,10],[9984,9984,9984,10],[9983,9983,9983,10]]}"); + + pcfree(str); + pc_patch_free(par); + pc_patch_free((PCPATCH *)pad); + pc_dimstats_free(stats); + pc_patch_free(pa); + pc_pointlist_free(pl); +} + +static void +test_patch_range_compression_dimensional_none() +{ + test_patch_range_compression_dimensional(PC_DIM_NONE); +} + +static void +test_patch_range_compression_dimensional_zlib() +{ + test_patch_range_compression_dimensional(PC_DIM_ZLIB); +} + +static void +test_patch_range_compression_dimensional_sigbits() +{ + test_patch_range_compression_dimensional(PC_DIM_SIGBITS); +} + +static void +test_patch_range_compression_dimensional_rle() +{ + test_patch_range_compression_dimensional(PC_DIM_RLE); +} + /* REGISTER ***********************************************************/ CU_TestInfo patch_tests[] = { @@ -788,6 +1014,18 @@ CU_TestInfo patch_tests[] = { PC_TEST(test_patch_pointn_dimensional_compression_sigbits), PC_TEST(test_patch_pointn_dimensional_compression_rle), PC_TEST(test_patch_pointn_ght_compression), + PC_TEST(test_patch_range_compression_none), + PC_TEST(test_patch_range_compression_none_with_full_range), + PC_TEST(test_patch_range_compression_none_with_zero_count), + PC_TEST(test_patch_range_compression_none_with_zero_first), + PC_TEST(test_patch_range_compression_none_with_out_of_bounds_first), + PC_TEST(test_patch_range_compression_dimensional_none), + PC_TEST(test_patch_range_compression_dimensional_zlib), + PC_TEST(test_patch_range_compression_dimensional_sigbits), + PC_TEST(test_patch_range_compression_dimensional_rle), +#ifdef HAVE_LAZPERF + PC_TEST(test_patch_range_compression_lazperf), +#endif CU_TEST_INFO_NULL }; diff --git a/lib/pc_api.h b/lib/pc_api.h index 525934f..b83c219 100644 --- a/lib/pc_api.h +++ b/lib/pc_api.h @@ -458,4 +458,7 @@ PCPATCH *pc_patch_sort(const PCPATCH *pa, const char **name, int ndims); /** True/false if the patch is sorted on dimension */ uint32_t pc_patch_is_sorted(const PCPATCH *pa, const char **name, int ndims, char strict); +/** Subset batch based on index */ +PCPATCH* pc_patch_range(const PCPATCH *pa, int first, int count); + #endif /* _PC_API_H */ diff --git a/lib/pc_patch.c b/lib/pc_patch.c index 0bb0867..33bff4b 100644 --- a/lib/pc_patch.c +++ b/lib/pc_patch.c @@ -306,7 +306,7 @@ PCPATCH * pc_patch_from_wkb(const PCSCHEMA *s, uint8_t *wkb, size_t wkbsize) { /* - byte: endianness (1 = NDR, 0 = XDR) + byte: endianness (1 = NDR, 0 = XDR) uint32: pcid (key to POINTCLOUD_SCHEMAS) uint32: compression (0 = no compression, 1 = dimensional, 2 = GHT) uchar[]: data (interpret relative to pcid and compression) @@ -378,7 +378,7 @@ uint8_t * pc_patch_to_wkb(const PCPATCH *patch, size_t *wkbsize) { /* - byte: endianness (1 = NDR, 0 = XDR) + byte: endianness (1 = NDR, 0 = XDR) uint32: pcid (key to POINTCLOUD_SCHEMAS) uint32: compression (0 = no compression, 1 = dimensional, 2 = GHT) uchar[]: data (interpret relative to pcid and compression) @@ -522,6 +522,68 @@ pc_patch_from_patchlist(PCPATCH **palist, int numpatches) return (PCPATCH*)paout; } +// first: the first element to select (1-based indexing) +// count: the number of points to select +PCPATCH * +pc_patch_range(const PCPATCH *pa, int first, int count) +{ + PCPATCH_UNCOMPRESSED *paout, *pu; + int countmax; + uint8_t *buf; + size_t size; + size_t start; + + assert(pa); + + first--; + countmax = pa->npoints - first; + + if ( count > countmax ) + count = countmax; + + if ( first < 0 || count <= 0 ) + return NULL; + + if ( count == pa->npoints ) + return (PCPATCH *) pa; + + paout = pc_patch_uncompressed_make(pa->schema, count); + if ( !paout ) + return NULL; + paout->npoints = count; + + pu = (PCPATCH_UNCOMPRESSED *) pc_patch_uncompress(pa); + if ( !pu ) + { + pc_patch_free((PCPATCH *) paout); + return NULL; + } + + buf = paout->data; + start = pa->schema->size * first; + size = pa->schema->size * count; + + memcpy(buf, pu->data + start, size); + + if ( ((PCPATCH *) pu) != pa ) + pc_patch_free((PCPATCH *) pu); + + if ( PC_FAILURE == pc_patch_uncompressed_compute_extent(paout) ) + { + pcerror("%s: extent computation failed", __func__); + pc_patch_free((PCPATCH *) paout); + return NULL; + } + if ( PC_FAILURE == pc_patch_uncompressed_compute_stats(paout) ) + { + pcerror("%s: stats computation failed", __func__); + pc_patch_free((PCPATCH *) paout); + return NULL; + } + + return (PCPATCH *) paout; +} + /** get point n from patch */ /** positive 1-based: 1=first point, npoints=last point */ /** negative 1-based: -1=last point, -npoints=first point */ diff --git a/pgsql/expected/pointcloud.out b/pgsql/expected/pointcloud.out index 50f6082..57f8b0a 100644 --- a/pgsql/expected/pointcloud.out +++ b/pgsql/expected/pointcloud.out @@ -312,6 +312,15 @@ SELECT sum(PC_NumPoints(pa)) FROM pa_test; 8 (1 row) +SELECT PC_AsText(PC_Range(pa, 1, 1)) FROM pa_test; + pc_astext +--------------------------------------- + {"pcid":1,"pts":[[0.02,0.03,0.05,6]]} + {"pcid":1,"pts":[[0.06,0.07,0.05,6]]} + {"pcid":1,"pts":[[0.06,0.07,0.05,6]]} + {"pcid":1,"pts":[[0.06,0.07,0.05,6]]} +(4 rows) + CREATE TABLE IF NOT EXISTS pa_test_dim ( pa PCPATCH(3) ); diff --git a/pgsql/pc_access.c b/pgsql/pc_access.c index 23a41ed..b926e39 100644 --- a/pgsql/pc_access.c +++ b/pgsql/pc_access.c @@ -28,6 +28,7 @@ Datum pcpatch_uncompress(PG_FUNCTION_ARGS); Datum pcpatch_compress(PG_FUNCTION_ARGS); Datum pcpatch_numpoints(PG_FUNCTION_ARGS); Datum pcpatch_pointn(PG_FUNCTION_ARGS); +Datum pcpatch_range(PG_FUNCTION_ARGS); Datum pcpatch_pcid(PG_FUNCTION_ARGS); Datum pcpatch_summary(PG_FUNCTION_ARGS); Datum pcpatch_compression(PG_FUNCTION_ARGS); @@ -677,6 +678,29 @@ Datum pcpatch_pointn(PG_FUNCTION_ARGS) PG_RETURN_POINTER(serpt); } +PG_FUNCTION_INFO_V1(pcpatch_range); +Datum pcpatch_range(PG_FUNCTION_ARGS) +{ + SERIALIZED_PATCH *serpaout; + SERIALIZED_PATCH *serpa = PG_GETARG_SERPATCH_P(0); + int32 first = PG_GETARG_INT32(1); + int32 count = PG_GETARG_INT32(2); + PCSCHEMA *schema = pc_schema_from_pcid(serpa->pcid, fcinfo); + PCPATCH *patch = pc_patch_deserialize(serpa, schema); + PCPATCH *patchout = NULL; + if ( patch ) + { + patchout = pc_patch_range(patch, first, count); + if ( patchout != patch ) + pc_patch_free(patch); + } + if ( !patchout ) + PG_RETURN_NULL(); + serpaout = pc_patch_serialize(patchout, NULL); + pc_patch_free(patchout); + PG_RETURN_POINTER(serpaout); +} + PG_FUNCTION_INFO_V1(pcpatch_pcid); Datum pcpatch_pcid(PG_FUNCTION_ARGS) { diff --git a/pgsql/pointcloud.sql.in b/pgsql/pointcloud.sql.in index f3b333b..59c5852 100644 --- a/pgsql/pointcloud.sql.in +++ b/pgsql/pointcloud.sql.in @@ -309,6 +309,10 @@ CREATE OR REPLACE FUNCTION PC_IsSorted(p pcpatch, attr text[], strict boolean de RETURNS boolean AS 'MODULE_PATHNAME', 'pcpatch_is_sorted' LANGUAGE 'c' IMMUTABLE STRICT; +CREATE OR REPLACE FUNCTION PC_Range(p pcpatch, first int4, count int4) + RETURNS pcpatch AS 'MODULE_PATHNAME', 'pcpatch_range' + LANGUAGE 'c' IMMUTABLE STRICT; + ------------------------------------------------------------------- -- POINTCLOUD_COLUMNS ------------------------------------------------------------------- diff --git a/pgsql/sql/pointcloud.sql b/pgsql/sql/pointcloud.sql index a0669ab..205e34b 100644 --- a/pgsql/sql/pointcloud.sql +++ b/pgsql/sql/pointcloud.sql @@ -232,6 +232,8 @@ SELECT PC_Envelope(pa) from pa_test; SELECT PC_AsText(PC_Union(pa)) FROM pa_test; SELECT sum(PC_NumPoints(pa)) FROM pa_test; +SELECT PC_AsText(PC_Range(pa, 1, 1)) FROM pa_test; + CREATE TABLE IF NOT EXISTS pa_test_dim ( pa PCPATCH(3) );