Skip to content

Commit

Permalink
H3_POLYFILL using SF's H3_POLYGON_TO_CELLS_STRINGS
Browse files Browse the repository at this point in the history
  • Loading branch information
DeanSherwin committed Mar 26, 2024
1 parent 0b05214 commit 09b6612
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 63 deletions.
7 changes: 5 additions & 2 deletions clouds/snowflake/libraries/javascript/libs/h3_polyfill.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { polyfill } from '../src/h3/h3_polyfill/h3core_custom';
import { bboxClip } from '@turf/turf';
import { bboxClip, polygon, intersect, geometryCollection } from '@turf/turf';

export default {
bboxClip,
polyfill
polyfill,
polygon,
intersect,
geometryCollection
};
2 changes: 1 addition & 1 deletion clouds/snowflake/modules/doc/h3/H3_POLYFILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ H3_POLYFILL(geography, resolution)

Returns an array with all the H3 cell indexes **with centers** contained in a given polygon. It will return `null` on error (invalid geography type or resolution out of bounds).

* `geography`: `GEOGRAPHY` **polygon** or **multipolygon** representing the shape to cover.
* `geography`: `GEOGRAPHY` **polygon** or **multipolygon** representing the shape to cover. **GeometryCollections** are also allowed but they must only contain **polygon** or **multipolygon** geographies.
* `resolution`: `INT` number between 0 and 15 with the [H3 resolution](https://h3geo.org/docs/core-library/restable).

**Return type**
Expand Down
96 changes: 36 additions & 60 deletions clouds/snowflake/modules/sql/h3/H3_POLYFILL.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,58 @@
-- Copyright (C) 2021 CARTO
----------------------------

CREATE OR REPLACE FUNCTION @@SF_SCHEMA@@._H3_POLYFILL
(geojson STRING, input_resolution DOUBLE)
RETURNS ARRAY
CREATE OR REPLACE FUNCTION @@SF_SCHEMA@@._HEMI_SPLIT
(geojson STRING)
RETURNS STRING
LANGUAGE JAVASCRIPT
IMMUTABLE
AS $$
if (!GEOJSON || INPUT_RESOLUTION == null) {
return [];
}

let inputGeoJSON = JSON.parse(GEOJSON);

@@SF_LIBRARY_H3_POLYFILL@@

const resolution = Number(INPUT_RESOLUTION);
if (resolution < 0 || resolution > 15) {
return [];
}
const westernHemisphere = h3PolyfillLib.polygon([[ [-180, 90], [0, 90], [0, -90], [-180, -90], [-180, 90]]]);
const easternHemisphere = h3PolyfillLib.polygon([[ [0, 90], [180, 90], [180, -90], [0, -90], [0, 90] ]]);

const bboxA = [-180, -90, 0, 90]
const bboxB = [0, -90, 180, 90]
const featureGeometry = JSON.parse(GEOJSON)
let polygonCoordinatesA = [];
let polygonCoordinatesB = [];
switch(featureGeometry.type) {
case 'GeometryCollection':
featureGeometry.geometries.forEach(function (geom) {
if (geom.type === 'MultiPolygon') {
var clippedGeometryA = h3PolyfillLib.bboxClip(geom, bboxA).geometry;
polygonCoordinatesA = polygonCoordinatesA.concat(clippedGeometryA.coordinates);
var clippedGeometryB = h3PolyfillLib.bboxClip(geom, bboxB).geometry;
polygonCoordinatesB = polygonCoordinatesB.concat(clippedGeometryB.coordinates);
} else if (geom.type === 'Polygon') {
var clippedGeometryA = h3PolyfillLib.bboxClip(geom, bboxA).geometry;
polygonCoordinatesA = polygonCoordinatesA.concat([clippedGeometryA.coordinates]);
var clippedGeometryB = h3PolyfillLib.bboxClip(geom, bboxB).geometry;
polygonCoordinatesB = polygonCoordinatesB.concat([clippedGeometryB.coordinates]);
}
});
break;
case 'MultiPolygon':
var clippedGeometryA = h3PolyfillLib.bboxClip(featureGeometry, bboxA).geometry;
polygonCoordinatesA = clippedGeometryA.coordinates;
var clippedGeometryB = h3PolyfillLib.bboxClip(featureGeometry, bboxB).geometry;
polygonCoordinatesB = clippedGeometryB.coordinates;
break;
case 'Polygon':
var clippedGeometryA = h3PolyfillLib.bboxClip(featureGeometry, bboxA).geometry;
polygonCoordinatesA = [clippedGeometryA.coordinates];
var clippedGeometryB = h3PolyfillLib.bboxClip(featureGeometry, bboxB).geometry;
polygonCoordinatesB = [clippedGeometryB.coordinates];
break;
default:
return [];
}
let polygons = [];

if (polygonCoordinatesA.length + polygonCoordinatesB.length === 0) {
return [];
if (inputGeoJSON.type == "GeometryCollection") {
inputGeoJSON.geometries.forEach(g => {
if (g.type === 'Polygon'|| g.type === 'MultiPolygon') {
polygons.push(g)
}
});
}
else if (inputGeoJSON.type === 'Polygon' || inputGeoJSON.type === 'MultiPolygon') {
polygons.push(inputGeoJSON)
}

let hexesA = polygonCoordinatesA.reduce(
(acc, coordinates) => acc.concat(h3PolyfillLib.polyfill(coordinates, resolution, true)),
[]
).filter(h => h != null);
let hexesB = polygonCoordinatesB.reduce(
(acc, coordinates) => acc.concat(h3PolyfillLib.polyfill(coordinates, resolution, true)),
[]
).filter(h => h != null);
hexes = [...hexesA, ...hexesB];
hexes = [...new Set(hexes)];
let intersections = [];

let intersectAndPush = (hemisphere, poly) => {
const intersection = h3PolyfillLib.intersect(poly, hemisphere);
if (intersection) {
intersections.push(intersection);
}
};

polygons.forEach(p => {
intersectAndPush(westernHemisphere, p);
intersectAndPush(easternHemisphere, p);
})

return hexes;
return JSON.stringify(h3PolyfillLib.geometryCollection(intersections.map(i => i.geometry)));
$$;

CREATE OR REPLACE SECURE FUNCTION @@SF_SCHEMA@@.H3_POLYFILL
(geog GEOGRAPHY, resolution INT)
RETURNS ARRAY
IMMUTABLE
AS $$
@@SF_SCHEMA@@._H3_POLYFILL(CAST(ST_ASGEOJSON(GEOG) AS STRING), CAST(RESOLUTION AS DOUBLE))
IFF(
GEOG IS NOT NULL AND RESOLUTION BETWEEN 0 AND 15,
COALESCE(H3_POLYGON_TO_CELLS_STRINGS(TO_GEOGRAPHY(@@SF_SCHEMA@@._HEMI_SPLIT(CAST(ST_ASGEOJSON(GEOG) AS STRING))), RESOLUTION), []),
[]
)
$$;

0 comments on commit 09b6612

Please sign in to comment.