""" Grid generation for coverage calculations. """ import numpy as np from dataclasses import dataclass from typing import List, Tuple from app.geometry.haversine import haversine_distance @dataclass class BoundingBox: min_lat: float min_lon: float max_lat: float max_lon: float @dataclass class Grid: points: List[Tuple[float, float]] bounding_box: BoundingBox resolution: float radius: float class GridService: """Generate coverage grid points.""" @staticmethod def generate( center_lat: float, center_lon: float, radius: float, resolution: float, ) -> Grid: points = [] lat_step = resolution / 111000 lon_step = resolution / (111000 * np.cos(np.radians(center_lat))) lat_delta = radius / 111000 lon_delta = radius / (111000 * np.cos(np.radians(center_lat))) bbox = BoundingBox( min_lat=center_lat - lat_delta, min_lon=center_lon - lon_delta, max_lat=center_lat + lat_delta, max_lon=center_lon + lon_delta, ) lat = center_lat - lat_delta while lat <= center_lat + lat_delta: lon = center_lon - lon_delta while lon <= center_lon + lon_delta: dist = haversine_distance(center_lat, center_lon, lat, lon) if dist <= radius: points.append((lat, lon)) lon += lon_step lat += lat_step return Grid(points=points, bounding_box=bbox, resolution=resolution, radius=radius) @staticmethod def generate_multi_site(sites: list, radius: float, resolution: float) -> Grid: all_points = set() min_lat = min_lon = float("inf") max_lat = max_lon = float("-inf") for site in sites: grid = GridService.generate(site.lat, site.lon, radius, resolution) for p in grid.points: all_points.add((round(p[0], 7), round(p[1], 7))) min_lat = min(min_lat, grid.bounding_box.min_lat) min_lon = min(min_lon, grid.bounding_box.min_lon) max_lat = max(max_lat, grid.bounding_box.max_lat) max_lon = max(max_lon, grid.bounding_box.max_lon) return Grid( points=list(all_points), bounding_box=BoundingBox(min_lat, min_lon, max_lat, max_lon), resolution=resolution, radius=radius, )