\pset tuples_only on
-- Variables for testing
\set resolution 10
\set hexagon '\'8a63a9a99047fff\''
\set meter ST_SetSRID(ST_Point(6196902.235389061,1413172.0833316022), 3857)
\set degree ST_SetSRID(ST_Point(55.6677199224442,12.592131261648213), 4326)
\set edgecross '\'8003fffffffffff\'::h3index'
\set polar '\'81f2bffffffffff\'::h3index'
\set lat1 84.76455330449812
\set lat2 89.980298101841
\set epsilon 0.0000000001
\set longPathEndpoint1 '\'842ca2bffffffff\'::h3index'
\set longPathEndpoint2 '\'842e611ffffffff\'::h3index'
-- polygon with 2 holes
\set with2holes '\'POLYGON((31.6520834 68.9912098,31.6521263 68.9910944,31.6530919 68.9912021,31.6540789 68.9912944,31.6550016 68.991279,31.6553449 68.9910175,31.6554737 68.9907713,31.6559458 68.990402,31.6563535 68.9900789,31.6568684 68.9897404,31.6569543 68.9895711,31.6567182 68.988671,31.6569114 68.9881786,31.6571045 68.9879401,31.6567611 68.9875708,31.6570401 68.9873015,31.6576195 68.986986,31.6581774 68.9868398,31.6584992 68.9870553,31.6586065 68.9872707,31.6590786 68.9873246,31.6594219 68.9872091,31.6596579 68.9870091,31.6596579 68.9867706,31.6601515 68.9866167,31.6607308 68.9864551,31.660409 68.986232,31.6601729 68.9860627,31.6605806 68.9859319,31.6614818 68.9859011,31.6620183 68.9857087,31.6622972 68.9854471,31.6628337 68.9852932,31.6633701 68.9852855,31.663928 68.9855702,31.6640782 68.9859088,31.6636705 68.9862166,31.6639924 68.9864859,31.664443 68.9868629,31.664679 68.9872091,31.6642928 68.9873784,31.6641426 68.9876939,31.6650009 68.9879016,31.6652155 68.9881555,31.6653657 68.9883709,31.6659665 68.9886941,31.6662884 68.9889941,31.666739 68.989248,31.6669321 68.9891095,31.6670394 68.9888787,31.6681123 68.9889326,31.6687345 68.989325,31.6692495 68.9895865,31.6701937 68.9897635,31.6710949 68.9897404,31.6725325 68.9897558,31.6733479 68.9898558,31.6743135 68.9904097,31.674571 68.990702,31.6747641 68.9909328,31.6745066 68.9911405,31.6738844 68.9912636,31.6731977 68.9914175,31.6734981 68.9916098,31.6739487 68.9915713,31.6744852 68.9915175,31.6750431 68.9914021,31.6751718 68.9911944,31.6752147 68.9910405,31.6756439 68.9910636,31.6765451 68.9912021,31.6777253 68.9912944,31.6784119 68.9912482,31.6790771 68.9911559,31.6793346 68.9913021,31.6787553 68.9916713,31.678133 68.9920867,31.6780472 68.9924483,31.6782617 68.9927098,31.6792702 68.9941098,31.6794419 68.9943636,31.6801715 68.9945328,31.6817808 68.9946328,31.6825533 68.9948174,31.6827249 68.995202,31.683712 68.9957404,31.6840124 68.9962634,31.684699 68.9965556,31.6848492 68.9968171,31.6841197 68.9969632,31.6831326 68.9969479,31.6827464 68.9969171,31.6824031 68.9968556,31.6821456 68.9967248,31.6813302 68.9968248,31.6810083 68.9971094,31.6806865 68.9971863,31.6802358 68.9971401,31.6792702 68.9967018,31.6787553 68.9963864,31.6780901 68.9958327,31.6777038 68.9956788,31.6766095 68.9955635,31.6762233 68.9954943,31.6759658 68.9952943,31.6753649 68.9951481,31.6746354 68.9952712,31.6735625 68.9951866,31.6728329 68.9951866,31.6726398 68.9953943,31.6719746 68.9955173,31.6709661 68.9954173,31.6704941 68.9951558,31.6700434 68.9948251,31.669743 68.9944867,31.6695285 68.994179,31.6693783 68.9939329,31.6690779 68.993756,31.6680479 68.9935867,31.663692 68.9929329,31.6628551 68.9927637,31.661675 68.9927637,31.6610527 68.9929637,31.6605377 68.9929714,31.6599583 68.9929406,31.6588855 68.9928175,31.658349 68.992656,31.657598 68.9923714,31.6567826 68.9922329,31.6552162 68.9920714,31.6541648 68.9919175,31.6531348 68.9916483,31.6523838 68.9914175,31.6520834 68.9912098),(31.657185 68.990202,31.657244 68.9903482,31.6574585 68.9903809,31.6578555 68.9903597,31.6581452 68.9902539,31.6583651 68.9900924,31.6582364 68.9899366,31.6578716 68.9898616,31.6575766 68.9898731,31.6573083 68.9899808,31.6572332 68.9900731,31.657185 68.990202),(31.6590196 68.9886094,31.6591644 68.9887229,31.6594058 68.9888537,31.6595936 68.9889557,31.6596794 68.9890441,31.6597116 68.9891345,31.659776 68.9892519,31.6599476 68.9891903,31.660012 68.9890614,31.6601139 68.9889383,31.6602641 68.9888326,31.6602749 68.988721,31.6601998 68.988596,31.6600603 68.988369,31.6601676 68.9882709,31.6604251 68.9882844,31.6607255 68.9882728,31.6609454 68.9881767,31.6605592 68.9880901,31.6604841 68.9879689,31.6603607 68.9878497,31.660071 68.9878843,31.6597867 68.9879959,31.6593575 68.9880305,31.6592127 68.9881132,31.6592234 68.9882421,31.6592127 68.9883786,31.6590196 68.9886094))\''::geometry(POLYGON)
-- S-shaped polygon with 4 holes crossing antimeridian
\set transmeridianWithHoles '\'POLYGON((-170 12.5, 170 12.5, 170 7.5, -175 7.5, -175 2.5, 170 2.5, 170 -12.5, -170 -12.5, -170 -7.5, 175 -7.5, 175 -2.5, -170 -2.5, -170 12.5), (-176 11.5, -179 11.5, -179 8.5, -176 8.5, -176 11.5), (174 11.5, 171 11.5, 171 8.5, 174 8.5, 174 11.5), (174 -3.5, 171 -3.5, 171 -6.5, 174 -6.5, 174 -3.5),(-176 -8.5, -179 -8.5, -179 -11.5, -176 -11.5, -176 -8.5))\''::geometry(POLYGON)
-- multipolygon with 2 polygons: one crossing and one not crossing antimeridian
\set transmeridianMulti '\'MULTIPOLYGON(((-175 50, -175 55, 175 55, 175 50, -175 50)), ((170 50, 170 55, 165 55, 165 50, 170 50)))\''::geometry(MULTIPOLYGON)


SELECT h3_latlng_to_cell(:degree, :resolution) = '8a63a9a99047fff';

-- meters are NOT reprojected
SELECT h3_latlng_to_cell(:meter, :resolution) <> '8a63a9a99047fff';

-- check back/forth conversion return same hex
SELECT h3_latlng_to_cell(h3_cell_to_geometry(:hexagon), :resolution) = '8a63a9a99047fff';

-- check num points in boundary
SELECT ST_NPoints(h3_cell_to_boundary_geometry(:hexagon)) = 7;

-- PostgreSQL 17+ maintenance ops run with restricted search_path
CREATE TABLE h3_pg17_idx_test (
    h3cell h3index
);
INSERT INTO h3_pg17_idx_test VALUES (:hexagon);
CREATE INDEX h3_pg17_idx_test_gix
    ON h3_pg17_idx_test
    USING GIST (h3_cell_to_geometry(h3cell));
DROP INDEX h3_pg17_idx_test_gix;

CREATE MATERIALIZED VIEW h3_pg17_mv_test AS
SELECT h3_cell_to_geometry(h3cell) AS geom
FROM h3_pg17_idx_test;
REFRESH MATERIALIZED VIEW h3_pg17_mv_test;
DROP MATERIALIZED VIEW h3_pg17_mv_test;
DROP TABLE h3_pg17_idx_test;

-- test strict h3_latlng_to_cell throws for bad latlon
CREATE FUNCTION h3_test_postgis_nounit() RETURNS boolean LANGUAGE PLPGSQL
    AS $$
        BEGIN
            PERFORM h3_latlng_to_cell(POINT(360, 2.592131261648213), 1);
            RETURN false;
        EXCEPTION WHEN OTHERS THEN
            RETURN true;
        END;
    $$;
SET h3.strict TO true;
SELECT h3_test_postgis_nounit();
SET h3.strict TO false;
DROP FUNCTION h3_test_postgis_nounit;

-- Test wraparound
\set lon 55.6677199224442
\set lat 12.592131261648213
SELECT h3_latlng_to_cell(POINT(:lon,       :lat), 7)
     = h3_latlng_to_cell(POINT(:lon + 360, :lat), 7);

SELECT h3_latlng_to_cell(POINT(:lon, :lat      ), 7)
     = h3_latlng_to_cell(POINT(:lon, :lat + 360), 7);

-- test h3_grid_path_cells_recursive works for long path
SELECT COUNT(*) > 0 FROM (
    SELECT h3_grid_path_cells_recursive(:longPathEndpoint1, :longPathEndpoint1)
) q;

-- h3_polygon_to_cells works for polygon with two holes
SELECT COUNT(*) = 48 FROM (
    SELECT h3_polygon_to_cells(:with2holes, 10)
) q;

-- valid input example at resolution 7: polyfill of 7 known cells is stable
WITH origin AS (
    SELECT h3_latlng_to_cell(:degree, 7) AS h3
),
cells AS (
    SELECT h3_grid_disk(h3, 1) AS h3 FROM origin
),
poly AS (
    SELECT h3_cells_to_multi_polygon_geometry(array_agg(h3)) AS g FROM cells
)
SELECT COUNT(*) = 7
FROM poly, LATERAL h3_polygon_to_cells(g, 7) AS cell(h3);

-- WKB multipolygon conversion skips NULL indexes instead of reading NULL Datum
SELECT ST_Equals(
    h3_cells_to_multi_polygon_geometry(ARRAY[:hexagon::h3index, NULL::h3index]),
    h3_cells_to_multi_polygon_geometry(ARRAY[:hexagon::h3index])
);

-- H3 v4.5.0 rejects ambiguous duplicate-cell outlines instead of producing
-- undefined multipolygon output.
CREATE FUNCTION h3_test_cells_to_multi_polygon_geometry_duplicate() RETURNS boolean LANGUAGE PLPGSQL
    AS $$
        BEGIN
            PERFORM h3_cells_to_multi_polygon_geometry(ARRAY['880326b885fffff'::h3index, '880326b885fffff'::h3index]);
            RETURN false;
        EXCEPTION WHEN OTHERS THEN
            RETURN true;
        END;
    $$;
SELECT h3_test_cells_to_multi_polygon_geometry_duplicate();
DROP FUNCTION h3_test_cells_to_multi_polygon_geometry_duplicate;

-- invalid polygon can produce a very large number of cells (upstream H3 behavior)
SET client_min_messages TO warning;
\set invalid_res 5
WITH geom AS (
    SELECT ST_SetSRID(ST_GeomFromGeoJSON('{
      "type": "Polygon",
      "coordinates": [
        [
          [-148.5, 29.1],
          [-148.5, 63.9],
          [-72.5, 29.1],
          [-72.5, 63.9],
          [-148.5, 29.1]
        ]
      ]
    }'), 4326) AS g
)
SELECT ST_IsValid(g) = false FROM geom;

-- valid rectangle with same vertex set (different winding/order) is much larger
WITH geom AS (
    SELECT ST_SetSRID(ST_GeomFromGeoJSON('{
      "type": "Polygon",
      "coordinates": [
        [
          [-148.5, 29.1],
          [-148.5, 63.9],
          [-72.5, 63.9],
          [-72.5, 29.1],
          [-148.5, 29.1]
        ]
      ]
    }'), 4326) AS g
)
SELECT ST_IsValid(g) FROM geom;

WITH geom AS (
    SELECT ST_SetSRID(ST_GeomFromGeoJSON('{
      "type": "Polygon",
      "coordinates": [
        [
          [-148.5, 29.1],
          [-148.5, 63.9],
          [-72.5, 29.1],
          [-72.5, 63.9],
          [-148.5, 29.1]
        ]
      ]
    }'), 4326) AS g
)
SELECT COUNT(*) = 48256
FROM geom, LATERAL h3_polygon_to_cells(g, :invalid_res) AS cell(h3);

WITH geom AS (
    SELECT ST_SetSRID(ST_GeomFromGeoJSON('{
      "type": "Polygon",
      "coordinates": [
        [
          [-148.5, 29.1],
          [-148.5, 63.9],
          [-72.5, 63.9],
          [-72.5, 29.1],
          [-148.5, 29.1]
        ]
      ]
    }'), 4326) AS g
)
SELECT COUNT(*) = 90190
FROM geom, LATERAL h3_polygon_to_cells(g, :invalid_res) AS cell(h3);

-- ST_MakeValid() repair can return multipolygons / collections; keep polygonal parts
CREATE FUNCTION h3_test_makevalid_structure(geom geometry) RETURNS geometry LANGUAGE plpgsql
AS $$
DECLARE
    out geometry;
BEGIN
    BEGIN
        EXECUTE 'SELECT ST_MakeValid($1, ''method=structure'')' INTO out USING geom;
    EXCEPTION WHEN undefined_function THEN
        out := ST_MakeValid(geom);
    END;

    RETURN out;
END;
$$;

WITH geom AS (
    SELECT ST_SetSRID(ST_GeomFromGeoJSON('{
      "type": "Polygon",
      "coordinates": [
        [
          [-148.5, 29.1],
          [-148.5, 63.9],
          [-72.5, 29.1],
          [-72.5, 63.9],
          [-148.5, 29.1]
        ]
      ]
    }'), 4326) AS g
),
repaired AS (
    SELECT ST_CollectionExtract(h3_test_makevalid_structure(g), 3) AS g FROM geom
)
SELECT GeometryType(g) IN ('POLYGON', 'MULTIPOLYGON') FROM repaired;

WITH geom AS (
    SELECT ST_SetSRID(ST_GeomFromGeoJSON('{
      "type": "Polygon",
      "coordinates": [
        [
          [-148.5, 29.1],
          [-148.5, 63.9],
          [-72.5, 29.1],
          [-72.5, 63.9],
          [-148.5, 29.1]
        ]
      ]
    }'), 4326) AS g
),
repaired AS (
    SELECT ST_CollectionExtract(h3_test_makevalid_structure(g), 3) AS g FROM geom
)
SELECT ST_IsValid(g) FROM repaired;

WITH geom AS (
    SELECT ST_SetSRID(ST_GeomFromGeoJSON('{
      "type": "Polygon",
      "coordinates": [
        [
          [-148.5, 29.1],
          [-148.5, 63.9],
          [-72.5, 29.1],
          [-72.5, 63.9],
          [-148.5, 29.1]
        ]
      ]
    }'), 4326) AS g
),
repaired AS (
    SELECT ST_CollectionExtract(h3_test_makevalid_structure(g), 3) AS g FROM geom
)
SELECT COUNT(*) = 48256
FROM repaired, LATERAL h3_polygon_to_cells(g, :invalid_res) AS cell(h3);

DROP FUNCTION h3_test_makevalid_structure(geometry);
RESET client_min_messages;

-- geometrycollections of polygons are accepted by the PostGIS wrapper
WITH cells AS (
    SELECT h3_grid_disk(:hexagon, 1) AS h3
),
picked AS (
    SELECT h3 FROM cells ORDER BY h3::text LIMIT 2
),
gc AS (
    SELECT ST_SetSRID(
        ST_GeomFromText(
            'GEOMETRYCOLLECTION(' ||
            (SELECT ST_AsText(h3_cell_to_boundary(h3)::geometry) FROM picked OFFSET 0 LIMIT 1) || ',' ||
            (SELECT ST_AsText(h3_cell_to_boundary(h3)::geometry) FROM picked OFFSET 1 LIMIT 1) || ')'
        ),
        4326
    ) AS g
)
SELECT GeometryType(g) = 'GEOMETRYCOLLECTION' FROM gc;

WITH cells AS (
    SELECT h3_grid_disk(:hexagon, 1) AS h3
),
picked AS (
    SELECT h3 FROM cells ORDER BY h3::text LIMIT 2
),
gc AS (
    SELECT ST_SetSRID(
        ST_GeomFromText(
            'GEOMETRYCOLLECTION(' ||
            (SELECT ST_AsText(h3_cell_to_boundary(h3)::geometry) FROM picked OFFSET 0 LIMIT 1) || ',' ||
            (SELECT ST_AsText(h3_cell_to_boundary(h3)::geometry) FROM picked OFFSET 1 LIMIT 1) || ')'
        ),
        4326
    ) AS g
)
SELECT COUNT(*) = 2
FROM gc, LATERAL h3_polygon_to_cells(g, :resolution) AS cell(h3);

RESET client_min_messages;

--
-- Test h3_cell_to_boundary_wkb
--

-- polyfill of geo boundary returns original index
SELECT h3_polygon_to_cells(h3_cell_to_boundary(:hexagon)::geometry::polygon, null, :resolution) = :hexagon;

-- the boundary of a non-edgecrossing index is a polygon
SELECT GeometryType(h3_cell_to_boundary_wkb(:hexagon)::geometry) LIKE 'POLYGON';

-- the boundary of an edgecrossing index is a multipolygon when split
SELECT GeometryType(h3_cell_to_boundary_wkb(:edgecross)::geometry) LIKE 'MULTIPOLYGON';

-- the boundary of a polar cell is a polygon
SELECT GeometryType(h3_cell_to_boundary_wkb(:polar)::geometry) LIKE 'POLYGON';

-- check num points in polar cell boundary
SELECT ST_NPoints(h3_cell_to_boundary_geometry(:polar)) = 11;

-- check latitude of antimeridian crossing points
SET h3.split_antimeridian TO true;
SELECT every(ABS(ST_Y(p) - :lat1) < :epsilon OR ABS(ST_Y(p) - :lat2) < :epsilon)
FROM (
    SELECT (dp).geom AS p FROM (
        (SELECT ST_DumpPoints(h3_cell_to_boundary_wkb(:edgecross)::geometry) AS dp)
    ) AS q1
) AS q2
WHERE ABS(ABS(ST_X(p)) - 180) < :epsilon;

--
-- Test h3_cells_to_multi_polygon_wkb
--

-- polygon is split in 4 polygons with 1 hole each
WITH split AS (
         SELECT h3_cells_to_multi_polygon_wkb(
             array(SELECT h3_polygon_to_cells(:transmeridianWithHoles, 4)))::geometry AS multi),
     dp AS (SELECT ST_Dump(multi) AS dp FROM split),
     polygons AS (SELECT (dp).geom AS geom FROM dp)
SELECT GeometryType(geom) LIKE 'POLYGON' AND ST_NumInteriorRings(geom) = 1
FROM polygons;

-- multipolygon with 2 polygons becomes multipolygon with 3
WITH split AS (
        SELECT h3_cells_to_multi_polygon_wkb(
            array(SELECT h3_polygon_to_cells(:transmeridianMulti, 3)))::geometry AS multi),
     dp AS (SELECT ST_Dump(multi) AS dp FROM split)
SELECT COUNT(*) = 3 FROM dp;

-- exact world cover at res1 stays valid without special SQL repair
WITH cells AS (
    SELECT array_agg(h3 ORDER BY h3) AS arr
    FROM (
        SELECT h3_cell_to_children(h3, 1) AS h3
        FROM h3_get_res_0_cells() h3
    ) q
)
SELECT ST_IsValid(h3_cells_to_multi_polygon_geometry(arr))
   AND GeometryType(h3_cells_to_multi_polygon_geometry(arr)) = 'POLYGON'
FROM cells;

-- ordinary polar caps must stay valid through the coarse geometry path
WITH cells AS (
    SELECT array_agg(h3 ORDER BY h3) AS arr
    FROM (
        SELECT h3_grid_disk(:polar, 1) AS h3
    ) q
)
SELECT ST_IsValid(h3_cells_to_multi_polygon_geometry(arr))
FROM cells;

-- antimeridian neighborhoods exercise split/noded reverse-edge cancellation
WITH cells AS (
    SELECT array_agg(h3 ORDER BY h3) AS arr
    FROM (
        SELECT h3_grid_disk(:edgecross, 1) AS h3
    ) q
)
SELECT ST_IsValid(h3_cells_to_multi_polygon_geometry(arr))
   AND NOT ST_IsEmpty(h3_cells_to_multi_polygon_geometry(arr))
FROM cells;

-- representative low-zoom tile cover remains both valid and covering
WITH tile AS (
    SELECT ST_Transform(ST_TileEnvelope(3, 0, 1), 4326) AS tile,
           h3_get_resolution_from_tile_zoom(3) AS res
),
cells AS (
    SELECT array_agg(h3 ORDER BY h3) AS arr
    FROM tile
    CROSS JOIN LATERAL h3_polygon_to_cells_experimental(
        tile, res, 'overlapping_bbox'
    ) AS cell(h3)
)
SELECT ST_IsValid(h3_cells_to_multi_polygon_geometry(arr))
   AND ST_IsEmpty(ST_Difference((SELECT tile FROM tile), h3_cells_to_multi_polygon_geometry(arr)))
FROM cells;

-- south-polar low-zoom tile cover stays valid and covering
WITH tile AS (
    SELECT ST_Transform(ST_TileEnvelope(2, 1, 3), 4326) AS tile,
           h3_get_resolution_from_tile_zoom(2) AS res
),
cells AS (
    SELECT array_agg(h3 ORDER BY h3) AS arr
    FROM tile
    CROSS JOIN LATERAL h3_polygon_to_cells_experimental(
        tile, res, 'overlapping_bbox'
    ) AS cell(h3)
)
SELECT ST_IsValid(h3_cells_to_multi_polygon_geometry(arr))
   AND ST_IsEmpty(ST_Difference((SELECT tile FROM tile), h3_cells_to_multi_polygon_geometry(arr)))
FROM cells;

-- north-polar low-zoom tile cover stays valid and covering
WITH tile AS (
    SELECT ST_Transform(ST_TileEnvelope(3, 4, 1), 4326) AS tile,
           h3_get_resolution_from_tile_zoom(3) AS res
),
cells AS (
    SELECT array_agg(h3 ORDER BY h3) AS arr
    FROM tile
    CROSS JOIN LATERAL h3_polygon_to_cells_experimental(
        tile, res, 'overlapping_bbox'
    ) AS cell(h3)
)
SELECT ST_IsValid(h3_cells_to_multi_polygon_geometry(arr))
   AND ST_IsEmpty(ST_Difference((SELECT tile FROM tile), h3_cells_to_multi_polygon_geometry(arr)))
FROM cells;

-- buffered z1 world-edge tiles no longer error and stay valid as geometry
WITH tile AS (
    SELECT ST_Transform(ST_TileEnvelope(1, 0, 0, margin => 0.125), 4326) AS tile,
           h3_get_resolution_from_tile_zoom(1) AS res
),
cells AS (
    SELECT array_agg(h3 ORDER BY h3) AS arr
    FROM tile
    CROSS JOIN LATERAL h3_polygon_to_cells_experimental(
        tile, res, 'overlapping_bbox'
    ) AS cell(h3)
)
SELECT ST_IsValid(h3_cells_to_multi_polygon_geometry(arr))
FROM cells;

-- buffered north-polar low-zoom tile cover stays valid and covering
WITH tile AS (
    SELECT ST_Transform(ST_TileEnvelope(3, 2, 1, margin => 0.125), 4326) AS tile,
           h3_get_resolution_from_tile_zoom(3) AS res
),
cells AS (
    SELECT array_agg(h3 ORDER BY h3) AS arr
    FROM tile
    CROSS JOIN LATERAL h3_polygon_to_cells_experimental(
        tile, res, 'overlapping_bbox'
    ) AS cell(h3)
)
SELECT ST_IsValid(h3_cells_to_multi_polygon_geometry(arr))
   AND ST_IsEmpty(ST_Difference((SELECT tile FROM tile), h3_cells_to_multi_polygon_geometry(arr)))
FROM cells;

-- h3_polygon_to_cells_experimental
SELECT COUNT(*) = 48 FROM (
    SELECT h3_polygon_to_cells_experimental(:with2holes, 10, 'center')
) q;

SELECT COUNT(*) = 76 FROM (
    SELECT h3_polygon_to_cells_experimental(:with2holes, 10, 'overlapping')
) q;

-- geography experimental polyfill must preserve geodesic dateline edges
WITH geog AS (
    SELECT 'POLYGON((-170 10,170 10,170 -10,-170 -10,-170 10))'::geography AS g
),
cells AS (
    SELECT h3
    FROM geog,
         LATERAL h3_polygon_to_cells_experimental(g, 1, 'center') AS cell(h3)
)
SELECT COUNT(*) > 0
   AND BOOL_AND(ST_Covers((SELECT g FROM geog), h3_cell_to_geometry(h3)::geography))
FROM cells;

--
-- test h3_get_resolution_from_tile_zoom
--

-- consequtive examples for tile zooms 0..15 with default params
--   tile_size: 512 px
--   hex_edge_pixels: 44
--   max_h3_resolution: 15
--   min_h3_resolution: 0

SELECT h3_get_resolution_from_tile_zoom(0) = 0;
SELECT h3_get_resolution_from_tile_zoom(1) = 0;
SELECT h3_get_resolution_from_tile_zoom(2) = 0;
SELECT h3_get_resolution_from_tile_zoom(3) = 1;
SELECT h3_get_resolution_from_tile_zoom(4) = 2;
SELECT h3_get_resolution_from_tile_zoom(5) = 3;
SELECT h3_get_resolution_from_tile_zoom(6) = 3;
SELECT h3_get_resolution_from_tile_zoom(7) = 4;
SELECT h3_get_resolution_from_tile_zoom(8) = 5;
SELECT h3_get_resolution_from_tile_zoom(9) = 5;
SELECT h3_get_resolution_from_tile_zoom(10) = 6;
SELECT h3_get_resolution_from_tile_zoom(11) = 7;
SELECT h3_get_resolution_from_tile_zoom(12) = 8;
SELECT h3_get_resolution_from_tile_zoom(13) = 8;
SELECT h3_get_resolution_from_tile_zoom(14) = 9;
SELECT h3_get_resolution_from_tile_zoom(15) = 10;

-- consequtive examples for tile zooms 0..15 with settings:
--   tile_size: 512 px
--   hex_edge_pixels: 5
--   max_h3_resolution: 8
--   min_h3_resolution: 0

SELECT h3_get_resolution_from_tile_zoom(0, 8, 0, 5, 512) = 1;
SELECT h3_get_resolution_from_tile_zoom(1, 8, 0, 5, 512) = 2;
SELECT h3_get_resolution_from_tile_zoom(2, 8, 0, 5, 512) = 3;
SELECT h3_get_resolution_from_tile_zoom(3, 8, 0, 5, 512) = 3;
SELECT h3_get_resolution_from_tile_zoom(4, 8, 0, 5, 512) = 4;
SELECT h3_get_resolution_from_tile_zoom(5, 8, 0, 5, 512) = 5;
SELECT h3_get_resolution_from_tile_zoom(6, 8, 0, 5, 512) = 5;
SELECT h3_get_resolution_from_tile_zoom(7, 8, 0, 5, 512) = 6;
SELECT h3_get_resolution_from_tile_zoom(8, 8, 0, 5, 512) = 7;
SELECT h3_get_resolution_from_tile_zoom(9, 8, 0, 5, 512) = 8;
SELECT h3_get_resolution_from_tile_zoom(10, 8, 0, 5, 512) = 8;
SELECT h3_get_resolution_from_tile_zoom(11, 8, 0, 5, 512) = 8;
SELECT h3_get_resolution_from_tile_zoom(12, 8, 0, 5, 512) = 8;
SELECT h3_get_resolution_from_tile_zoom(13, 8, 0, 5, 512) = 8;
SELECT h3_get_resolution_from_tile_zoom(14, 8, 0, 5, 512) = 8;
SELECT h3_get_resolution_from_tile_zoom(15, 8, 0, 5, 512) = 8;
