diff --git a/example/example.dart b/example/example.dart new file mode 100644 index 0000000..fb4deae --- /dev/null +++ b/example/example.dart @@ -0,0 +1,70 @@ +// ignore_for_file: avoid_print +import 'package:h3_core/h3_core.dart'; + +void main() { + // Convert a coordinate to an H3 cell index at resolution 9. + final sf = LatLng(37.7749295, -122.4194155); + final cell = latLngToCell(sf, 9); + print('H3 cell: ${cell.toHex()}'); // e.g. 8928308280fffff + + // Get the center of the cell. + final center = cellToLatLng(cell); + print('Center: ${center.lat}, ${center.lng}'); + + // Inspect the cell. + print('Resolution: ${getResolution(cell)}'); + print('Valid: ${isValidCell(cell)}'); + print('Pentagon: ${isPentagon(cell)}'); + + // Get the cell boundary vertices. + final boundary = cellToBoundary(cell); + print('Boundary vertices: ${boundary.vertices.length}'); + + // Find neighbors within 1 grid step. + final neighbors = gridDisk(cell, 1); + print('Neighbors (k=1): ${neighbors.length}'); + + // Grid distance between two cells. + final other = neighbors.last; + final distance = gridDistance(cell, other); + print('Grid distance: $distance'); + + // Hierarchy: parent and children. + final parent = cellToParent(cell, 8); + print('Parent (res 8): ${parent.toHex()}'); + + final children = cellToChildren(cell, 10); + print('Children (res 10): ${children.length}'); + + // Compact and uncompact. + final compacted = compactCells(children); + print('Compacted: ${compacted.length}'); + + // Measurements. + final area = cellAreaKm2(cell); + print('Cell area: ${area.toStringAsFixed(4)} km²'); + + final dist = greatCircleDistanceKm(sf, LatLng(40.7128, -74.0060)); + print('SF to NYC: ${dist.toStringAsFixed(1)} km'); + + // Directed edges. + final neighbor = neighbors[1]; + if (areNeighborCells(cell, neighbor)) { + final edge = cellsToDirectedEdge(cell, neighbor); + print('Edge: ${edge.toHex()}'); + print('Edge length: ${edgeLengthKm(edge).toStringAsFixed(3)} km'); + } + + // Region: fill a polygon with cells. + final polygon = GeoPolygon([ + LatLng(37.78, -122.42), + LatLng(37.78, -122.41), + LatLng(37.77, -122.41), + LatLng(37.77, -122.42), + ]); + final filled = polygonToCells(polygon, 9); + print('Cells in polygon: ${filled.length}'); + + // Version info. + print('h3_core ${H3Version.package}, H3 native ${H3Version.native}'); +} diff --git a/lib/src/errors.dart b/lib/src/errors.dart index d789243..75ee5e4 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -1,10 +1,18 @@ -/// Base exception for all H3 errors. +/// Exception thrown when an H3 operation fails. +/// +/// Each error has a numeric [code] corresponding to the H3 C library error +/// codes and a human-readable [message]. class H3Exception implements Exception { + /// The H3 error code (1–19). final int code; + + /// A human-readable description of the error. final String message; + /// Creates an [H3Exception] with the given [code] and [message]. const H3Exception(this.code, this.message); + /// Creates an [H3Exception] from an H3 C library error [code]. factory H3Exception.fromCode(int code) { return switch (code) { 1 => const H3Exception(1, 'Operation failed'), diff --git a/lib/src/h3_ffi.dart b/lib/src/h3_ffi.dart index b890c41..1ff0535 100644 --- a/lib/src/h3_ffi.dart +++ b/lib/src/h3_ffi.dart @@ -13,6 +13,8 @@ import 'models/lat_lng.dart'; // Indexing +/// Converts a [latLng] coordinate to the H3 cell index at the given +/// [resolution] (0–15). H3Index latLngToCell(LatLng latLng, int resolution) { return using((Arena arena) { final ll = arena(); @@ -25,6 +27,7 @@ H3Index latLngToCell(LatLng latLng, int resolution) { }); } +/// Returns the center coordinate of the given H3 [cell]. LatLng cellToLatLng(H3Index cell) { return using((Arena arena) { final ll = arena(); @@ -33,6 +36,7 @@ LatLng cellToLatLng(H3Index cell) { }); } +/// Returns the boundary vertices of the given H3 [cell]. CellBoundary cellToBoundary(H3Index cell) { return using((Arena arena) { final cb = arena(); @@ -54,18 +58,25 @@ CellBoundary cellToBoundary(H3Index cell) { // Inspection +/// Returns the resolution (0–15) of the given H3 index. int getResolution(H3Index h) => c.getResolution(h.toInt()); +/// Returns the base cell number (0–121) of the given H3 index. int getBaseCellNumber(H3Index h) => c.getBaseCellNumber(h.toInt()); +/// Returns whether [h] is a valid H3 cell index. bool isValidCell(H3Index h) => c.isValidCell(h.toInt()) == 1; +/// Returns whether [h] is any valid H3 index (cell, edge, or vertex). bool isValidIndex(H3Index h) => c.isValidIndex(h.toInt()) == 1; +/// Returns whether [h] is a pentagon cell. bool isPentagon(H3Index h) => c.isPentagon(h.toInt()) == 1; +/// Returns whether [h] has Class III resolution orientation. bool isResClassIII(H3Index h) => c.isResClassIII(h.toInt()) == 1; +/// Returns the icosahedron face(s) that the given cell intersects. List getIcosahedronFaces(H3Index h) { return using((Arena arena) { final maxOut = arena(); @@ -84,11 +95,14 @@ List getIcosahedronFaces(H3Index h) { }); } +/// Returns whether [edge] is a valid H3 directed edge index. bool isValidDirectedEdge(H3Index edge) => c.isValidDirectedEdge(edge.toInt()) == 1; +/// Returns whether [vertex] is a valid H3 vertex index. bool isValidVertex(H3Index vertex) => c.isValidVertex(vertex.toInt()) == 1; +/// Returns the direction digit at the given [resolution] for index [h]. int getIndexDigit(H3Index h, int resolution) { return using((Arena arena) { final out = arena(); @@ -97,6 +111,8 @@ int getIndexDigit(H3Index h, int resolution) { }); } +/// Constructs an H3 cell index from [res], [baseCellNumber], and direction +/// [digits]. H3Index constructCell(int res, int baseCellNumber, List digits) { return using((Arena arena) { final digitsPtr = arena(digits.length); @@ -111,6 +127,8 @@ H3Index constructCell(int res, int baseCellNumber, List digits) { // String conversion +/// Parses a [hex] string into an [H3Index], throwing [H3Exception] on +/// invalid input. H3Index stringToH3(String hex) { return using((Arena arena) { final str = hex.toNativeUtf8(allocator: arena); @@ -120,6 +138,7 @@ H3Index stringToH3(String hex) { }); } +/// Returns the hex string representation of the given H3 index. String h3ToString(H3Index h) { return using((Arena arena) { final buf = arena(17); @@ -130,6 +149,7 @@ String h3ToString(H3Index h) { // Traversal +/// Returns all cells within [k] grid steps of [origin] (filled disk). List gridDisk(H3Index origin, int k) { return using((Arena arena) { final sizeOut = arena(); @@ -146,6 +166,7 @@ List gridDisk(H3Index origin, int k) { }); } +/// Returns cells within [k] steps of [origin] mapped to their grid distance. Map gridDiskDistances(H3Index origin, int k) { return using((Arena arena) { final sizeOut = arena(); @@ -166,6 +187,7 @@ Map gridDiskDistances(H3Index origin, int k) { }); } +/// Returns cells exactly [k] grid steps from [origin] (hollow ring). List gridRing(H3Index origin, int k) { return using((Arena arena) { final sizeOut = arena(); @@ -182,6 +204,7 @@ List gridRing(H3Index origin, int k) { }); } +/// Returns the minimum grid distance between [origin] and [destination]. int gridDistance(H3Index origin, H3Index destination) { return using((Arena arena) { final out = arena(); @@ -190,6 +213,7 @@ int gridDistance(H3Index origin, H3Index destination) { }); } +/// Returns the cells along the shortest grid path from [start] to [end]. List gridPathCells(H3Index start, H3Index end) { return using((Arena arena) { final sizeOut = arena(); @@ -205,6 +229,7 @@ List gridPathCells(H3Index start, H3Index end) { // Hierarchy +/// Returns the parent cell of [cell] at the coarser [parentRes]. H3Index cellToParent(H3Index cell, int parentRes) { return using((Arena arena) { final out = arena(); @@ -213,6 +238,7 @@ H3Index cellToParent(H3Index cell, int parentRes) { }); } +/// Returns the children of [cell] at the finer [childRes]. List cellToChildren(H3Index cell, int childRes) { return using((Arena arena) { final sizeOut = arena(); @@ -226,6 +252,7 @@ List cellToChildren(H3Index cell, int childRes) { }); } +/// Returns the center child of [cell] at the finer [childRes]. H3Index cellToCenterChild(H3Index cell, int childRes) { return using((Arena arena) { final out = arena(); @@ -234,6 +261,7 @@ H3Index cellToCenterChild(H3Index cell, int childRes) { }); } +/// Returns the position of [child] within its parent at [parentRes]. int cellToChildPos(H3Index child, int parentRes) { return using((Arena arena) { final out = arena(); @@ -242,6 +270,7 @@ int cellToChildPos(H3Index child, int parentRes) { }); } +/// Returns the child cell at [childPos] within [parent] at [childRes]. H3Index childPosToCell(int childPos, H3Index parent, int childRes) { return using((Arena arena) { final out = arena(); @@ -250,6 +279,7 @@ H3Index childPosToCell(int childPos, H3Index parent, int childRes) { }); } +/// Compacts a set of [cells] by replacing complete groups with their parent. List compactCells(List cells) { return using((Arena arena) { final h3Set = arena(cells.length); @@ -267,6 +297,7 @@ List compactCells(List cells) { }); } +/// Expands compacted [cells] to the given [resolution]. List uncompactCells(List cells, int resolution) { return using((Arena arena) { final h3Set = arena(cells.length); @@ -292,6 +323,7 @@ List uncompactCells(List cells, int resolution) { // Directed edges +/// Returns whether [origin] and [destination] share an edge. bool areNeighborCells(H3Index origin, H3Index destination) { return using((Arena arena) { final out = arena(); @@ -300,6 +332,7 @@ bool areNeighborCells(H3Index origin, H3Index destination) { }); } +/// Returns the directed edge from [origin] to [destination]. H3Index cellsToDirectedEdge(H3Index origin, H3Index destination) { return using((Arena arena) { final out = arena(); @@ -310,6 +343,7 @@ H3Index cellsToDirectedEdge(H3Index origin, H3Index destination) { }); } +/// Returns the origin cell of the directed [edge]. H3Index getDirectedEdgeOrigin(H3Index edge) { return using((Arena arena) { final out = arena(); @@ -318,6 +352,7 @@ H3Index getDirectedEdgeOrigin(H3Index edge) { }); } +/// Returns the destination cell of the directed [edge]. H3Index getDirectedEdgeDestination(H3Index edge) { return using((Arena arena) { final out = arena(); @@ -326,6 +361,7 @@ H3Index getDirectedEdgeDestination(H3Index edge) { }); } +/// Returns the origin and destination cells of the directed [edge]. List directedEdgeToCells(H3Index edge) { return using((Arena arena) { final out = arena(2); @@ -334,6 +370,7 @@ List directedEdgeToCells(H3Index edge) { }); } +/// Returns all directed edges originating from [origin]. List originToDirectedEdges(H3Index origin) { return using((Arena arena) { final out = arena(6); @@ -345,6 +382,7 @@ List originToDirectedEdges(H3Index origin) { }); } +/// Returns the boundary vertices of the directed [edge]. CellBoundary directedEdgeToBoundary(H3Index edge) { return using((Arena arena) { final cb = arena(); @@ -366,6 +404,7 @@ CellBoundary directedEdgeToBoundary(H3Index edge) { // Vertices +/// Returns the vertex at index [vertexNum] (0–5) of the [cell]. H3Index cellToVertex(H3Index cell, int vertexNum) { return using((Arena arena) { final out = arena(); @@ -374,6 +413,7 @@ H3Index cellToVertex(H3Index cell, int vertexNum) { }); } +/// Returns all vertices of the given [cell]. List cellToVertexes(H3Index cell) { return using((Arena arena) { final out = arena(6); @@ -385,6 +425,7 @@ List cellToVertexes(H3Index cell) { }); } +/// Returns the coordinates of the given [vertex]. LatLng vertexToLatLng(H3Index vertex) { return using((Arena arena) { final ll = arena(); @@ -395,6 +436,7 @@ LatLng vertexToLatLng(H3Index vertex) { // Measurements +/// Returns the great-circle distance between [a] and [b] in kilometers. double greatCircleDistanceKm(LatLng a, LatLng b) { return using((Arena arena) { final aPtr = arena(); @@ -407,6 +449,7 @@ double greatCircleDistanceKm(LatLng a, LatLng b) { }); } +/// Returns the great-circle distance between [a] and [b] in meters. double greatCircleDistanceM(LatLng a, LatLng b) { return using((Arena arena) { final aPtr = arena(); @@ -419,6 +462,7 @@ double greatCircleDistanceM(LatLng a, LatLng b) { }); } +/// Returns the exact area of the [cell] in km². double cellAreaKm2(H3Index cell) { return using((Arena arena) { final out = arena(); @@ -427,6 +471,7 @@ double cellAreaKm2(H3Index cell) { }); } +/// Returns the exact area of the [cell] in m². double cellAreaM2(H3Index cell) { return using((Arena arena) { final out = arena(); @@ -435,6 +480,7 @@ double cellAreaM2(H3Index cell) { }); } +/// Returns the exact length of the directed [edge] in kilometers. double edgeLengthKm(H3Index edge) { return using((Arena arena) { final out = arena(); @@ -443,6 +489,7 @@ double edgeLengthKm(H3Index edge) { }); } +/// Returns the exact length of the directed [edge] in meters. double edgeLengthM(H3Index edge) { return using((Arena arena) { final out = arena(); @@ -451,6 +498,7 @@ double edgeLengthM(H3Index edge) { }); } +/// Returns the average hexagon area in km² at the given [resolution]. double getHexagonAreaAvgKm2(int resolution) { return using((Arena arena) { final out = arena(); @@ -459,6 +507,7 @@ double getHexagonAreaAvgKm2(int resolution) { }); } +/// Returns the average hexagon area in m² at the given [resolution]. double getHexagonAreaAvgM2(int resolution) { return using((Arena arena) { final out = arena(); @@ -467,6 +516,7 @@ double getHexagonAreaAvgM2(int resolution) { }); } +/// Returns the average hexagon edge length in km at the given [resolution]. double getHexagonEdgeLengthAvgKm(int resolution) { return using((Arena arena) { final out = arena(); @@ -475,6 +525,8 @@ double getHexagonEdgeLengthAvgKm(int resolution) { }); } +/// Returns the average hexagon edge length in meters at the given +/// [resolution]. double getHexagonEdgeLengthAvgM(int resolution) { return using((Arena arena) { final out = arena(); @@ -483,6 +535,7 @@ double getHexagonEdgeLengthAvgM(int resolution) { }); } +/// Returns the total number of cells at the given [resolution]. int getNumCells(int resolution) { return using((Arena arena) { final out = arena(); @@ -493,6 +546,7 @@ int getNumCells(int resolution) { // Coordinate systems +/// Converts [cell] to local IJ coordinates relative to [origin]. CoordIJ cellToLocalIj(H3Index origin, H3Index cell) { return using((Arena arena) { final ij = arena(); @@ -501,6 +555,7 @@ CoordIJ cellToLocalIj(H3Index origin, H3Index cell) { }); } +/// Converts local [ij] coordinates relative to [origin] back to an H3 cell. H3Index localIjToCell(H3Index origin, CoordIJ ij) { return using((Arena arena) { final ijPtr = arena(); @@ -514,6 +569,7 @@ H3Index localIjToCell(H3Index origin, CoordIJ ij) { // Regions +/// Returns all cells whose centers are within the [polygon] at [resolution]. List polygonToCells(GeoPolygon polygon, int resolution) { return using((Arena arena) { final gp = arena(); @@ -558,6 +614,7 @@ List polygonToCells(GeoPolygon polygon, int resolution) { }); } +/// Returns cells within the [polygon] using the specified containment [mode]. List polygonToCellsExperimental( GeoPolygon polygon, int resolution, { @@ -611,6 +668,8 @@ List polygonToCellsExperimental( }); } +/// Returns the outlines of a set of [cells] as a GeoJSON-style +/// multi-polygon. List>> cellsToMultiPolygon(List cells) { return using((Arena arena) { final h3Set = arena(cells.length); @@ -664,8 +723,10 @@ List>> _extractMultiPolygon( // Utilities +/// Returns the number of resolution-0 cells (always 122). int res0CellCount() => c.res0CellCount(); +/// Returns all 122 resolution-0 cells. List getRes0Cells() { return using((Arena arena) { final out = arena(122); @@ -674,8 +735,10 @@ List getRes0Cells() { }); } +/// Returns the number of pentagons per resolution (always 12). int pentagonCount() => c.pentagonCount(); +/// Returns all 12 pentagon cells at the given [resolution]. List getPentagons(int resolution) { return using((Arena arena) { final out = arena(12); @@ -684,16 +747,28 @@ List getPentagons(int resolution) { }); } +/// Version constants for the h3_core package and the underlying H3 C library. abstract final class H3Version { + /// The h3_core Dart package version. static const String package = '1.0.1'; + + /// The H3 C library version string. static const String native = '4.4.1'; + + /// The H3 C library major version. static const int major = 4; + + /// The H3 C library minor version. static const int minor = 4; + + /// The H3 C library patch version. static const int patch = 1; } // Async — runs on isolate to keep the UI thread free. +/// Async version of [polygonToCells]; runs on an isolate to avoid blocking +/// the UI thread. Future> polygonToCellsAsync( GeoPolygon polygon, int resolution, @@ -701,14 +776,20 @@ Future> polygonToCellsAsync( return Isolate.run(() => polygonToCells(polygon, resolution)); } +/// Async version of [gridDisk]; runs on an isolate to avoid blocking the +/// UI thread. Future> gridDiskAsync(H3Index origin, int k) async { return Isolate.run(() => gridDisk(origin, k)); } +/// Async version of [compactCells]; runs on an isolate to avoid blocking +/// the UI thread. Future> compactCellsAsync(List cells) async { return Isolate.run(() => compactCells(cells)); } +/// Async version of [uncompactCells]; runs on an isolate to avoid blocking +/// the UI thread. Future> uncompactCellsAsync( List cells, int resolution, diff --git a/lib/src/h3_stub.dart b/lib/src/h3_stub.dart index 08a50bb..499525b 100644 --- a/lib/src/h3_stub.dart +++ b/lib/src/h3_stub.dart @@ -13,122 +13,248 @@ Never _unsupported() => // Indexing +/// Converts a [latLng] coordinate to the H3 cell index at the given +/// [resolution] (0–15). H3Index latLngToCell(LatLng latLng, int resolution) => _unsupported(); + +/// Returns the center coordinate of the given H3 [cell]. LatLng cellToLatLng(H3Index cell) => _unsupported(); + +/// Returns the boundary vertices of the given H3 [cell]. CellBoundary cellToBoundary(H3Index cell) => _unsupported(); // Inspection +/// Returns the resolution (0–15) of the given H3 index. int getResolution(H3Index h) => _unsupported(); + +/// Returns the base cell number (0–121) of the given H3 index. int getBaseCellNumber(H3Index h) => _unsupported(); + +/// Returns whether [h] is a valid H3 cell index. bool isValidCell(H3Index h) => _unsupported(); + +/// Returns whether [h] is any valid H3 index (cell, edge, or vertex). bool isValidIndex(H3Index h) => _unsupported(); + +/// Returns whether [h] is a pentagon cell. bool isPentagon(H3Index h) => _unsupported(); + +/// Returns whether [h] has Class III resolution orientation. bool isResClassIII(H3Index h) => _unsupported(); + +/// Returns the icosahedron face(s) that the given cell intersects. List getIcosahedronFaces(H3Index h) => _unsupported(); + +/// Returns whether [edge] is a valid H3 directed edge index. bool isValidDirectedEdge(H3Index edge) => _unsupported(); + +/// Returns whether [vertex] is a valid H3 vertex index. bool isValidVertex(H3Index vertex) => _unsupported(); + +/// Returns the direction digit at the given [resolution] for index [h]. int getIndexDigit(H3Index h, int resolution) => _unsupported(); + +/// Constructs an H3 cell index from [res], [baseCellNumber], and direction +/// [digits]. H3Index constructCell(int res, int baseCellNumber, List digits) => _unsupported(); // String conversion +/// Parses a [hex] string into an [H3Index], throwing [H3Exception] on +/// invalid input. H3Index stringToH3(String hex) => _unsupported(); + +/// Returns the hex string representation of the given H3 index. String h3ToString(H3Index h) => _unsupported(); // Traversal +/// Returns all cells within [k] grid steps of [origin] (filled disk). List gridDisk(H3Index origin, int k) => _unsupported(); + +/// Returns cells within [k] steps of [origin] mapped to their grid distance. Map gridDiskDistances(H3Index origin, int k) => _unsupported(); + +/// Returns cells exactly [k] grid steps from [origin] (hollow ring). List gridRing(H3Index origin, int k) => _unsupported(); + +/// Returns the minimum grid distance between [origin] and [destination]. int gridDistance(H3Index origin, H3Index destination) => _unsupported(); + +/// Returns the cells along the shortest grid path from [start] to [end]. List gridPathCells(H3Index start, H3Index end) => _unsupported(); // Hierarchy +/// Returns the parent cell of [cell] at the coarser [parentRes]. H3Index cellToParent(H3Index cell, int parentRes) => _unsupported(); + +/// Returns the children of [cell] at the finer [childRes]. List cellToChildren(H3Index cell, int childRes) => _unsupported(); + +/// Returns the center child of [cell] at the finer [childRes]. H3Index cellToCenterChild(H3Index cell, int childRes) => _unsupported(); + +/// Returns the position of [child] within its parent at [parentRes]. int cellToChildPos(H3Index child, int parentRes) => _unsupported(); + +/// Returns the child cell at [childPos] within [parent] at [childRes]. H3Index childPosToCell(int childPos, H3Index parent, int childRes) => _unsupported(); + +/// Compacts a set of [cells] by replacing complete groups with their parent. List compactCells(List cells) => _unsupported(); + +/// Expands compacted [cells] to the given [resolution]. List uncompactCells(List cells, int resolution) => _unsupported(); // Directed edges +/// Returns whether [origin] and [destination] share an edge. bool areNeighborCells(H3Index origin, H3Index destination) => _unsupported(); + +/// Returns the directed edge from [origin] to [destination]. H3Index cellsToDirectedEdge(H3Index origin, H3Index destination) => _unsupported(); + +/// Returns the origin cell of the directed [edge]. H3Index getDirectedEdgeOrigin(H3Index edge) => _unsupported(); + +/// Returns the destination cell of the directed [edge]. H3Index getDirectedEdgeDestination(H3Index edge) => _unsupported(); + +/// Returns the origin and destination cells of the directed [edge]. List directedEdgeToCells(H3Index edge) => _unsupported(); + +/// Returns all directed edges originating from [origin]. List originToDirectedEdges(H3Index origin) => _unsupported(); + +/// Returns the boundary vertices of the directed [edge]. CellBoundary directedEdgeToBoundary(H3Index edge) => _unsupported(); // Vertices +/// Returns the vertex at index [vertexNum] (0–5) of the [cell]. H3Index cellToVertex(H3Index cell, int vertexNum) => _unsupported(); + +/// Returns all vertices of the given [cell]. List cellToVertexes(H3Index cell) => _unsupported(); + +/// Returns the coordinates of the given [vertex]. LatLng vertexToLatLng(H3Index vertex) => _unsupported(); // Measurements +/// Returns the great-circle distance between [a] and [b] in kilometers. double greatCircleDistanceKm(LatLng a, LatLng b) => _unsupported(); + +/// Returns the great-circle distance between [a] and [b] in meters. double greatCircleDistanceM(LatLng a, LatLng b) => _unsupported(); + +/// Returns the exact area of the [cell] in km². double cellAreaKm2(H3Index cell) => _unsupported(); + +/// Returns the exact area of the [cell] in m². double cellAreaM2(H3Index cell) => _unsupported(); + +/// Returns the exact length of the directed [edge] in kilometers. double edgeLengthKm(H3Index edge) => _unsupported(); + +/// Returns the exact length of the directed [edge] in meters. double edgeLengthM(H3Index edge) => _unsupported(); + +/// Returns the average hexagon area in km² at the given [resolution]. double getHexagonAreaAvgKm2(int resolution) => _unsupported(); + +/// Returns the average hexagon area in m² at the given [resolution]. double getHexagonAreaAvgM2(int resolution) => _unsupported(); + +/// Returns the average hexagon edge length in km at the given [resolution]. double getHexagonEdgeLengthAvgKm(int resolution) => _unsupported(); + +/// Returns the average hexagon edge length in meters at the given +/// [resolution]. double getHexagonEdgeLengthAvgM(int resolution) => _unsupported(); + +/// Returns the total number of cells at the given [resolution]. int getNumCells(int resolution) => _unsupported(); // Coordinate systems +/// Converts [cell] to local IJ coordinates relative to [origin]. CoordIJ cellToLocalIj(H3Index origin, H3Index cell) => _unsupported(); + +/// Converts local [ij] coordinates relative to [origin] back to an H3 cell. H3Index localIjToCell(H3Index origin, CoordIJ ij) => _unsupported(); // Regions +/// Returns all cells whose centers are within the [polygon] at [resolution]. List polygonToCells(GeoPolygon polygon, int resolution) => _unsupported(); + +/// Returns cells within the [polygon] using the specified containment [mode]. List polygonToCellsExperimental( GeoPolygon polygon, int resolution, { ContainmentMode mode = ContainmentMode.center, }) => _unsupported(); + +/// Returns the outlines of a set of [cells] as a GeoJSON-style +/// multi-polygon. List>> cellsToMultiPolygon(List cells) => _unsupported(); // Utilities +/// Returns the number of resolution-0 cells (always 122). int res0CellCount() => _unsupported(); + +/// Returns all 122 resolution-0 cells. List getRes0Cells() => _unsupported(); + +/// Returns the number of pentagons per resolution (always 12). int pentagonCount() => _unsupported(); + +/// Returns all 12 pentagon cells at the given [resolution]. List getPentagons(int resolution) => _unsupported(); // Version +/// Version constants for the h3_core package and the underlying H3 C library. abstract final class H3Version { + /// The h3_core Dart package version. static const String package = '1.0.1'; + + /// The H3 C library version string. static const String native = '4.4.1'; + + /// The H3 C library major version. static const int major = 4; + + /// The H3 C library minor version. static const int minor = 4; + + /// The H3 C library patch version. static const int patch = 1; } // Async wrappers +/// Async version of [polygonToCells]; runs on an isolate on native platforms. Future> polygonToCellsAsync(GeoPolygon polygon, int resolution) => _unsupported(); +/// Async version of [gridDisk]; runs on an isolate on native platforms. Future> gridDiskAsync(H3Index origin, int k) => _unsupported(); + +/// Async version of [compactCells]; runs on an isolate on native platforms. Future> compactCellsAsync(List cells) => _unsupported(); + +/// Async version of [uncompactCells]; runs on an isolate on native platforms. Future> uncompactCellsAsync( List cells, int resolution, diff --git a/lib/src/h3_web.dart b/lib/src/h3_web.dart index 166318b..240375d 100644 --- a/lib/src/h3_web.dart +++ b/lib/src/h3_web.dart @@ -54,6 +54,8 @@ T _catchJsError(T Function() fn) { // Indexing +/// Converts a [latLng] coordinate to the H3 cell index at the given +/// [resolution] (0–15). H3Index latLngToCell(LatLng latLng, int resolution) { return _catchJsError(() { final hex = js.h3.latLngToCell( @@ -65,6 +67,7 @@ H3Index latLngToCell(LatLng latLng, int resolution) { }); } +/// Returns the center coordinate of the given H3 [cell]. LatLng cellToLatLng(H3Index cell) { return _catchJsError(() { final arr = js.h3.cellToLatLng(_toJs(cell)); @@ -72,6 +75,7 @@ LatLng cellToLatLng(H3Index cell) { }); } +/// Returns the boundary vertices of the given H3 [cell]. CellBoundary cellToBoundary(H3Index cell) { return _catchJsError(() { final arr = js.h3.cellToBoundary(_toJs(cell)); @@ -85,18 +89,23 @@ CellBoundary cellToBoundary(H3Index cell) { // Inspection +/// Returns the resolution (0–15) of the given H3 index. int getResolution(H3Index h) { return _catchJsError(() => js.h3.getResolution(_toJs(h)).toDartInt); } +/// Returns the base cell number (0–121) of the given H3 index. int getBaseCellNumber(H3Index h) { return _catchJsError(() => js.h3.getBaseCellNumber(_toJs(h)).toDartInt); } +/// Returns whether [h] is a valid H3 cell index. bool isValidCell(H3Index h) { return _catchJsError(() => js.h3.isValidCell(_toJs(h)).toDart); } +/// Returns whether [h] is any valid H3 index (cell, edge, or vertex). +/// /// Checks cell, edge, and vertex validity (h3-js has no single isValidIndex). bool isValidIndex(H3Index h) { final hex = _toJs(h); @@ -105,14 +114,17 @@ bool isValidIndex(H3Index h) { js.h3.isValidVertex(hex).toDart; } +/// Returns whether [h] is a pentagon cell. bool isPentagon(H3Index h) { return _catchJsError(() => js.h3.isPentagon(_toJs(h)).toDart); } +/// Returns whether [h] has Class III resolution orientation. bool isResClassIII(H3Index h) { return _catchJsError(() => js.h3.isResClassIII(_toJs(h)).toDart); } +/// Returns the icosahedron face(s) that the given cell intersects. List getIcosahedronFaces(H3Index h) { return _catchJsError(() { final faces = js.h3.getIcosahedronFaces(_toJs(h)); @@ -120,15 +132,18 @@ List getIcosahedronFaces(H3Index h) { }); } +/// Returns whether [edge] is a valid H3 directed edge index. bool isValidDirectedEdge(H3Index edge) { return _catchJsError(() => js.h3.isValidDirectedEdge(_toJs(edge)).toDart); } +/// Returns whether [vertex] is a valid H3 vertex index. bool isValidVertex(H3Index vertex) { return _catchJsError(() => js.h3.isValidVertex(_toJs(vertex)).toDart); } -/// Extracts the direction digit at [resolution] from [h]. +/// Returns the direction digit at the given [resolution] for index [h]. +/// /// Uses BigInt since h3-js doesn't expose this and JS can't do 64-bit bit ops. int getIndexDigit(H3Index h, int resolution) { final bigInt = BigInt.parse(h.toHex(), radix: 16); @@ -136,7 +151,9 @@ int getIndexDigit(H3Index h, int resolution) { return ((bigInt >> shift) & BigInt.from(0x7)).toInt(); } -/// Builds an H3 cell from resolution, base cell, and direction digits. +/// Constructs an H3 cell index from [res], [baseCellNumber], and direction +/// [digits]. +/// /// Uses BigInt for the same reason as [getIndexDigit]. H3Index constructCell(int res, int baseCellNumber, List digits) { var value = BigInt.zero; @@ -157,6 +174,8 @@ H3Index constructCell(int res, int baseCellNumber, List digits) { // String conversion +/// Parses a [hex] string into an [H3Index], throwing [H3Exception] on +/// invalid input. H3Index stringToH3(String hex) { final h = H3Index.parse(hex); if (!isValidIndex(h)) { @@ -165,10 +184,12 @@ H3Index stringToH3(String hex) { return h; } +/// Returns the hex string representation of the given H3 index. String h3ToString(H3Index h) => h.toHex(); // Traversal +/// Returns all cells within [k] grid steps of [origin] (filled disk). List gridDisk(H3Index origin, int k) { return _catchJsError(() { final cells = js.h3.gridDisk(_toJs(origin), k.toJS); @@ -176,6 +197,7 @@ List gridDisk(H3Index origin, int k) { }); } +/// Returns cells within [k] steps of [origin] mapped to their grid distance. Map gridDiskDistances(H3Index origin, int k) { return _catchJsError(() { final rings = js.h3.gridDiskDistances(_toJs(origin), k.toJS); @@ -190,6 +212,7 @@ Map gridDiskDistances(H3Index origin, int k) { }); } +/// Returns cells exactly [k] grid steps from [origin] (hollow ring). List gridRing(H3Index origin, int k) { return _catchJsError(() { final cells = js.h3.gridRingUnsafe(_toJs(origin), k.toJS); @@ -197,12 +220,14 @@ List gridRing(H3Index origin, int k) { }); } +/// Returns the minimum grid distance between [origin] and [destination]. int gridDistance(H3Index origin, H3Index destination) { return _catchJsError(() { return js.h3.gridDistance(_toJs(origin), _toJs(destination)).toDartInt; }); } +/// Returns the cells along the shortest grid path from [start] to [end]. List gridPathCells(H3Index start, H3Index end) { return _catchJsError(() { final cells = js.h3.gridPathCells(_toJs(start), _toJs(end)); @@ -212,12 +237,14 @@ List gridPathCells(H3Index start, H3Index end) { // Hierarchy +/// Returns the parent cell of [cell] at the coarser [parentRes]. H3Index cellToParent(H3Index cell, int parentRes) { return _catchJsError(() { return _fromJs(js.h3.cellToParent(_toJs(cell), parentRes.toJS)); }); } +/// Returns the children of [cell] at the finer [childRes]. List cellToChildren(H3Index cell, int childRes) { return _catchJsError(() { final children = js.h3.cellToChildren(_toJs(cell), childRes.toJS); @@ -225,18 +252,21 @@ List cellToChildren(H3Index cell, int childRes) { }); } +/// Returns the center child of [cell] at the finer [childRes]. H3Index cellToCenterChild(H3Index cell, int childRes) { return _catchJsError(() { return _fromJs(js.h3.cellToCenterChild(_toJs(cell), childRes.toJS)); }); } +/// Returns the position of [child] within its parent at [parentRes]. int cellToChildPos(H3Index child, int parentRes) { return _catchJsError(() { return js.h3.cellToChildPos(_toJs(child), parentRes.toJS).toDartInt; }); } +/// Returns the child cell at [childPos] within [parent] at [childRes]. H3Index childPosToCell(int childPos, H3Index parent, int childRes) { return _catchJsError(() { return _fromJs( @@ -245,6 +275,7 @@ H3Index childPosToCell(int childPos, H3Index parent, int childRes) { }); } +/// Compacts a set of [cells] by replacing complete groups with their parent. List compactCells(List cells) { return _catchJsError(() { final jsArr = [for (final c in cells) _toJs(c)].toJS; @@ -253,6 +284,7 @@ List compactCells(List cells) { }); } +/// Expands compacted [cells] to the given [resolution]. List uncompactCells(List cells, int resolution) { return _catchJsError(() { final jsArr = [for (final c in cells) _toJs(c)].toJS; @@ -263,12 +295,14 @@ List uncompactCells(List cells, int resolution) { // Directed edges +/// Returns whether [origin] and [destination] share an edge. bool areNeighborCells(H3Index origin, H3Index destination) { return _catchJsError(() { return js.h3.areNeighborCells(_toJs(origin), _toJs(destination)).toDart; }); } +/// Returns the directed edge from [origin] to [destination]. H3Index cellsToDirectedEdge(H3Index origin, H3Index destination) { return _catchJsError(() { return _fromJs( @@ -277,18 +311,21 @@ H3Index cellsToDirectedEdge(H3Index origin, H3Index destination) { }); } +/// Returns the origin cell of the directed [edge]. H3Index getDirectedEdgeOrigin(H3Index edge) { return _catchJsError(() { return _fromJs(js.h3.getDirectedEdgeOrigin(_toJs(edge))); }); } +/// Returns the destination cell of the directed [edge]. H3Index getDirectedEdgeDestination(H3Index edge) { return _catchJsError(() { return _fromJs(js.h3.getDirectedEdgeDestination(_toJs(edge))); }); } +/// Returns the origin and destination cells of the directed [edge]. List directedEdgeToCells(H3Index edge) { return _catchJsError(() { final cells = js.h3.directedEdgeToCells(_toJs(edge)); @@ -296,6 +333,7 @@ List directedEdgeToCells(H3Index edge) { }); } +/// Returns all directed edges originating from [origin]. List originToDirectedEdges(H3Index origin) { return _catchJsError(() { final edges = js.h3.originToDirectedEdges(_toJs(origin)); @@ -303,6 +341,7 @@ List originToDirectedEdges(H3Index origin) { }); } +/// Returns the boundary vertices of the directed [edge]. CellBoundary directedEdgeToBoundary(H3Index edge) { return _catchJsError(() { final arr = js.h3.directedEdgeToBoundary(_toJs(edge)); @@ -316,12 +355,14 @@ CellBoundary directedEdgeToBoundary(H3Index edge) { // Vertices +/// Returns the vertex at index [vertexNum] (0–5) of the [cell]. H3Index cellToVertex(H3Index cell, int vertexNum) { return _catchJsError(() { return _fromJs(js.h3.cellToVertex(_toJs(cell), vertexNum.toJS)); }); } +/// Returns all vertices of the given [cell]. List cellToVertexes(H3Index cell) { return _catchJsError(() { final verts = js.h3.cellToVertexes(_toJs(cell)); @@ -329,6 +370,7 @@ List cellToVertexes(H3Index cell) { }); } +/// Returns the coordinates of the given [vertex]. LatLng vertexToLatLng(H3Index vertex) { return _catchJsError(() { final arr = js.h3.vertexToLatLng(_toJs(vertex)); @@ -338,6 +380,7 @@ LatLng vertexToLatLng(H3Index vertex) { // Measurements +/// Returns the great-circle distance between [a] and [b] in kilometers. double greatCircleDistanceKm(LatLng a, LatLng b) { return _catchJsError(() { return js.h3 @@ -350,6 +393,7 @@ double greatCircleDistanceKm(LatLng a, LatLng b) { }); } +/// Returns the great-circle distance between [a] and [b] in meters. double greatCircleDistanceM(LatLng a, LatLng b) { return _catchJsError(() { return js.h3 @@ -358,42 +402,49 @@ double greatCircleDistanceM(LatLng a, LatLng b) { }); } +/// Returns the exact area of the [cell] in km². double cellAreaKm2(H3Index cell) { return _catchJsError(() { return js.h3.cellArea(_toJs(cell), 'km2'.toJS).toDartDouble; }); } +/// Returns the exact area of the [cell] in m². double cellAreaM2(H3Index cell) { return _catchJsError(() { return js.h3.cellArea(_toJs(cell), 'm2'.toJS).toDartDouble; }); } +/// Returns the exact length of the directed [edge] in kilometers. double edgeLengthKm(H3Index edge) { return _catchJsError(() { return js.h3.edgeLength(_toJs(edge), 'km'.toJS).toDartDouble; }); } +/// Returns the exact length of the directed [edge] in meters. double edgeLengthM(H3Index edge) { return _catchJsError(() { return js.h3.edgeLength(_toJs(edge), 'm'.toJS).toDartDouble; }); } +/// Returns the average hexagon area in km² at the given [resolution]. double getHexagonAreaAvgKm2(int resolution) { return _catchJsError(() { return js.h3.getHexagonAreaAvg(resolution.toJS, 'km2'.toJS).toDartDouble; }); } +/// Returns the average hexagon area in m² at the given [resolution]. double getHexagonAreaAvgM2(int resolution) { return _catchJsError(() { return js.h3.getHexagonAreaAvg(resolution.toJS, 'm2'.toJS).toDartDouble; }); } +/// Returns the average hexagon edge length in km at the given [resolution]. double getHexagonEdgeLengthAvgKm(int resolution) { return _catchJsError(() { return js.h3 @@ -402,6 +453,8 @@ double getHexagonEdgeLengthAvgKm(int resolution) { }); } +/// Returns the average hexagon edge length in meters at the given +/// [resolution]. double getHexagonEdgeLengthAvgM(int resolution) { return _catchJsError(() { return js.h3 @@ -410,6 +463,7 @@ double getHexagonEdgeLengthAvgM(int resolution) { }); } +/// Returns the total number of cells at the given [resolution]. int getNumCells(int resolution) { return _catchJsError(() { return js.h3.getNumCells(resolution.toJS).toDartInt; @@ -418,6 +472,7 @@ int getNumCells(int resolution) { // Coordinate systems +/// Converts [cell] to local IJ coordinates relative to [origin]. CoordIJ cellToLocalIj(H3Index origin, H3Index cell) { return _catchJsError(() { final ij = js.h3.cellToLocalIj(_toJs(origin), _toJs(cell)); @@ -425,6 +480,7 @@ CoordIJ cellToLocalIj(H3Index origin, H3Index cell) { }); } +/// Converts local [ij] coordinates relative to [origin] back to an H3 cell. H3Index localIjToCell(H3Index origin, CoordIJ ij) { return _catchJsError(() { final jsIj = js.CoordIJJsLiteral(i: ij.i, j: ij.j); @@ -434,6 +490,7 @@ H3Index localIjToCell(H3Index origin, CoordIJ ij) { // Regions +/// Returns all cells whose centers are within the [polygon] at [resolution]. List polygonToCells(GeoPolygon polygon, int resolution) { return _catchJsError(() { final coords = _geoPolygonToJsCoords(polygon); @@ -442,6 +499,8 @@ List polygonToCells(GeoPolygon polygon, int resolution) { }); } +/// Returns cells within the [polygon] using the specified containment [mode]. +/// /// Only [ContainmentMode.center] is supported on web (h3-js v4 limitation). List polygonToCellsExperimental( GeoPolygon polygon, @@ -457,6 +516,8 @@ List polygonToCellsExperimental( ); } +/// Returns the outlines of a set of [cells] as a GeoJSON-style +/// multi-polygon. List>> cellsToMultiPolygon(List cells) { return _catchJsError(() { final jsArr = [for (final c in cells) _toJs(c)].toJS; @@ -496,8 +557,10 @@ JSAny _geoPolygonToJsCoords(GeoPolygon polygon) { // Utilities +/// Returns the number of resolution-0 cells (always 122). int res0CellCount() => 122; +/// Returns all 122 resolution-0 cells. List getRes0Cells() { return _catchJsError(() { final cells = js.h3.getRes0Cells(); @@ -505,8 +568,10 @@ List getRes0Cells() { }); } +/// Returns the number of pentagons per resolution (always 12). int pentagonCount() => 12; +/// Returns all 12 pentagon cells at the given [resolution]. List getPentagons(int resolution) { return _catchJsError(() { final cells = js.h3.getPentagons(resolution.toJS); @@ -514,16 +579,27 @@ List getPentagons(int resolution) { }); } +/// Version constants for the h3_core package and the underlying H3 C library. abstract final class H3Version { + /// The h3_core Dart package version. static const String package = '1.0.1'; + + /// The H3 C library version string. static const String native = '4.4.1'; + + /// The H3 C library major version. static const int major = 4; + + /// The H3 C library minor version. static const int minor = 4; + + /// The H3 C library patch version. static const int patch = 1; } // Async — on web h3-js is sync, so these just wrap in a future. +/// Async version of [polygonToCells]; wraps in a future on web. Future> polygonToCellsAsync( GeoPolygon polygon, int resolution, @@ -531,14 +607,17 @@ Future> polygonToCellsAsync( return polygonToCells(polygon, resolution); } +/// Async version of [gridDisk]; wraps in a future on web. Future> gridDiskAsync(H3Index origin, int k) async { return gridDisk(origin, k); } +/// Async version of [compactCells]; wraps in a future on web. Future> compactCellsAsync(List cells) async { return compactCells(cells); } +/// Async version of [uncompactCells]; wraps in a future on web. Future> uncompactCellsAsync( List cells, int resolution, diff --git a/lib/src/models/cell_boundary.dart b/lib/src/models/cell_boundary.dart index d6d2d49..6bffb90 100644 --- a/lib/src/models/cell_boundary.dart +++ b/lib/src/models/cell_boundary.dart @@ -1,9 +1,11 @@ import 'lat_lng.dart'; -/// Cell boundary as a list of vertices in degrees. +/// Cell boundary as an ordered list of vertices in degrees. class CellBoundary { + /// The boundary vertices in order. Typically 6 for hexagons, 5 for pentagons. final List vertices; + /// Creates a [CellBoundary] from an ordered list of [vertices]. const CellBoundary(this.vertices); @override diff --git a/lib/src/models/coord_ij.dart b/lib/src/models/coord_ij.dart index 639f344..de5f973 100644 --- a/lib/src/models/coord_ij.dart +++ b/lib/src/models/coord_ij.dart @@ -1,8 +1,15 @@ -/// IJ coordinates for local coordinate operations. +/// Local IJ coordinate pair for grid-local coordinate operations. +/// +/// IJ coordinates are a 2D coordinate system anchored at a given origin cell, +/// useful for local grid traversal and distance calculations. class CoordIJ { + /// The I (column) coordinate. final int i; + + /// The J (row) coordinate. final int j; + /// Creates a [CoordIJ] with the given [i] and [j] coordinates. const CoordIJ(this.i, this.j); @override diff --git a/lib/src/models/geo_polygon.dart b/lib/src/models/geo_polygon.dart index 05f4883..7d71fa1 100644 --- a/lib/src/models/geo_polygon.dart +++ b/lib/src/models/geo_polygon.dart @@ -15,10 +15,15 @@ enum ContainmentMode { overlappingBbox, } -/// Polygon for region operations. +/// Polygon defined by an exterior ring and optional holes, for region +/// operations such as [polygonToCells]. class GeoPolygon { + /// The exterior ring of the polygon as a list of coordinates. final List exterior; + + /// Optional interior rings (holes) to exclude from the polygon. final List> holes; + /// Creates a [GeoPolygon] with an [exterior] ring and optional [holes]. const GeoPolygon(this.exterior, [this.holes = const []]); } diff --git a/lib/src/models/h3_index.dart b/lib/src/models/h3_index.dart index 75e20cc..349d793 100644 --- a/lib/src/models/h3_index.dart +++ b/lib/src/models/h3_index.dart @@ -1,11 +1,18 @@ -/// H3 cell/edge/vertex index stored as a hex string. +/// H3 cell, directed edge, or vertex index stored as a hex string. /// -/// String-backed to avoid JS number precision loss (H3 indexes are 64-bit, -/// JS doubles only hold 53 bits exactly). +/// Backed by a [String] rather than [int] to avoid precision loss on the +/// web platform — H3 indexes are 64-bit integers, but JavaScript doubles +/// only hold 53 bits of integer precision. extension type const H3Index(String _hex) { + /// Creates an [H3Index] from a 64-bit integer value. factory H3Index.fromInt(int value) => H3Index(value.toRadixString(16)); + + /// Parses a hex string into an [H3Index], normalising to lower case. factory H3Index.parse(String hex) => H3Index(hex.toLowerCase()); + /// Returns the 64-bit integer value of this index. int toInt() => int.parse(_hex, radix: 16); + + /// Returns the lowercase hex string representation of this index. String toHex() => _hex; } diff --git a/lib/src/models/lat_lng.dart b/lib/src/models/lat_lng.dart index a0bcadc..a715e95 100644 --- a/lib/src/models/lat_lng.dart +++ b/lib/src/models/lat_lng.dart @@ -1,8 +1,12 @@ -/// Latitude/longitude in degrees. +/// Latitude/longitude coordinate pair in degrees. class LatLng { + /// Latitude in degrees, from -90 to 90. final double lat; + + /// Longitude in degrees, from -180 to 180. final double lng; + /// Creates a coordinate from [lat] and [lng] in degrees. const LatLng(this.lat, this.lng); @override