flutter_h3 0.1.1 copy "flutter_h3: ^0.1.1" to clipboard
flutter_h3: ^0.1.1 copied to clipboard

A Flutter plugin that provides an FFI interface to Uber's H3 geospatial indexing system. Supports polygon-to-cell conversion, geolocation indexing and more.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_h3/flutter_h3.dart';
import 'dart:math';

// Main entry point for the Flutter H3 example app
void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // Victoria Station polygon (covering the general station area)
  // The polygon must be closed (first and last points must be identical)
  final List<List<double>> victoriaStationPolygon = [
    [51.49455159458547, -0.1446546696172812], // Starting point 
    [51.49417708873512, -0.1433847336885552], // 
    [51.49506367987514, -0.1428248239074459], // 
    [51.49546415697182, -0.1431919795735559], // 
    [51.49565626552264, -0.1438897584222176], // 
    [51.49455159458547, -0.1446546696172812], // Back to start
  ];
  
  // Victoria Station approximate center point (calculated from polygon)
  final double victoriaLat = 51.49488255713804;
  final double victoriaLng = -0.1437692030418115;
  
  // Sample location (San Francisco)
  final double sfLat = 37.775938728915946;
  final double sfLng = -122.41795063018799;
  
  // H3 indices and data
  int? victoriaH3Index;
  String? victoriaH3String;
  List<int> victoriaStationCells = [];
  List<int> victoriaStationCellsExperimental = [];
  int victoriaPolygonResolution = 13; // Default resolution we're trying to use
  
  // San Francisco data
  int? sfH3Index;
  String? sfH3String;
  Map<String, double>? sfCenter;
  dynamic victoriaBoundary; // Use dynamic for now to debug type
  dynamic sfBoundary; // Use dynamic for now to debug type
  List<int> surroundingCells = [];
  List<int> childCells = [];
  int? parentIndex;

  String? errorMessage;

  @override
  void initState() {
    super.initState();
    
    try {
      // STEP 1: Convert a latitude/longitude coordinate to an H3 index
      // This demonstrates FlutterH3.latLngToCell
      final victoriaH3Index = FlutterH3.latLngToCell(victoriaLat, victoriaLng, 10);
      victoriaH3String = FlutterH3.h3ToString(victoriaH3Index);
      print('Victoria Station H3 Index (Resolution 10): $victoriaH3String');
      
      // STEP 2: Get the boundary of an H3 cell
      // This demonstrates FlutterH3.cellToBoundary
      victoriaBoundary = FlutterH3.cellToBoundary(victoriaH3Index);
      print('Victoria boundary type: ${victoriaBoundary.runtimeType}');
      print('Victoria boundary value: $victoriaBoundary');
      print('Victoria boundary length: ${victoriaBoundary?.length}');
      if (victoriaBoundary is List && victoriaBoundary.isNotEmpty) {
        print('First element type: ${victoriaBoundary[0].runtimeType}');
        print('First element value: ${victoriaBoundary[0]}');
      }
      
      // Print Victoria polygon coordinates for debug
      print('Victoria Station polygon:');
      for (var point in victoriaStationPolygon) {
        print('  [${point[0]}, ${point[1]}]');
      }
      
      // STEP 3: Convert a polygon to H3 cells at different resolutions
      // This demonstrates FlutterH3.polygonToCells
      for (int resolution = 9; resolution <= 15; resolution++) {
        print('Trying resolution $resolution for Victoria Station polygon...');
        try {
          List<int> cells = FlutterH3.polygonToCells(victoriaStationPolygon, null, resolution);
          print('✅ Resolution $resolution produced ${cells.length} cells');
          
          // Store cells if this is resolution 13
          if (resolution == 13) {
            victoriaStationCells = cells;
            victoriaPolygonResolution = 13; // Explicitly set to 13
            print('Found ${cells.length} cells at resolution 13');
            // Print first few cells for debugging
            if (cells.isNotEmpty) {
              print('Sample cells at resolution 13:');
              for (var cell in cells.take(5)) {
                print('  ${FlutterH3.h3ToString(cell)}');
              }
            }
          }
        } catch (e) {
          print('❌ Error at resolution $resolution: $e');
        }
      }
      
      // Only fall back to resolution 10 if we got no cells at resolution
      if (victoriaStationCells.isEmpty) {
        print('⚠️ No cells found at resolution 13, falling back to resolution 10');
        victoriaStationCells = FlutterH3.polygonToCells(victoriaStationPolygon, null, 10);
        victoriaPolygonResolution = 10;
        print('Fallback to resolution 10: ${victoriaStationCells.length} cells');
      }
      
      print('Final resolution being used: $victoriaPolygonResolution with ${victoriaStationCells.length} cells');
      
      // STEP 4: Use the experimental polygon-to-cells function with different flags
      // This demonstrates FlutterH3.polygonToCellsExperimental
      try {
        // Use flag 1 for full cell containment mode (more accurate but slower)
        List<int> experimentalCells = FlutterH3.polygonToCellsExperimental(
          victoriaStationPolygon, 
          null, 
          victoriaPolygonResolution, 
          1 // CONTAINMENT_FULL
        );
        victoriaStationCellsExperimental = experimentalCells;
        print('Found ${experimentalCells.length} cells with experimental algorithm (FULL containment)');
        
        // Use flag 2 for overlapping containment (any cell that overlaps the polygon)
        List<int> overlapCells = FlutterH3.polygonToCellsExperimental(
          victoriaStationPolygon, 
          null, 
          victoriaPolygonResolution, 
          2 // CONTAINMENT_OVERLAPPING
        );
        print('Found ${overlapCells.length} cells with experimental algorithm (OVERLAPPING containment)');
      } catch (e) {
        print('Error with experimental algorithm: $e');
      }
      
      // STEP 5: Demonstrate San Francisco examples
      // This shows more H3 functions
      final sfH3Index = FlutterH3.latLngToCell(sfLat, sfLng, 9);
      sfH3String = FlutterH3.h3ToString(sfH3Index);
      
      // Convert an H3 index back to its center lat/lng
      final sfCenterLatLng = FlutterH3.cellToLatLng(sfH3Index);
      if (sfCenterLatLng != null && sfCenterLatLng.length >= 2) {
        sfCenter = {
          'lat': sfCenterLatLng['lat'] ?? 0.0, 
          'lng': sfCenterLatLng['lng'] ?? 0.0
        };
      }
      
      // Get the boundary of the San Francisco cell
      sfBoundary = FlutterH3.cellToBoundary(sfH3Index);
      print('SF Boundary type: ${sfBoundary.runtimeType}');
      print('SF Boundary value: $sfBoundary');
      print('SF Boundary length: ${sfBoundary?.length}');
      if (sfBoundary is List && sfBoundary.isNotEmpty) {
        print('First element type: ${sfBoundary[0].runtimeType}');
        print('First element value: ${sfBoundary[0]}');
      }
      
      // Get surrounding cells (k-ring)
      surroundingCells = FlutterH3.gridDisk(sfH3Index, 1);
    
      // Get child cells at higher resolution
      childCells = FlutterH3.cellToChildren(sfH3Index, 10);
      if (childCells.length > 7) {
        childCells = childCells.sublist(0, 7);
      }
    } catch (e) {
      setState(() {
        errorMessage = 'Error initializing H3: $e';
      });
      print('Error: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('H3 Indexing - Victoria Station'),
          backgroundColor: Colors.indigo,
          foregroundColor: Colors.white,
        ),
        body: errorMessage != null
            ? Center(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      const Icon(Icons.error_outline, color: Colors.red, size: 48),
                      const SizedBox(height: 16),
                      Text(
                        'Initialization Error',
                        style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                      ),
                      const SizedBox(height: 8),
                      Text(errorMessage!),
                      const SizedBox(height: 24),
                      ElevatedButton(
                        onPressed: () {
                          setState(() {
                            errorMessage = null;
                          });
                          initState();
                        },
                        child: const Text('Retry'),
                      ),
                    ],
                  ),
                ),
              )
            : SingleChildScrollView(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // VICTORIA STATION SECTION
                    _buildVictoriaStationSection(),
                    
                    const Divider(height: 40, thickness: 2),
                    
                    // SAN FRANCISCO SECTION (COLLAPSED/SECONDARY)
                    _buildHeader('Additional H3 Examples (San Francisco)'),
                    
                    _buildSection(
                      'San Francisco Location',
                      'Latitude: $sfLat\nLongitude: $sfLng',
                    ),
                    
                    if (sfH3Index != null) _buildSection(
                      'H3 Index (Resolution 9)',
                      'Integer: $sfH3Index\nString: ${sfH3String ?? "N/A"}',
                    ),
                    
                    if (sfCenter != null) _buildSection(
                      'Center Point',
                      'Latitude: ${sfCenter!['lat']}\nLongitude: ${sfCenter!['lng']}',
                    ),
                    
                    if (sfBoundary != null) _buildSection(
                      'Boundary',
                      _formatBoundaryPoints(sfBoundary!),
                    ),
                    
                    if (surroundingCells.isNotEmpty) _buildSection(
                      'Surrounding Cells (k=1)',
                      'Found ${surroundingCells.length} cells\nFirst few: ${surroundingCells.take(3).map((idx) => FlutterH3.h3ToString(idx)).join(", ")}...',
                    ),
                  ],
                ),
              ),
      ),
    );
  }

  // Helper method to create section headers
  Widget _buildHeader(String title) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 16.0, top: 8.0),
      child: Text(
        title,
        style: const TextStyle(
          fontSize: 22,
          fontWeight: FontWeight.bold,
          color: Colors.indigo,
        ),
      ),
    );
  }

  // Helper method to create a map preview placeholder
  Widget _buildVictoriaMapPreview() {
    return Container(
      margin: const EdgeInsets.only(bottom: 16.0),
      height: 180,
      width: double.infinity,
      decoration: BoxDecoration(
        color: Colors.grey[200],
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: Colors.indigo.withOpacity(0.5), width: 2),
      ),
      child: const Center(
        child: Text(
          "Victoria Station, London\nH3 Grid Overlay",
          textAlign: TextAlign.center,
          style: TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }

  // Build the Victoria Station section with H3 cell data
  Widget _buildVictoriaStationSection() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        _buildHeader('Victoria Station'),
        const SizedBox(height: 12),
        
        Text('Location: $victoriaLat, $victoriaLng'),
        Text('H3 Index (res 10): $victoriaH3String'),
        const SizedBox(height: 8),
        
        Text('H3 Cells (standard algorithm, res $victoriaPolygonResolution): ${victoriaStationCells.length}'),
        if (victoriaStationCells.isNotEmpty)
          Container(
            height: 150,
            child: ListView.builder(
              itemCount: min(victoriaStationCells.length, 5),
              itemBuilder: (context, index) {
                return Text('• ${FlutterH3.h3ToString(victoriaStationCells[index])}');
              },
            ),
          )
        else
          Text('No H3 cells found with standard algorithm'),
          
        const SizedBox(height: 12),
        
        Text('H3 Cells (experimental algorithm, res $victoriaPolygonResolution): ${victoriaStationCellsExperimental.length}'),
        if (victoriaStationCellsExperimental.isNotEmpty)
          Container(
            height: 150,
            child: ListView.builder(
              itemCount: min(victoriaStationCellsExperimental.length, 5),
              itemBuilder: (context, index) {
                return Text('• ${FlutterH3.h3ToString(victoriaStationCellsExperimental[index])}');
              },
            ),
          )
        else
          Text('No H3 cells found with experimental algorithm'),
      ],
    );
  }

  // Helper method to build a section with title and content
  Widget _buildSection(String title, String content) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: const TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 8),
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: Colors.grey[200],
              borderRadius: BorderRadius.circular(8),
            ),
            child: Text(
              content,
              style: const TextStyle(
                fontFamily: 'monospace',
                fontSize: 14,
              ),
            ),
          ),
        ],
      ),
    );
  }

  // Format boundary points for display
  String _formatBoundaryPoints(dynamic boundary) {
    StringBuffer buffer = StringBuffer();
    if (boundary is List) {
      for (int i = 0; i < boundary.length; i++) {
        final item = boundary[i];
        if (item is List) {
          // If item is a list of doubles [lat, lng]
          if (item.length >= 2) {
            buffer.writeln('Point ${i + 1}: Lat: ${item[0].toStringAsFixed(6)}, Lng: ${item[1].toStringAsFixed(6)}');
          }
        } else if (item is Map) {
          // If item is a map with lat/lng keys
          if (item.containsKey('lat') && item.containsKey('lng')) {
            buffer.writeln('Point ${i + 1}: Lat: ${item['lat'].toStringAsFixed(6)}, Lng: ${item['lng'].toStringAsFixed(6)}');
          }
        } else if (item is double) {
          // If boundary is flat list of doubles [lat1, lng1, lat2, lng2, ...]
          if (i + 1 < boundary.length) {
            buffer.writeln('Point ${i ~/ 2 + 1}: Lat: ${item.toStringAsFixed(6)}, Lng: ${boundary[i+1].toStringAsFixed(6)}');
            i++; // Skip the lng value since we've used it
          }
        }
      }
    }
    return buffer.toString();
  }
}
1
likes
140
points
15
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin that provides an FFI interface to Uber's H3 geospatial indexing system. Supports polygon-to-cell conversion, geolocation indexing and more.

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

ffi, flutter, path, path_provider, plugin_platform_interface

More

Packages that depend on flutter_h3

Packages that implement flutter_h3