New grid systems: Plus codes, Maidenhead, and GARS.#

This example demonstrates the new grid systems added to M3S: Plus codes (Open Location Code), Maidenhead locator system, and GARS (Global Area Reference System).

  • New Grid Systems in M3S: Plus Codes, Maidenhead, and GARS, Grid Systems Comparison (San Francisco Area), Cell Areas by Precision Level, Grid Density Comparison, Grid System Characteristics
  • Grid Precision Evolution: Cell Size and Coverage, Plus Codes Precision Evolution, Maidenhead Precision Evolution, GARS Precision Evolution
  • Global Coverage Examples: Grid Systems Around the World, San Francisco (37.77, -122.42), London (51.51, -0.13), Tokyo (35.68, 139.65), Sydney (-33.87, 151.21), São Paulo (-23.55, -46.63), Grid System Summary
Testing coordinates: 37.7749, -122.4194 (San Francisco)
============================================================

1. Plus codes (Open Location Code)
----------------------------------------
Plus code: 48V9+HQJF
Cell area: 0.0611 km²
Precision: 4
Number of neighbors: 8

2. Maidenhead Locator System
----------------------------------------
Maidenhead locator: CM87SS
Cell area: 33.9295 km²
Precision: 3
Number of neighbors: 8

3. GARS (Global Area Reference System)
----------------------------------------
GARS identifier: 116JV3
Cell area: 609.8944 km²
Precision: 2
Number of neighbors: 8

4. Bounding Box Queries
----------------------------------------
Bounding box: (37.67, -122.52) to (37.87, -122.32)
Plus codes cells in bbox: 6561
Maidenhead cells in bbox: 20
GARS cells in bbox: 4

5. Precision Comparison
----------------------------------------
Plus codes:
  Precision 1: 48 (area: 3746070.4678 km²)
  Precision 2: 48V9+ (area: 9806.4958 km²)
  Precision 3: 48V9+HQ (area: 24.4282 km²)
  Precision 4: 48V9+HQJF (area: 0.0611 km²)

Maidenhead:
  Precision 1: CM (area: 2030598.6063 km²)
  Precision 2: CM87 (area: 19612.7501 km²)
  Precision 3: CM87SS (area: 33.9295 km²)
  Precision 4: CM87SS95 (area: 0.3393 km²)

GARS:
  Precision 1: 116JV (area: 2443.7115 km²)
  Precision 2: 116JV3 (area: 609.8944 km²)
  Precision 3: 116JV37 (area: 67.8402 km²)

6. GeoDataFrame Integration
----------------------------------------
Plus codes intersect result: 1 cells
  Cell ID: 48V9+HQJF
Maidenhead intersect result: 1 cells
  Cell ID: CM87SS
GARS intersect result: 1 cells
  Cell ID: 116JV3

=== Creating Grid Systems Visualization ===
/home/runner/work/m3s/m3s/examples/new_grids_example.py:286: UserWarning: Attempting to set identical low and high ylims makes transformation singular; automatically expanding.
  ax3.set_ylim(0, max(cell_counts) * 1.1)
Grid comparison plots saved as 'new_grids_comparison.png'

=== Creating Precision Evolution Plots ===
Precision evolution plots saved as 'precision_evolution.png'

=== Creating Global Coverage Examples ===
Global coverage plots saved as 'global_coverage.png'

============================================================
New Grids Example completed successfully!
Generated visualization files:
- new_grids_comparison.png
- precision_evolution.png
- global_coverage.png
============================================================

import geopandas as gpd
import matplotlib.pyplot as plt
from shapely.geometry import Point

from m3s import GARSGrid, MaidenheadGrid, PlusCodeGrid


def main():
    """Demonstrate the new grid systems."""
    # Test coordinates (San Francisco)
    lat, lon = 37.7749, -122.4194
    print(f"Testing coordinates: {lat}, {lon} (San Francisco)")
    print("=" * 60)

    # Plus codes (Open Location Code)
    print("\n1. Plus codes (Open Location Code)")
    print("-" * 40)

    pluscode_grid = PlusCodeGrid(precision=4)
    pluscode_cell = pluscode_grid.get_cell_from_point(lat, lon)

    print(f"Plus code: {pluscode_cell.identifier}")
    print(f"Cell area: {pluscode_cell.area_km2:.4f} km²")
    print(f"Precision: {pluscode_cell.precision}")

    # Get neighbors
    neighbors = pluscode_grid.get_neighbors(pluscode_cell)
    print(f"Number of neighbors: {len(neighbors)}")

    # Maidenhead locator system
    print("\n2. Maidenhead Locator System")
    print("-" * 40)

    maidenhead_grid = MaidenheadGrid(precision=3)
    maidenhead_cell = maidenhead_grid.get_cell_from_point(lat, lon)

    print(f"Maidenhead locator: {maidenhead_cell.identifier}")
    print(f"Cell area: {maidenhead_cell.area_km2:.4f} km²")
    print(f"Precision: {maidenhead_cell.precision}")

    # Get neighbors
    neighbors = maidenhead_grid.get_neighbors(maidenhead_cell)
    print(f"Number of neighbors: {len(neighbors)}")

    # GARS (Global Area Reference System)
    print("\n3. GARS (Global Area Reference System)")
    print("-" * 40)

    gars_grid = GARSGrid(precision=2)
    gars_cell = gars_grid.get_cell_from_point(lat, lon)

    print(f"GARS identifier: {gars_cell.identifier}")
    print(f"Cell area: {gars_cell.area_km2:.4f} km²")
    print(f"Precision: {gars_cell.precision}")

    # Get neighbors
    neighbors = gars_grid.get_neighbors(gars_cell)
    print(f"Number of neighbors: {len(neighbors)}")

    # Demonstrate bbox functionality
    print("\n4. Bounding Box Queries")
    print("-" * 40)

    # Small bounding box around the test point
    min_lat, min_lon = lat - 0.1, lon - 0.1
    max_lat, max_lon = lat + 0.1, lon + 0.1

    print(
        f"Bounding box: ({min_lat:.2f}, {min_lon:.2f}) to ({max_lat:.2f}, {max_lon:.2f})"
    )

    # Get cells in bbox for each grid system
    pluscode_cells = pluscode_grid.get_cells_in_bbox(min_lat, min_lon, max_lat, max_lon)
    maidenhead_cells = maidenhead_grid.get_cells_in_bbox(
        min_lat, min_lon, max_lat, max_lon
    )
    gars_cells = gars_grid.get_cells_in_bbox(min_lat, min_lon, max_lat, max_lon)

    print(f"Plus codes cells in bbox: {len(pluscode_cells)}")
    print(f"Maidenhead cells in bbox: {len(maidenhead_cells)}")
    print(f"GARS cells in bbox: {len(gars_cells)}")

    # Demonstrate different precisions
    print("\n5. Precision Comparison")
    print("-" * 40)

    print("Plus codes:")
    for precision in range(1, 5):
        grid = PlusCodeGrid(precision=precision)
        cell = grid.get_cell_from_point(lat, lon)
        print(
            f"  Precision {precision}: {cell.identifier} (area: {cell.area_km2:.4f} km²)"
        )

    print("\nMaidenhead:")
    for precision in range(1, 5):
        grid = MaidenheadGrid(precision=precision)
        cell = grid.get_cell_from_point(lat, lon)
        print(
            f"  Precision {precision}: {cell.identifier} (area: {cell.area_km2:.4f} km²)"
        )

    print("\nGARS:")
    for precision in range(1, 4):
        grid = GARSGrid(precision=precision)
        cell = grid.get_cell_from_point(lat, lon)
        print(
            f"  Precision {precision}: {cell.identifier} (area: {cell.area_km2:.4f} km²)"
        )

    # GeoDataFrame integration example
    print("\n6. GeoDataFrame Integration")
    print("-" * 40)

    # Create a simple GeoDataFrame with a point
    gdf = gpd.GeoDataFrame(
        {"name": ["San Francisco"], "geometry": [Point(lon, lat)]}, crs="EPSG:4326"
    )

    # Intersect with different grid systems
    pluscode_result = pluscode_grid.intersects(gdf)
    maidenhead_result = maidenhead_grid.intersects(gdf)
    gars_result = gars_grid.intersects(gdf)

    print(f"Plus codes intersect result: {len(pluscode_result)} cells")
    if len(pluscode_result) > 0:
        print(f"  Cell ID: {pluscode_result.iloc[0]['cell_id']}")

    print(f"Maidenhead intersect result: {len(maidenhead_result)} cells")
    if len(maidenhead_result) > 0:
        print(f"  Cell ID: {maidenhead_result.iloc[0]['cell_id']}")

    print(f"GARS intersect result: {len(gars_result)} cells")
    if len(gars_result) > 0:
        print(f"  Cell ID: {gars_result.iloc[0]['cell_id']}")


def plot_grid_systems_comparison():
    """Create comprehensive plots comparing the new grid systems."""
    print("\n=== Creating Grid Systems Visualization ===")

    # Test coordinates (San Francisco)
    center_lat, center_lon = 37.7749, -122.4194

    # Define visualization area around San Francisco
    offset = 0.05  # Larger area for better visualization
    bounds = (
        center_lon - offset,
        center_lat - offset,
        center_lon + offset,
        center_lat + offset,
    )

    # Create grid instances with different precisions for visualization
    grids = {
        "Plus Codes": PlusCodeGrid(precision=3),
        "Maidenhead": MaidenheadGrid(precision=3),
        "GARS": GARSGrid(precision=2),
    }

    # Create figure with subplots
    fig, axes = plt.subplots(2, 2, figsize=(16, 12))
    fig.suptitle(
        "New Grid Systems in M3S: Plus Codes, Maidenhead, and GARS",
        fontsize=16,
        fontweight="bold",
    )

    # Plot 1: Grid systems overlay comparison
    ax1 = axes[0, 0]
    ax1.set_title("Grid Systems Comparison (San Francisco Area)")
    ax1.set_xlabel("Longitude")
    ax1.set_ylabel("Latitude")

    colors = ["red", "blue", "green"]
    alphas = [0.6, 0.4, 0.3]

    # Plot each grid system
    for i, (name, grid) in enumerate(grids.items()):
        try:
            cells = grid.get_cells_in_bbox(*bounds)
            if cells:
                # Create GeoDataFrame for plotting
                cell_data = []
                for cell in cells[:25]:  # Limit for visibility
                    cell_data.append({"geometry": cell.polygon, "system": name})

                if cell_data:
                    gdf = gpd.GeoDataFrame(cell_data)
                    gdf.boundary.plot(
                        ax=ax1,
                        color=colors[i],
                        alpha=alphas[i],
                        linewidth=1.5,
                        label=name,
                    )
        except Exception as e:
            print(f"Warning: Could not plot {name}: {e}")

    # Add center point
    ax1.plot(center_lon, center_lat, "ko", markersize=8, label="San Francisco")
    ax1.legend()
    ax1.set_xlim(bounds[0], bounds[2])
    ax1.set_ylim(bounds[1], bounds[3])
    ax1.grid(True, alpha=0.3)

    # Plot 2: Cell area comparison across precisions
    ax2 = axes[0, 1]
    ax2.set_title("Cell Areas by Precision Level")
    ax2.set_xlabel("Precision Level")
    ax2.set_ylabel("Cell Area (km²)")
    ax2.set_yscale("log")

    # Calculate areas for different precisions
    precision_ranges = {
        "Plus Codes": range(1, 6),
        "Maidenhead": range(1, 5),
        "GARS": range(1, 4),
    }
    grid_classes = {
        "Plus Codes": PlusCodeGrid,
        "Maidenhead": MaidenheadGrid,
        "GARS": GARSGrid,
    }

    for i, (name, precisions) in enumerate(precision_ranges.items()):
        areas = []
        precision_levels = []

        for precision in precisions:
            try:
                grid = grid_classes[name](precision=precision)
                cell = grid.get_cell_from_point(center_lat, center_lon)
                areas.append(cell.area_km2)
                precision_levels.append(precision)
            except Exception as e:
                print(
                    f"Warning: Could not get area for {name} precision {precision}: {e}"
                )
                continue

        if areas:
            ax2.plot(
                precision_levels,
                areas,
                "o-",
                color=colors[i],
                linewidth=2,
                markersize=6,
                label=name,
            )

    ax2.legend()
    ax2.grid(True, alpha=0.3)

    # Plot 3: Grid density comparison (cells per unit area)
    ax3 = axes[1, 0]
    ax3.set_title("Grid Density Comparison")
    ax3.set_xlabel("Grid System")
    ax3.set_ylabel("Number of Cells in Test Area")

    cell_counts = []
    grid_names = []

    for name, grid in grids.items():
        try:
            cells = grid.get_cells_in_bbox(*bounds)
            cell_counts.append(len(cells))
            grid_names.append(name)
        except Exception as e:
            print(f"Warning: Could not count cells for {name}: {e}")
            continue

    if cell_counts:
        bars = ax3.bar(
            grid_names, cell_counts, color=colors[: len(cell_counts)], alpha=0.7
        )
        ax3.set_ylim(0, max(cell_counts) * 1.1)

        # Add value labels on bars
        for bar, count in zip(bars, cell_counts):
            ax3.text(
                bar.get_x() + bar.get_width() / 2,
                bar.get_height() + max(cell_counts) * 0.01,
                str(count),
                ha="center",
                va="bottom",
                fontweight="bold",
            )

    ax3.grid(True, alpha=0.3)

    # Plot 4: Individual grid system details
    ax4 = axes[1, 1]
    ax4.set_title("Grid System Characteristics")
    ax4.axis("off")

    # Create text summary of grid characteristics
    characteristics = {
        "Plus Codes": {
            "Full Name": "Open Location Code",
            "Developed by": "Google",
            "Cell Shape": "Rectangular",
            "Precision Range": "1-11",
            "Use Case": "Address replacement",
        },
        "Maidenhead": {
            "Full Name": "Maidenhead Locator System",
            "Developed by": "Amateur Radio",
            "Cell Shape": "Rectangular",
            "Precision Range": "1-8",
            "Use Case": "Ham radio communication",
        },
        "GARS": {
            "Full Name": "Global Area Reference System",
            "Developed by": "NGA (formerly NIMA)",
            "Cell Shape": "Rectangular",
            "Precision Range": "1-3",
            "Use Case": "Military/government mapping",
        },
    }

    y_pos = 0.9
    for name, details in characteristics.items():
        ax4.text(
            0.05,
            y_pos,
            f"{name}:",
            fontsize=12,
            fontweight="bold",
            transform=ax4.transAxes,
            color=colors[list(characteristics.keys()).index(name)],
        )
        y_pos -= 0.06

        for key, value in details.items():
            ax4.text(
                0.1, y_pos, f"{key}: {value}", fontsize=10, transform=ax4.transAxes
            )
            y_pos -= 0.05
        y_pos -= 0.03

    plt.tight_layout()
    plt.savefig("new_grids_comparison.png", dpi=150, bbox_inches="tight")
    print("Grid comparison plots saved as 'new_grids_comparison.png'")
    plt.show()


def plot_precision_evolution():
    """Create plots showing how grid precision affects cell size and coverage."""
    print("\n=== Creating Precision Evolution Plots ===")

    # Test coordinates
    center_lat, center_lon = 37.7749, -122.4194

    fig, axes = plt.subplots(1, 3, figsize=(18, 6))
    fig.suptitle(
        "Grid Precision Evolution: Cell Size and Coverage",
        fontsize=16,
        fontweight="bold",
    )

    colors = ["red", "blue", "green"]
    grid_systems = [
        ("Plus Codes", PlusCodeGrid, range(2, 6)),
        ("Maidenhead", MaidenheadGrid, range(2, 5)),
        ("GARS", GARSGrid, range(1, 4)),
    ]

    for idx, (name, grid_class, precision_range) in enumerate(grid_systems):
        ax = axes[idx]
        ax.set_title(f"{name} Precision Evolution")
        ax.set_xlabel("Longitude")
        ax.set_ylabel("Latitude")

        # Plot different precision levels
        for precision in precision_range:
            try:
                grid = grid_class(precision=precision)
                cell = grid.get_cell_from_point(center_lat, center_lon)

                # Create GeoDataFrame for this cell
                gdf = gpd.GeoDataFrame([{"geometry": cell.polygon}])

                # Plot with different alpha based on precision
                alpha = 0.3 + (precision - min(precision_range)) * 0.2
                gdf.boundary.plot(
                    ax=ax,
                    color=colors[idx],
                    alpha=alpha,
                    linewidth=2,
                    label=f"Precision {precision}",
                )

            except Exception as e:
                print(f"Warning: Could not plot {name} precision {precision}: {e}")
                continue

        # Add center point
        ax.plot(center_lon, center_lat, "ko", markersize=6)
        ax.legend()
        ax.grid(True, alpha=0.3)

        # Set reasonable bounds around the center point
        offset = 0.02
        ax.set_xlim(center_lon - offset, center_lon + offset)
        ax.set_ylim(center_lat - offset, center_lat + offset)

    plt.tight_layout()
    plt.savefig("precision_evolution.png", dpi=150, bbox_inches="tight")
    print("Precision evolution plots saved as 'precision_evolution.png'")
    plt.show()


def plot_global_coverage():
    """Create a plot showing global coverage examples of the grid systems."""
    print("\n=== Creating Global Coverage Examples ===")

    # Define interesting global locations
    locations = {
        "San Francisco": (37.7749, -122.4194),
        "London": (51.5074, -0.1278),
        "Tokyo": (35.6762, 139.6503),
        "Sydney": (-33.8688, 151.2093),
        "São Paulo": (-23.5505, -46.6333),
    }

    fig, axes = plt.subplots(2, 3, figsize=(18, 12))
    fig.suptitle(
        "Global Coverage Examples: Grid Systems Around the World",
        fontsize=16,
        fontweight="bold",
    )

    # Flatten axes for easier iteration
    axes_flat = axes.flatten()

    # Create one subplot per location
    for idx, (city, (lat, lon)) in enumerate(locations.items()):
        if idx >= len(axes_flat):
            break

        ax = axes_flat[idx]
        ax.set_title(f"{city} ({lat:.2f}, {lon:.2f})")
        ax.set_xlabel("Longitude")
        ax.set_ylabel("Latitude")

        colors = ["red", "blue", "green"]
        grids = [
            ("Plus Codes", PlusCodeGrid(precision=3)),
            ("Maidenhead", MaidenheadGrid(precision=3)),
            ("GARS", GARSGrid(precision=2)),
        ]

        offset = 0.05
        bounds = (lon - offset, lat - offset, lon + offset, lat + offset)

        for i, (name, grid) in enumerate(grids):
            try:
                # Get the cell containing this location
                cell = grid.get_cell_from_point(lat, lon)
                gdf = gpd.GeoDataFrame([{"geometry": cell.polygon}])
                gdf.boundary.plot(
                    ax=ax, color=colors[i], alpha=0.7, linewidth=2, label=name
                )

                # Add cell identifier as text
                centroid = cell.polygon.centroid
                ax.annotate(
                    f"{name}: {cell.identifier}",
                    xy=(centroid.x, centroid.y),
                    xytext=(5, 5),
                    textcoords="offset points",
                    fontsize=8,
                    bbox={"boxstyle": "round,pad=0.3", "fc": colors[i], "alpha": 0.3},
                )

            except Exception as e:
                print(f"Warning: Could not plot {name} for {city}: {e}")
                continue

        # Add location point
        ax.plot(lon, lat, "ko", markersize=6, label=city)
        ax.legend(fontsize=8)
        ax.grid(True, alpha=0.3)
        ax.set_xlim(bounds[0], bounds[2])
        ax.set_ylim(bounds[1], bounds[3])

    # Use the last subplot for summary statistics
    if len(locations) < len(axes_flat):
        ax_summary = axes_flat[-1]
        ax_summary.set_title("Grid System Summary")
        ax_summary.axis("off")

        # Create summary table
        summary_data = []
        for name, grid_class in [
            ("Plus Codes", PlusCodeGrid),
            ("Maidenhead", MaidenheadGrid),
            ("GARS", GARSGrid),
        ]:
            try:
                grid = grid_class(precision=2 if name != "GARS" else 1)
                cell = grid.get_cell_from_point(37.7749, -122.4194)  # SF coordinates
                summary_data.append([name, f"{cell.area_km2:.2f} km²", cell.identifier])
            except Exception:
                summary_data.append([name, "Error", "N/A"])

        # Create table
        table = ax_summary.table(
            cellText=summary_data,
            colLabels=["Grid System", "Example Area", "SF Identifier"],
            cellLoc="center",
            loc="center",
            colWidths=[0.3, 0.3, 0.4],
        )
        table.auto_set_font_size(False)
        table.set_fontsize(10)
        table.scale(1, 2)

    plt.tight_layout()
    plt.savefig("global_coverage.png", dpi=150, bbox_inches="tight")
    print("Global coverage plots saved as 'global_coverage.png'")
    plt.show()


if __name__ == "__main__":
    main()

    # Create comprehensive visualizations
    plot_grid_systems_comparison()
    plot_precision_evolution()
    plot_global_coverage()

    print("\n" + "=" * 60)
    print("New Grids Example completed successfully!")
    print("Generated visualization files:")
    print("- new_grids_comparison.png")
    print("- precision_evolution.png")
    print("- global_coverage.png")
    print("=" * 60)

Total running time of the script: (0 minutes 9.514 seconds)

Gallery generated by Sphinx-Gallery