This is an automated email from the ASF dual-hosted git repository.

jackie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 456e425920 Add `gridDisk` H3 Function (#15349)
456e425920 is described below

commit 456e4259209aeafd63699a83845976a3c4eb24f4
Author: ashishjayamohan <[email protected]>
AuthorDate: Sat Mar 29 17:23:17 2025 -0700

    Add `gridDisk` H3 Function (#15349)
---
 .../common/function/TransformFunctionType.java     |   1 +
 .../transform/function/GridDiskFunction.java       |  90 +++++++++++++++
 .../transform/function/ScalarFunctions.java        |  20 ++++
 .../function/TransformFunctionFactory.java         |   2 +
 .../transform/function/GridDiskFunctionTest.java   | 122 +++++++++++++++++++++
 5 files changed, 235 insertions(+)

diff --git 
a/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java
 
b/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java
index 75693e3719..05ee16a5e2 100644
--- 
a/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java
+++ 
b/pinot-common/src/main/java/org/apache/pinot/common/function/TransformFunctionType.java
@@ -221,6 +221,7 @@ public enum TransformFunctionType {
       OperandTypes.or(OperandTypes.family(SqlTypeFamily.BINARY, 
SqlTypeFamily.INTEGER),
           OperandTypes.family(SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC, 
SqlTypeFamily.INTEGER))),
   GRID_DISTANCE("gridDistance", ReturnTypes.BIGINT, 
OperandTypes.NUMERIC_NUMERIC),
+  GRID_DISK("gridDisk", ReturnTypes.BIGINT, OperandTypes.NUMERIC_NUMERIC),
 
   // Vector functions
   // TODO: Once VECTOR type is defined, we should update here.
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/GridDiskFunction.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/GridDiskFunction.java
new file mode 100644
index 0000000000..393caa10c5
--- /dev/null
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/GridDiskFunction.java
@@ -0,0 +1,90 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.core.geospatial.transform.function;
+
+import com.google.common.base.Preconditions;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.pinot.core.operator.ColumnContext;
+import org.apache.pinot.core.operator.blocks.ValueBlock;
+import org.apache.pinot.core.operator.transform.TransformResultMetadata;
+import org.apache.pinot.core.operator.transform.function.BaseTransformFunction;
+import org.apache.pinot.core.operator.transform.function.TransformFunction;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.roaringbitmap.RoaringBitmap;
+
+/**
+ * Function that returns all H3 indexes within a specified hexagonal grid 
distance from a given origin index.
+ * The function takes two arguments:
+ *  - gridDisk(origin, k)
+ */
+public class GridDiskFunction extends BaseTransformFunction {
+  public static final String FUNCTION_NAME = "gridDisk";
+  private TransformFunction _originArgument;
+  private TransformFunction _kArgument;
+
+  @Override
+  public String getName() {
+    return FUNCTION_NAME;
+  }
+
+  @Override
+  public void init(List<TransformFunction> arguments, Map<String, 
ColumnContext> columnContextMap) {
+    super.init(arguments, columnContextMap);
+    Preconditions.checkArgument(arguments.size() == 2, "Transform function %s 
requires 2 arguments", getName());
+
+    TransformFunction transformFunction = arguments.get(0);
+    
Preconditions.checkArgument(transformFunction.getResultMetadata().isSingleValue(),
+        "First argument (origin) must be single-valued for transform function: 
%s", getName());
+    _originArgument = transformFunction;
+
+    transformFunction = arguments.get(1);
+    
Preconditions.checkArgument(transformFunction.getResultMetadata().isSingleValue(),
+        "Second argument (k) must be single-valued for transform function: 
%s", getName());
+    _kArgument = transformFunction;
+  }
+
+  @Override
+  public TransformResultMetadata getResultMetadata() {
+    // Returns a list of H3 indexes, so it's multi-valued
+    return new TransformResultMetadata(FieldSpec.DataType.LONG, false, false);
+  }
+
+  @Nullable
+  public RoaringBitmap getNullBitmap(ValueBlock valueBlock) {
+    return null;
+  }
+
+  @Override
+  public long[][] transformToLongValuesMV(ValueBlock valueBlock) {
+    int numDocs = valueBlock.getNumDocs();
+    initLongValuesMV(numDocs);
+
+    long[] origins = _originArgument.transformToLongValuesSV(valueBlock);
+    int[] ks = _kArgument.transformToIntValuesSV(valueBlock);
+
+    for (int i = 0; i < numDocs; i++) {
+      long[] diskIndexes = ScalarFunctions.gridDisk(origins[i], ks[i]);
+      _longValuesMV[i] = diskIndexes;
+    }
+
+    return _longValuesMV;
+  }
+}
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ScalarFunctions.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ScalarFunctions.java
index b1ffb1396a..b6390afa85 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ScalarFunctions.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ScalarFunctions.java
@@ -18,6 +18,7 @@
  */
 package org.apache.pinot.core.geospatial.transform.function;
 
+import java.util.List;
 import org.apache.pinot.segment.local.utils.GeometrySerializer;
 import org.apache.pinot.segment.local.utils.GeometryUtils;
 import org.apache.pinot.segment.local.utils.H3Utils;
@@ -267,4 +268,23 @@ public class ScalarFunctions {
     }
     return H3Utils.H3_CORE.gridDistance(firstH3Index, secondH3Index);
   }
+
+  /**
+   * Returns all H3 indexes within a specified hexagonal grid distance from a 
given origin index. The function considers
+   * hexagonal traversal rules and accounts for wrapping around pentagons.
+   *
+   * @param origin The starting H3 index from which to compute the grid disk.
+   * @param k      The radius of the disk in hexagonal grid steps.
+   * @return An array of H3 indexes within the specified distance from the 
origin.
+   */
+  @ScalarFunction
+  public static long[] gridDisk(long origin, int k) {
+    List<Long> diskCells = H3Utils.H3_CORE.gridDisk(origin, k);
+    long[] result = new long[diskCells.size()];
+    int index = 0;
+    for (Long cell : diskCells) {
+      result[index++] = cell;
+    }
+    return result;
+  }
 }
diff --git 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java
 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java
index 55b67a4254..b2521e7681 100644
--- 
a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java
+++ 
b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java
@@ -34,6 +34,7 @@ import 
org.apache.pinot.common.request.context.FunctionContext;
 import org.apache.pinot.common.request.context.LiteralContext;
 import org.apache.pinot.common.utils.HashUtil;
 import org.apache.pinot.core.geospatial.transform.function.GeoToH3Function;
+import org.apache.pinot.core.geospatial.transform.function.GridDiskFunction;
 import 
org.apache.pinot.core.geospatial.transform.function.GridDistanceFunction;
 import org.apache.pinot.core.geospatial.transform.function.StAreaFunction;
 import org.apache.pinot.core.geospatial.transform.function.StAsBinaryFunction;
@@ -212,6 +213,7 @@ public class TransformFunctionFactory {
     // geo indexing
     typeToImplementation.put(TransformFunctionType.GEO_TO_H3, 
GeoToH3Function.class);
     typeToImplementation.put(TransformFunctionType.GRID_DISTANCE, 
GridDistanceFunction.class);
+    typeToImplementation.put(TransformFunctionType.GRID_DISK, 
GridDiskFunction.class);
 
     // tuple selection
     typeToImplementation.put(TransformFunctionType.LEAST, 
LeastTransformFunction.class);
diff --git 
a/pinot-core/src/test/java/org/apache/pinot/core/geospatial/transform/function/GridDiskFunctionTest.java
 
b/pinot-core/src/test/java/org/apache/pinot/core/geospatial/transform/function/GridDiskFunctionTest.java
new file mode 100644
index 0000000000..4b38a765ad
--- /dev/null
+++ 
b/pinot-core/src/test/java/org/apache/pinot/core/geospatial/transform/function/GridDiskFunctionTest.java
@@ -0,0 +1,122 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.core.geospatial.transform.function;
+
+import com.uber.h3core.H3Core;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.pinot.common.request.context.ExpressionContext;
+import org.apache.pinot.common.request.context.FunctionContext;
+import org.apache.pinot.core.operator.ColumnContext;
+import 
org.apache.pinot.core.operator.transform.function.BaseTransformFunctionTest;
+import org.apache.pinot.core.operator.transform.function.TransformFunction;
+import 
org.apache.pinot.core.operator.transform.function.TransformFunctionFactory;
+import org.apache.pinot.segment.spi.datasource.DataSource;
+import org.apache.pinot.spi.data.FieldSpec;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+public class GridDiskFunctionTest extends BaseTransformFunctionTest {
+  @BeforeClass
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+  }
+
+  @Test
+  public void testGridDisk() throws IOException {
+    H3Core h3Core = H3Core.newInstance();
+    // Test point in San Francisco
+    double lat = 37.7749;
+    double lng = -122.4194;
+    long h3Index = h3Core.latLngToCell(lat, lng, 9);
+
+    // Test with k=1 (immediate neighbors)
+    int k = 1;
+
+    // Create expected values
+    List<Long> expectedDisk = h3Core.gridDisk(h3Index, k);
+    long[][] expectedValues = new long[NUM_ROWS][];
+    Arrays.fill(expectedValues, 
expectedDisk.stream().mapToLong(Long::longValue).toArray());
+
+    // Create the transform function
+    ExpressionContext h3Context = 
ExpressionContext.forLiteral(FieldSpec.DataType.LONG, h3Index);
+    ExpressionContext kContext = 
ExpressionContext.forLiteral(FieldSpec.DataType.INT, k);
+    TransformFunction transformFunction = TransformFunctionFactory.get(
+        ExpressionContext.forFunction(new 
FunctionContext(FunctionContext.Type.TRANSFORM, "gridDisk",
+            Arrays.asList(h3Context, kContext))), _dataSourceMap);
+
+    Map<String, ColumnContext> columnContextMap = new HashMap<>();
+    for (Map.Entry<String, DataSource> entry : _dataSourceMap.entrySet()) {
+      columnContextMap.put(entry.getKey(), 
ColumnContext.fromDataSource(entry.getValue()));
+    }
+    transformFunction.init(Arrays.asList(
+        TransformFunctionFactory.get(h3Context, _dataSourceMap),
+        TransformFunctionFactory.get(kContext, _dataSourceMap)), 
columnContextMap);
+
+    long[][] result = 
transformFunction.transformToLongValuesMV(_projectionBlock);
+    assertArrayEquals(result, expectedValues);
+
+    // Test with k=0 (only the center)
+    k = 0;
+    expectedDisk = h3Core.gridDisk(h3Index, k);
+    expectedValues = new long[NUM_ROWS][];
+    Arrays.fill(expectedValues, 
expectedDisk.stream().mapToLong(Long::longValue).toArray());
+
+    kContext = ExpressionContext.forLiteral(FieldSpec.DataType.INT, k);
+    transformFunction = TransformFunctionFactory.get(
+        ExpressionContext.forFunction(new 
FunctionContext(FunctionContext.Type.TRANSFORM, "gridDisk",
+            Arrays.asList(h3Context, kContext))), _dataSourceMap);
+    transformFunction.init(Arrays.asList(
+        TransformFunctionFactory.get(h3Context, _dataSourceMap),
+        TransformFunctionFactory.get(kContext, _dataSourceMap)), 
columnContextMap);
+
+    result = transformFunction.transformToLongValuesMV(_projectionBlock);
+    assertArrayEquals(result, expectedValues);
+
+    // Test with k=2 (two rings of neighbors)
+    k = 2;
+    expectedDisk = h3Core.gridDisk(h3Index, k);
+    expectedValues = new long[NUM_ROWS][];
+    Arrays.fill(expectedValues, 
expectedDisk.stream().mapToLong(Long::longValue).toArray());
+
+    kContext = ExpressionContext.forLiteral(FieldSpec.DataType.INT, k);
+    transformFunction = TransformFunctionFactory.get(
+        ExpressionContext.forFunction(new 
FunctionContext(FunctionContext.Type.TRANSFORM, "gridDisk",
+            Arrays.asList(h3Context, kContext))), _dataSourceMap);
+    transformFunction.init(Arrays.asList(
+        TransformFunctionFactory.get(h3Context, _dataSourceMap),
+        TransformFunctionFactory.get(kContext, _dataSourceMap)), 
columnContextMap);
+
+    result = transformFunction.transformToLongValuesMV(_projectionBlock);
+    assertArrayEquals(result, expectedValues);
+  }
+
+  private void assertArrayEquals(long[][] actual, long[][] expected) {
+    assertEquals(actual.length, expected.length);
+    for (int i = 0; i < actual.length; i++) {
+      assertEquals(actual[i], expected[i]);
+    }
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to