Browse Source

HPCC-22215 Add ECLIndex representation to H3 plugin

ECL friendly (STRING16) variant of the h3 index.

Signed-off-by: Gordon Smith <gordonjsmith@gmail.com>
Gordon Smith 6 years ago
parent
commit
7835681c6c
6 changed files with 134 additions and 2 deletions
  1. 2 0
      .gitignore
  2. 43 1
      plugins/h3/README.md
  3. 57 1
      plugins/h3/h3.cpp
  4. 7 0
      plugins/h3/lib_h3.ecllib
  5. 10 0
      testing/regress/ecl/h3.ecl
  6. 15 0
      testing/regress/ecl/key/h3.xml

+ 2 - 0
.gitignore

@@ -7,3 +7,5 @@ node_modules
 esp/src/lib
 esp/src/build
 .vscode/
+.eclcc/
+eclcc.log

+ 43 - 1
plugins/h3/README.md

@@ -154,7 +154,7 @@ distortion. This is the same set of limitations as the local IJ coordinate space
 h3_index_t parent(CONST h3_index_t idx, CONST h3_resolution_t resolution)
 ```
 
-Returns the parent (coarser) index containing `idx` as resolution `resolution`.
+Returns the parent (coarser) index containing `idx` at resolution `resolution`.
 
 #### children
 
@@ -229,3 +229,45 @@ UNSIGNED8 numHexagons(CONST h3_resolution_t resolution)
 
 Number of unique **H3** indexes at the given `resolution`.
 
+### ECL Optimized
+_The following functions provide an ECL friendly representation of the h3 index.  Specifically, it is a `STRING16` format where each character represents a finer precision coordinate location (one character per resolution).  This allows for quick equality and parentage checking using string slicing techniques._ 
+
+#### ECLIndex
+
+```c
+STRING16 ECLIndex(CONST h3_degrees_t lat, CONST h3_degrees_t lng, CONST h3_resolution_t resolution)
+```
+
+Indexes the location at the specified `lat`, `lng` and `resolution` (0->15).
+
+#### toECLIndex
+
+```c
+STRING16 toECLIndex(CONST h3_index_t h3Idx)
+```
+
+Converts a h3Index to an ECL Index.
+
+#### fromECLIndex
+
+```c
+h3_index_t fromECLIndex(CONST STRING16 eclIdx)
+```
+
+Converts an ECL Index to a h3Index.
+
+#### ECLIndexResolution
+
+```c
+h3_resolution_t ECLIndexResolution(CONST STRING16 eclIdx)
+```
+
+Returns the resolution of the `eclIdx`.  This is equivilant to:  `LENGTH(TRIM(eclIdx)) - 1`
+
+#### ECLIndexParent
+
+```c
+STRING16 ECLIndexParent(CONST STRING16 eclIdx, CONST h3_resolution_t resolution)
+```
+
+Returns the parent (coarser) index containing `eclIdx` at resolution `resolution`.  This is equivilant to `eclIdx[1..resolution + 1]`

+ 57 - 1
plugins/h3/h3.cpp

@@ -44,7 +44,6 @@ ECL_H3_API bool getECLPluginDefinition(ECLPluginDefinitionBlock *pb)
 }
 
 #include <h3api.h>
-#define H3_INDEX_MINSTRLEN 17
 
 static void toSetOf(bool &__isAllResult, size32_t &__lenResult, void *&__result, H3Index *buff, int maxBuffLen)
 {
@@ -117,6 +116,7 @@ ECL_H3_API uint32_t ECL_H3_CALL baseCell(ICodeContext *_ctx, unsigned __int64 in
     return ::h3GetBaseCell(index);
 }
 
+#define H3_INDEX_MINSTRLEN 17
 ECL_H3_API void ECL_H3_CALL toString(ICodeContext *_ctx, size32_t &lenVarStr, char *&varStr, unsigned __int64 index)
 {
     varStr = static_cast<char *>(rtlMalloc(H3_INDEX_MINSTRLEN));
@@ -275,4 +275,60 @@ ECL_H3_API unsigned __int64 ECL_H3_CALL numHexagons(ICodeContext *_ctx, uint32_t
     return ::numHexagons(resolution);
 }
 
+// ECL Optimized  ---
+const unsigned __int64 MASK_CELL_BASE = 0b01111111;
+const unsigned __int64 MASK_CELL_RES = 0b00000111;
+const unsigned __int64 MASK_RES = 0b00001111;
+#define MASK_GET(valToMask, mask, shift) ((valToMask) >> (shift)) & (mask)
+#define MASK_SET(valToMask, mask, shift, val) (((valToMask) & ~(static_cast<unsigned __int64>(mask) << (shift))) | (static_cast<unsigned __int64>(val) << (shift)))
+
+ECL_H3_API void ECL_H3_CALL toECLIndex(ICodeContext *_ctx, char * __result, unsigned __int64 index)
+{
+    for (unsigned int i = 0; i < 16; ++i) {
+        const unsigned char resVal = MASK_GET(index, i == 0 ? MASK_CELL_BASE : MASK_CELL_RES, (15 - i) * 3);
+        __result[i] = ((i == 0 && resVal == MASK_CELL_BASE) || (i > 0 && resVal == MASK_CELL_RES)) ? ' ' : '0' + resVal;
+    }
+}
+
+ECL_H3_API void ECL_H3_CALL ECLIndex(ICodeContext *_ctx, char * __result, double lat, double lon, uint32_t resolution)
+{
+    return toECLIndex(_ctx, __result, index(_ctx, lat, lon, resolution));
+}
+
+ECL_H3_API unsigned __int64 ECL_H3_CALL fromECLIndex(ICodeContext *_ctx, const char *eclIndex)
+{
+    unsigned __int64 __result = 646078419604526808; //  lat:0, lng:0, res:15
+    unsigned __int64 res = 0;
+    for (unsigned int i = 0; i < 16; ++i) {
+        unsigned char resVal = eclIndex[i];
+        if (resVal == ' ') {
+            resVal = i == 0 ? MASK_CELL_BASE : MASK_CELL_RES;
+        } else {
+            resVal -= '0';
+            res = i;
+        }
+        __result = MASK_SET(__result, i == 0 ? MASK_CELL_BASE : MASK_CELL_RES, (15 - i) * 3, resVal);
+    }
+    return MASK_SET(__result, MASK_RES, 52, res);
+}
+
+ECL_H3_API uint32_t ECL_H3_CALL ECLIndexResolution(ICodeContext *_ctx, const char *eclIndex)
+{
+    //  This is equivilant to:  LENGTH(TRIM(eclIndex)) - 1;
+    for (unsigned int i = 1; i < 16; ++i) {
+        if (eclIndex[i] == ' ') {
+            return i - 1;
+        }
+    }
+    return 15;
+}
+
+ECL_H3_API void ECL_H3_CALL ECLIndexParent(ICodeContext *_ctx, char * __result, const char *eclIndex, uint32_t resolution)
+{
+    //  This is equivilant to:  eclIndex[1..resolution + 1];
+    for (unsigned int i = 0; i < 16; ++i) {
+        __result[i] = i <= resolution ? eclIndex[i] : ' ';
+    }
+}
+
 } // namespace h3

+ 7 - 0
plugins/h3/lib_h3.ecllib

@@ -58,4 +58,11 @@ EXPORT h3 := SERVICE : plugin('h3plugin'), library('h3'), namespace('h3'), CPP,
     REAL8 hexAreaKm2(CONST h3_resolution_t resolution) : cpp,action,context,fold,entrypoint='hexAreaKm2';
     REAL8 hexAreaM2(CONST h3_resolution_t resolution) : cpp,action,context,fold,entrypoint='hexAreaM2';
     UNSIGNED8 numHexagons(CONST h3_resolution_t resolution) : cpp,action,context,fold,entrypoint='numHexagons';
+
+    // ECL Optimized  ---
+    STRING16 ECLIndex(CONST h3_degrees_t lat, CONST h3_degrees_t lng, CONST h3_resolution_t resolution) : cpp,action,context,fold,entrypoint='ECLIndex';
+    STRING16 toECLIndex(CONST h3_index_t h3Idx) : cpp,action,context,fold,entrypoint='toECLIndex';
+    h3_index_t fromECLIndex(CONST STRING16 eclIdx) : cpp,action,context,fold,entrypoint='fromECLIndex';
+    h3_resolution_t ECLIndexResolution(CONST STRING16 eclIdx) : cpp,action,context,fold,entrypoint='ECLIndexResolution';
+    STRING16 ECLIndexParent(CONST STRING16 eclIdx, CONST h3_resolution_t resolution) : cpp,action,context,fold,entrypoint='ECLIndexParent';
 END;

+ 10 - 0
testing/regress/ecl/h3.ecl

@@ -72,3 +72,13 @@ h3.radsToDegs(h3.degsToRads(42));
 h3.hexAreaKm2(12);
 h3.hexAreaM2(12);
 h3.numHexagons(4);
+
+//  ECL Index;
+eclIdx := h3.ECLIndex(lat, lng, res);
+h3.fromECLIndex(eclIdx) = h3Idx;
+h3.toECLIndex(h3Idx) = eclIdx;
+eclIdxRes5 := h3.ECLIndexParent(eclIdx, 5);
+LENGTH(TRIM(eclIdxRes5)) - 1 = 5;
+TRIM(eclIdxRes5) = eclIdx[1..6];
+h3.resolution(h3.fromECLIndex(eclIdxRes5)) = 5;
+

+ 15 - 0
testing/regress/ecl/key/h3.xml

@@ -87,3 +87,18 @@
 <Dataset name='Result 28'>
  <Row><Result_28>288122</Result_28></Row>
 </Dataset>
+<Dataset name='Result 29'>
+ <Row><Result_29>true</Result_29></Row>
+</Dataset>
+<Dataset name='Result 30'>
+ <Row><Result_30>true</Result_30></Row>
+</Dataset>
+<Dataset name='Result 31'>
+ <Row><Result_31>true</Result_31></Row>
+</Dataset>
+<Dataset name='Result 32'>
+ <Row><Result_32>true</Result_32></Row>
+</Dataset>
+<Dataset name='Result 33'>
+ <Row><Result_33>true</Result_33></Row>
+</Dataset>