Source code for xoa.geo

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Geographic utilities
"""
# Copyright 2020-2021 Shom
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import math
import numpy as np
import numba

from . import coords as xcoords


#: Earth radius in meters
EARTH_RADIUS = 6371e3


@numba.vectorize
def _haversine_(lon0, lat0, lon1, lat1):
    """Haversine distance between two points on a **unit sphere**

    Parameters
    ----------
    lon0: float, array_like
        Longitude of the first point(s)
    lat0: float, array_like
        Latitude of the first point(s)
    lon1: float, array_like
        Longitude of the second point(s)
    lat1: float, array_like
        Latitude of the second point(s)

    Return
    ------
    float, array_like
        Distance(s)
    """
    deg2rad = math.pi / 180.
    dist = math.sin(deg2rad*(lat0-lat1)*0.5)**2
    dist += (math.cos(deg2rad*lat0) * math.cos(deg2rad*lat1) *
             math.sin(deg2rad*(lon0-lon1)*0.5)**2)
    dist = 2. * math.asin(math.sqrt(dist))
    return dist


[docs]def haversine(lon0, lat0, lon1, lat1, radius=EARTH_RADIUS): """Haversine distance between two points Parameters ---------- lon0: float, array_like Longitude of the first point(s) lat0: float, array_like Latitude of the first point(s) lon1: float, array_like Longitude of the second point(s) lat1: float, array_like Latitude of the second point(s) radius: float Radius of the sphere which defaults to the earth radius Return ------ float, array_like Distance(s) """ return _haversine_( np.double(lon0), np.double(lat0), np.double(lon1), np.double(lat1)) * radius
[docs]def bearing(lon0, lat0, lon1, lat1): """Compute the bearing angle (forward azimuth) Parameters ---------- lon0: float, array_like Longitude of the first point(s) lat0: float, array_like Latitude of the first point(s) lon1: float, array_like Longitude of the second point(s) lat1: float, array_like Latitude of the second point(s) Return ------ float, array_like Angle(s) """ return _bearing_( np.double(lon0), np.double(lat0), np.double(lon1), np.double(lat1))
@numba.vectorize def _bearing_(lon0, lat0, lon1, lat1): deg2rad = math.pi / 180. a = math.arctan2(math.cos(deg2rad*lat0)*math.sin(deg2rad*lat1) - math.sin(deg2rad*lat0)*math.cos(deg2rad*lat1)*math.cos(deg2rad*(lon1-lon0)), math.sin(deg2rad*(lon1-lon0))*math.cos(deg2rad*lat1)) return a * 180 / math.pi
[docs]def get_extent(extent, margin=0, square=False): """Compute the geographic extent in degrees Parameters ---------- extent: xarray.DataArray, xarray.Dataset, dict, tuple, list Either: - An array or dataset with longitude and latitude coordinates. - A dict with ``lon`` and ``lat`` keys: ``dict(lon=..., lat=...)`` - A two-element tuple of longitudes and latitudes: ``(lon, lat)`` - A extent list: ``[xmin, xmax, ymin, ymax]``. margin: float A relative fraction of the width and height that is used to set margins. For instance, a value of ``-0.1`` shrinks the box of 10% on each side. square: bool Force the box to be square in degrees. Return ------ list ``[xmin, xmax, ymin, ymax]`` Example ------- .. ipython:: python @suppress from xoa.geo import get_extent @suppress import numpy as np get_extent([10., 20., 10., 20.], margin=0.1) get_extent({"lon": np.linspace(10, 20, 5), "lat": np.linspace(10, 20, 5)}, square=True) get_extent((np.linspace(10, 20, 5), np.linspace(10, 20, 5)), margin=-.1, square=True) """ # Get min and max if hasattr(extent, "coords"): lon = xcoords.get_lon(extent) lat = xcoords.get_lat(extent) extent = (lon, lat) elif isinstance(extent, dict): extent = extent['lon'], extent['lat'] if isinstance(extent, (list, np.ndarray)): xmin, xmax, ymin, ymax = extent else: # tuple lon, lat = extent xmin = float(np.min(lon)) xmax = float(np.max(lon)) ymin = float(np.min(lat)) ymax = float(np.max(lat)) # Scale if square or margin: dx = xmax - xmin dy = ymax - ymin x0 = 0.5 * (xmin+xmax) y0 = 0.5 * (ymin+ymax) if square: aspect = dx * math.cos(y0*math.pi/180) / dy if aspect > 1: dy *= aspect else: dx /= aspect xmargin = margin * dx ymargin = margin * dy xmin = x0 - 0.5 * dx - xmargin xmax = x0 + 0.5 * dx + xmargin ymin = y0 - 0.5 * dy - ymargin ymax = y0 + 0.5 * dy + ymargin return [xmin, xmax, ymin, ymax]