Skip to content

Commit

Permalink
add h3.KRING_INDEXED and quadkey.KRING_INDEXED (#144)
Browse files Browse the repository at this point in the history
* add h3.KRING_INDEXED and quadkey.KRING_INDEXED

* Quadkey kring indexed (#145)

* Add h3.KRING_INDEXED and quadkey.KRING_INDEXED

* remove kring_hollow

* add KRING_INDEXED test

* add KRING_INDEXED docs and update tests

* remove trailing comma

* fix tabs

* fix tabs

* delete s2

* fix camelCase

* fix erroring

* update changelogs
  • Loading branch information
francois-baptiste authored Aug 4, 2021
1 parent 8cdba5a commit 5f369d7
Show file tree
Hide file tree
Showing 11 changed files with 379 additions and 17 deletions.
5 changes: 5 additions & 0 deletions modules/h3/bigquery/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.2] - 2021-08-04

### Added
- Add KRING_INDEXED function.

## [1.0.1] - 2021-04-09

### Changed
Expand Down
53 changes: 53 additions & 0 deletions modules/h3/bigquery/doc/KRING_INDEXED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
### KRING_INDEXED

{{% bannerNote type="code" %}}
h3.KRING_INDEXED(index, distance)
{{%/ bannerNote %}}

**Description**

Returns an array with the indexes and their `distance` in term of cell to the given input hexagon of all hexagons within `distance`. The order of the hexagons is undefined. Returns `null` on invalid input.

* `index`: `STRING` The H3 cell index.
* `distance`: `INT64` distance (in number of cells) to the source.

**Return type**

`ARRAY<STRUCT<idx STRING, distance INT64>>`

{{% customSelector %}}
**Example**
{{%/ customSelector %}}

```sql
SELECT carto-os.h3.KRING_INDEXED('837b59fffffffff', 1) mykring_indexed;
-- "mykring_indexed": [
{
"idx": "837b59fffffffff",
"distance": "0"
},
{
"idx": "837b5dfffffffff",
"distance": "1"
},
{
"idx": "837b58fffffffff",
"distance": "1"
},
{
"idx": "837b5bfffffffff",
"distance": "1"
},
{
"idx": "837a66fffffffff",
"distance": "1"
},
{
"idx": "837a64fffffffff",
"distance": "1"
},
{
"idx": "837b4afffffffff",
"distance": "1"
}
]
19 changes: 19 additions & 0 deletions modules/h3/bigquery/sql/KRING_INDEXED.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
----------------------------
-- Copyright (C) 2021 CARTO
----------------------------

CREATE OR REPLACE FUNCTION `@@BQ_PREFIX@@h3.KRING_INDEXED`
(idx STRING, distance INT64)
RETURNS ARRAY<STRUCT<idx STRING, distance INT64>>
DETERMINISTIC
LANGUAGE js
OPTIONS (library=["@@BQ_LIBRARY_BUCKET@@"])
AS """
if (!idx || distance == null || distance < 0) {
return null;
}
if (!h3Lib.h3IsValid(idx)) {
return null;
}
return Array.from(Array(parseInt(distance)+1).keys()).map(x => h3Lib.hexRing(idx, x).map(idx => ({idx:idx, distance:x}))).flat();
""";
46 changes: 46 additions & 0 deletions modules/h3/bigquery/test/integration/KRING_INDEXED.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { runQuery } = require('../../../../../common/bigquery/test-utils');


test('KRING_INDEXED should work', async () => {
const query = `
WITH kring_data AS
( SELECT myrow,
\`@@BQ_PREFIX@@h3.KRING_INDEXED\`(idx, distance) as kring_elem,
FROM UNNEST([
STRUCT(1 as myrow, "invalid_index" as idx, 1 as distance),
STRUCT(2, "8928308280fffff", NULL),
STRUCT(3, "8928308280fffff", 1),
STRUCT(4, "8928308280fffff", 3)
]))
SELECT
myrow,
-- use STRING_AGG to deal with null
STRING_AGG(CAST(ke.distance as STRING) ORDER BY ke.idx) as kring_distance,
STRING_AGG(CAST(ke.idx as STRING) ORDER BY ke.idx) as kring_idx
FROM kring_data left join UNNEST(kring_elem) as ke
GROUP BY myrow
`;
const myrows = await runQuery(query);
expect(myrows.map(r => r.kring_distance)).toEqual(
[
null,
null,
'1,1,1,0,1,1,1',
'1,1,1,0,2,2,2,2,3,2,3,2,3,1,3,2,3,3,2,2,2,3,3,1,1,2,3,3,3,2,3,3,3,3,3,3,3'
]);

expect(myrows.map(r => r.kring_idx)).toEqual(
[
null,
null,
'89283082803ffff,89283082807ffff,8928308280bffff,8928308280fffff,8928308283bffff,89283082873ffff,89283082877ffff',
'89283082803ffff,89283082807ffff,8928308280bffff,8928308280fffff,89283082813ffff,89283082817ffff,8928308281bffff,89283082823ffff,89283082827ffff,8928308282bffff,8928308282fffff,89283082833ffff,89283082837ffff,8928308283bffff,89283082843ffff,89283082847ffff,8928308284fffff,89283082853ffff,89283082857ffff,89283082863ffff,89283082867ffff,8928308286bffff,8928308286fffff,89283082873ffff,89283082877ffff,8928308287bffff,8928308288bffff,8928308288fffff,892830828a3ffff,892830828abffff,892830828afffff,892830828bbffff,892830828c7ffff,892830828cfffff,89283082ab7ffff,89283082b93ffff,89283082b9bffff'
]);
});

test('KRING_INDEXED should fail with NULL argument', async () => {
const query = `
SELECT \`@@BQ_PREFIX@@h3.KRING_INDEXED\`(NULL)
`;
await expect(runQuery(query)).rejects.toThrow();
});
6 changes: 6 additions & 0 deletions modules/quadkey/bigquery/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.2] - 2021-08-04

### Added
- Add KRING_INDEXED function.
- Add ST_GEOGPOINTFROMQUADINT function.

## [1.0.1] - 2021-04-16

### Changed
Expand Down
72 changes: 72 additions & 0 deletions modules/quadkey/bigquery/doc/KRING_INDEXED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
### KRING_INDEXED

{{% bannerNote type="code" %}}
quadkey.KRING_INDEXED(quadint, distance)
{{%/ bannerNote %}}

**Description**

Returns an array containing all the quadints and their relative position to the given quadint in term of x and y. Quadints returned are directly next to the given quadint at the same level of zoom. Diagonal, horizontal and vertical nearby quadints plus the current quadint are considered, so KRING_INDEXED always returns `(distance*2 + 1)^2` quadints.

* `quadint`: `INT64` quadint to get the KRING_INDEXED from.
* `distance`: `INT64` distance (in cells) to the source.

**Return type**

`ARRAY<STRUCT<x INT64, y INT64, idx INT64>>`


{{% customSelector %}}
**Example**
{{%/ customSelector %}}

```sql
SELECT carto-os.quadkey.KRING_INDEXED(4388, 1) mykring_indexed;
-- "mykring_indexed": [
{
"x": "-1",
"y": "-1",
"idx": "3844"
},
{
"x": "0",
"y": "-1",
"idx": "3876"
},
{
"x": "1",
"y": "-1",
"idx": "3908"
},
{
"x": "-1",
"y": "0",
"idx": "4356"
},
{
"x": "0",
"y": "0",
"idx": "4388"
},
{
"x": "1",
"y": "0",
"idx": "4420"
},
{
"x": "-1",
"y": "1",
"idx": "4868"
},
{
"x": "0",
"y": "1",
"idx": "4900"
},
{
"x": "1",
"y": "1",
"idx": "4932"
}
]
```
2 changes: 2 additions & 0 deletions modules/quadkey/bigquery/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { version } from '../package.json';
import {
bbox,
kring,
kring_indexed,
sibling,
toParent,
toChildren,
Expand All @@ -17,6 +18,7 @@ import {
export default {
bbox,
kring,
kring_indexed,
sibling,
toParent,
toChildren,
Expand Down
117 changes: 100 additions & 17 deletions modules/quadkey/bigquery/lib/quadkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,25 +117,70 @@ export function sibling (quadint, direction) {
if (direction !== 'left' && direction !== 'right' && direction !== 'up' && direction !== 'down') {
throw new Error('Wrong direction argument passed to sibling');
}

const tile = ZXYFromQuadint(quadint);
const z = tile.z;
let x = tile.x;
let y = tile.y;
const tilesPerLevel = 2 << (z - 1);
if (direction === 'left') {
x = x > 0 ? x - 1 : tilesPerLevel - 1;
return siblingLeft(quadint);
}
if (direction === 'right') {
x = x < tilesPerLevel - 1 ? x + 1 : 0;
return siblingRight(quadint);
}
if (direction === 'up') {
y = y > 0 ? y - 1 : tilesPerLevel - 1;
return siblingUp(quadint);
}
if (direction === 'down') {
y = y < tilesPerLevel - 1 ? y + 1 : 0;
return siblingDown(quadint);
}
return quadintFromZXY(z, x, y);
}

/**
* returns the sibling of the given quadint and will wrap
* @param {int} quadint key to get sibling of
* @param {string} direction direction of sibling from key
* @return {int} sibling key
*/
export function siblingLeft (quadint) {
const tile = ZXYFromQuadint(quadint);
const tilesPerLevel = 2 << (tile.z - 1);
const x = tile.x > 0 ? tile.x - 1 : tilesPerLevel - 1;
return quadintFromZXY(tile.z, x, tile.y);
}

/**
* returns the sibling of the given quadint and will wrap
* @param {int} quadint key to get sibling of
* @param {string} direction direction of sibling from key
* @return {int} sibling key
*/
export function siblingRight (quadint) {
const tile = ZXYFromQuadint(quadint);
const tilesPerLevel = 2 << (tile.z - 1);
const x = tile.x < tilesPerLevel - 1 ? tile.x + 1 : 0;
return quadintFromZXY(tile.z, x, tile.y);
}

/**
* returns the sibling of the given quadint and will wrap
* @param {int} quadint key to get sibling of
* @param {string} direction direction of sibling from key
* @return {int} sibling key
*/
export function siblingUp (quadint) {
const tile = ZXYFromQuadint(quadint);
const tilesPerLevel = 2 << (tile.z - 1);
const y = tile.y > 0 ? tile.y - 1 : tilesPerLevel - 1;
return quadintFromZXY(tile.z, tile.x, y);
}

/**
* returns the sibling of the given quadint and will wrap
* @param {int} quadint key to get sibling of
* @param {string} direction direction of sibling from key
* @return {int} sibling key
*/
export function siblingDown (quadint) {
const tile = ZXYFromQuadint(quadint);
const tilesPerLevel = 2 << (tile.z - 1);
const y = tile.y < tilesPerLevel - 1 ? tile.y + 1 : 0;
return quadintFromZXY(tile.z, tile.x, y);
}

/**
Expand Down Expand Up @@ -193,16 +238,19 @@ export function toParent (quadint, resolution) {
* @return {int} kring of the input quadint
*/
export function kring (quadint, distance) {
if (distance < 1) {
throw new Error('Wrong kring distance');
if (distance < 0) {
throw new Error('Kring distance should be at least zero');
}
if (distance === 0) {
return [quadint];
}

let i, j;
let cornerQuadint = quadint;
// Traverse to top left corner
for (i = 0; i < distance; i++) {
cornerQuadint = sibling(cornerQuadint, 'left');
cornerQuadint = sibling(cornerQuadint, 'up');
cornerQuadint = siblingLeft(cornerQuadint);
cornerQuadint = siblingUp(cornerQuadint)
}

const neighbors = [];
Expand All @@ -211,9 +259,44 @@ export function kring (quadint, distance) {
traversalQuadint = cornerQuadint;
for (i = 0; i < distance * 2 + 1; i++) {
neighbors.push(traversalQuadint);
traversalQuadint = sibling(traversalQuadint, 'right');
traversalQuadint = siblingRight(traversalQuadint);
}
cornerQuadint = siblingDown(cornerQuadint)
}
return neighbors;
}

/**
* get the kring of a quadint
* @param {int} quadint quadint to get the kring of
* @param {int} distance in tiles of the desired kring
* @return {int} kring of the input quadint
*/
export function kring_indexed (quadint, distance) {
if (distance < 0) {
throw new Error('Wrong kring distance');
}
if (distance === 0) {
return [quadint];
}

let i, j;
let cornerQuadint = quadint;
// Traverse to top left corner
for (i = 0; i < distance; i++) {
cornerQuadint = siblingLeft(cornerQuadint);
cornerQuadint = siblingUp(cornerQuadint)
}

const neighbors = [];
let traversalQuadint;
for (j = -distance; j <= distance; j++) {
traversalQuadint = cornerQuadint;
for (i = -distance; i <= distance; i++) {
neighbors.push({ 'x':i,'y':j,'idx':traversalQuadint.toString() });
traversalQuadint = siblingRight(traversalQuadint);
}
cornerQuadint = sibling(cornerQuadint, 'down');
cornerQuadint = siblingDown(cornerQuadint)
}
return neighbors;
}
Expand Down
Loading

0 comments on commit 5f369d7

Please sign in to comment.