voxcity.downloader.ocean ======================== .. py:module:: voxcity.downloader.ocean .. autoapi-nested-parse:: Ocean detection using OSM coastlines via Overpass API. OSM handles oceans by the "absence of land" principle: 1. The renderer starts with a blue canvas 2. Land polygons (derived from natural=coastline) are drawn on top 3. Anything not covered by land is ocean This module queries coastlines from Overpass API and determines land/ocean based on the coastline orientation rule: land is on the LEFT of the coastline. For areas without coastlines, we check if the point is in the ocean using a simple heuristic based on nearby land features. Attributes ---------- .. autoapisummary:: voxcity.downloader.ocean.CACHE_DIR voxcity.downloader.ocean.get_land_mask_from_osm_land_polygons Functions --------- .. autoapisummary:: voxcity.downloader.ocean.get_land_polygon_for_area voxcity.downloader.ocean.get_cache_path voxcity.downloader.ocean.query_coastlines_from_overpass voxcity.downloader.ocean.build_coastline_polygons voxcity.downloader.ocean.is_polygon_land voxcity.downloader.ocean.check_if_area_is_ocean_via_land_features voxcity.downloader.ocean.get_land_mask_from_coastlines voxcity.downloader.ocean.get_ocean_class_for_source voxcity.downloader.ocean.apply_ocean_mask_to_grid Module Contents --------------- .. py:data:: CACHE_DIR .. py:function:: get_land_polygon_for_area(rectangle_vertices: List[Tuple[float, float]], use_cache: bool = False) Get the land polygon for a given area using OSM coastlines. This is the main entry point for ocean detection. It queries coastlines from Overpass API and builds a polygon representing land areas. :param rectangle_vertices: List of (lon, lat) tuples defining the area :param use_cache: Whether to use disk cache (default False) :returns: Shapely Polygon/MultiPolygon representing land, or None if no coastlines found .. py:function:: get_cache_path(rectangle_vertices: List[Tuple[float, float]], grid_shape: Tuple[int, int]) -> pathlib.Path Generate a cache filename based on the bounding box and grid shape. .. py:function:: query_coastlines_from_overpass(min_lat: float, min_lon: float, max_lat: float, max_lon: float, buffer_deg: float = 0.1, max_retries: int = 2) -> Tuple[dict, bool] Query coastline ways from Overpass API. :param min_lat: Bounding box :param min_lon: Bounding box :param max_lat: Bounding box :param max_lon: Bounding box :param buffer_deg: Buffer around bbox to catch coastlines that might affect the area :param max_retries: Per-endpoint retry count for transient errors (e.g., 429/5xx) :returns: Tuple ``(data, ok)`` where ``data`` is the Overpass JSON response (``{'elements': []}`` on failure) and ``ok`` is True only if the API actually returned a successful response. Callers should treat ``ok == False`` as "API failure" rather than "no coastlines in area". .. py:function:: build_coastline_polygons(overpass_data: dict, bbox: Tuple[float, float, float, float]) Build land polygons by splitting bbox with coastlines. Algorithm: 1. Merge bbox boundary with all clipped coastlines to form a network 2. Use polygonize to create all possible polygons 3. For each polygon, determine if it's land by checking the coastline direction (land is on the LEFT of coastline when walking along it) :returns: Shapely polygon representing land area, or None if processing fails .. py:function:: is_polygon_land(polygon, coastline_segments) Determine if a polygon is land based on coastline orientation. OSM Rule: Land is on the LEFT of the coastline direction. Algorithm (orientation-match, robust to non-convex / harbor-shaped polygons): 1. Force the polygon's exterior to CCW orientation. With CCW orientation, the polygon's interior lies on the LEFT of every exterior edge as you walk around it. 2. Walk consecutive vertex pairs ``(a, b)`` of the exterior. 3. For each edge whose midpoint lies on a coastline (within a tiny tolerance), look up the coastline segment containing that midpoint and compare its direction to the polygon edge direction via dot product: - dot > 0 -> edge follows coastline forward -> interior on LEFT of coastline -> LAND vote. - dot < 0 -> edge runs against coastline -> interior on RIGHT of coastline -> WATER vote. 4. Majority vote across all coastline-coincident edges. Defaults to land on tie or when no coastline-coincident edges exist (e.g., a polygon bounded entirely by the bbox boundary). .. py:function:: check_if_area_is_ocean_via_land_features(rectangle_vertices: List[Tuple[float, float]]) -> bool Quick check: if an area has buildings/roads/land-use features, it's not pure ocean. Returns True if the area appears to be mostly ocean (few land features). .. py:function:: get_land_mask_from_coastlines(rectangle_vertices: List[Tuple[float, float]], grid_shape: Tuple[int, int], use_cache: bool = True) -> numpy.ndarray Create a boolean mask where True = land, False = ocean. Uses Overpass API to query coastlines and determine land/ocean areas. Much faster than downloading the full 600MB land polygons file. :param rectangle_vertices: List of (lon, lat) tuples defining the area :param grid_shape: (rows, cols) of the output grid :param use_cache: Whether to cache the result :returns: Boolean array where True = land, False = ocean :rtype: np.ndarray .. py:data:: get_land_mask_from_osm_land_polygons .. py:function:: get_ocean_class_for_source(source: str) -> str Get the appropriate ocean/water class name for a given land cover source. .. py:function:: apply_ocean_mask_to_grid(grid: numpy.ndarray, rectangle_vertices: List[Tuple[float, float]], source: str = 'OpenStreetMap', ocean_class: Optional[str] = None) -> numpy.ndarray Apply ocean detection to an existing land cover grid. Cells that are: 1. Currently set to the default class (e.g., 'Developed space') 2. Located in ocean areas (outside OSM land polygons) Will be changed to the ocean/water class. :param grid: Land cover grid (2D array of class names) :param rectangle_vertices: Area coordinates :param source: Land cover source name :param ocean_class: Override for ocean class name (auto-detected if None) :returns: Updated grid with ocean areas classified as water