コード例 #1
0
    def test_dist_approx_pass(self):
        """Test approximate distance functions"""
        data = np.array([
            # lat1, lon1, lat2, lon2, dist, dist_sph
            [45.5, -32.1, 14, 56, 7702.88906574, 8750.64119051],
            [45.5, 147.8, 14, -124, 7709.82781473, 8758.34146833],
            [45.5, 507.9, 14, -124, 7702.88906574, 8750.64119051],
            [45.5, -212.2, 14, -124, 7709.82781473, 8758.34146833],
            [-3, -130.1, 4, -30.5, 11079.7217421, 11087.0352544],
        ])
        compute_dist = np.stack([
            dist_approx(data[:, None, 0],
                        data[:, None, 1],
                        data[:, None, 2],
                        data[:, None, 3],
                        method="equirect")[:, 0, 0],
            dist_approx(data[:, None, 0],
                        data[:, None, 1],
                        data[:, None, 2],
                        data[:, None, 3],
                        method="geosphere")[:, 0, 0],
        ],
                                axis=-1)
        self.assertEqual(compute_dist.shape[0], data.shape[0])
        for d, cd in zip(data[:, 4:], compute_dist):
            self.assertAlmostEqual(d[0], cd[0])
            self.assertAlmostEqual(d[1], cd[1])

        for units, factor in zip(["radian", "degree", "km"],
                                 [np.radians(1.0), 1.0, ONE_LAT_KM]):
            factor /= ONE_LAT_KM
            compute_dist = np.stack([
                dist_approx(data[:, None, 0],
                            data[:, None, 1],
                            data[:, None, 2],
                            data[:, None, 3],
                            method="equirect",
                            units=units)[:, 0, 0],
                dist_approx(data[:, None, 0],
                            data[:, None, 1],
                            data[:, None, 2],
                            data[:, None, 3],
                            method="geosphere",
                            units=units)[:, 0, 0],
            ],
                                    axis=-1)
            self.assertEqual(compute_dist.shape[0], data.shape[0])
            for d, cd in zip(data[:, 4:], compute_dist):
                self.assertAlmostEqual(d[0] * factor, cd[0])
                self.assertAlmostEqual(d[1] * factor, cd[1])
コード例 #2
0
 def test_dist_approx_log_pass(self):
     """Test log-functionality of approximate distance functions"""
     data = np.array([
         # lat1, lon1, lat2, lon2, dist, dist_sph
         [0, 0, 0, 1, 111.12, 111.12],
         [-13, 179, 5, -179, 2011.84774049, 2012.30698122],
     ])
     for i, method in enumerate(["equirect", "geosphere"]):
         for units, factor in zip(["radian", "degree", "km"],
                                  [np.radians(1.0), 1.0, ONE_LAT_KM]):
             factor /= ONE_LAT_KM
             dist, vec = dist_approx(data[:, None, 0],
                                     data[:, None, 1],
                                     data[:, None, 2],
                                     data[:, None, 3],
                                     log=True,
                                     method=method,
                                     units=units)
             dist, vec = dist[:, 0, 0], vec[:, 0, 0]
             np.testing.assert_allclose(np.linalg.norm(vec, axis=-1), dist)
             np.testing.assert_allclose(dist, data[:, 4 + i] * factor)
             # both points on equator (no change in latitude)
             self.assertAlmostEqual(vec[0, 0], 0)
             # longitude from 179 to -179 is positive (!) in lon-direction
             np.testing.assert_array_less(100, vec[1, :] / factor)
コード例 #3
0
    def test_dist_approx_pass(self):
        """Test approximate distance functions"""
        data = np.array([
            # lat1, lon1, lat2, lon2, dist, dist_sph
            [45.5, -32.2, 14, 56, 7709.827814738594, 8758.34146833],
            [45.5, 147.8, 14, -124, 7709.827814738594, 8758.34146833],
            [45.5, 507.8, 14, -124, 7709.827814738594, 8758.34146833],
            [45.5, -212.2, 14, -124, 7709.827814738594, 8758.34146833],
        ])
        compute_dist = np.stack([
            dist_approx(data[:, None, 0],
                        data[:, None, 1],
                        data[:, None, 2],
                        data[:, None, 3],
                        method="equirect")[:, 0, 0],
            dist_approx(data[:, None, 0],
                        data[:, None, 1],
                        data[:, None, 2],
                        data[:, None, 3],
                        method="geosphere")[:, 0, 0],
        ],
                                axis=-1)
        self.assertEqual(compute_dist.shape[0], data.shape[0])
        for d, cd in zip(data[:, 4:], compute_dist):
            self.assertAlmostEqual(d[0], cd[0])
            self.assertAlmostEqual(d[1], cd[1])

        data = np.array([
            # lat1, lon1, lat2, lon2, dist, dist_sph
            [0, 0, 0, 1, 111.12, 111.12],
            [-13, 179, 5, -179, 2011.84774049, 2012.30698122],
        ])
        for i, method in enumerate(["equirect", "geosphere"]):
            dist, vec = dist_approx(data[:, None, 0],
                                    data[:, None, 1],
                                    data[:, None, 2],
                                    data[:, None, 3],
                                    log=True,
                                    method=method)
            dist, vec = dist[:, 0, 0], vec[:, 0, 0]
            self.assertTrue(np.allclose(np.linalg.norm(vec, axis=-1), dist))
            self.assertTrue(np.allclose(dist, data[:, 4 + i]))
            # both points on equator (no change in latitude)
            self.assertAlmostEqual(vec[0, 0], 0)
            # longitude from 179 to -179 is positive (!) in lon-direction
            self.assertTrue(np.all(vec[1, :] > 100))
コード例 #4
0
def _vtrans(t_lat, t_lon, t_tstep, metric="equirect"):
    """Translational vector and velocity at each track node.

    Parameters
    ----------
    t_lat : np.array
        track latitudes
    t_lon : np.array
        track longitudes
    t_tstep : np.array
        track time steps
    metric : str, optional
        Specify an approximation method to use for earth distances: "equirect" (faster) or
        "geosphere" (more accurate). See `dist_approx` function in `climada.util.coordinates`.
        Default: "equirect".

    Returns
    -------
    v_trans_norm : np.array
        Same shape as input, the first velocity is always 0.
    v_trans : np.array
        Directional vectors of velocity.
    """
    v_trans = np.zeros((t_lat.size, 2))
    v_trans_norm = np.zeros((t_lat.size, ))
    norm, vec = u_coord.dist_approx(t_lat[:-1, None],
                                    t_lon[:-1, None],
                                    t_lat[1:, None],
                                    t_lon[1:, None],
                                    log=True,
                                    normalize=False,
                                    method=metric)
    v_trans[1:, :] = vec[:, 0, 0]
    v_trans[1:, :] *= KMH_TO_MS / t_tstep[1:, None]
    v_trans_norm[1:] = norm[:, 0, 0]
    v_trans_norm[1:] *= KMH_TO_MS / t_tstep[1:]

    # limit to 30 nautical miles per hour
    msk = (v_trans_norm > 30 * KN_TO_MS)
    fact = 30 * KN_TO_MS / v_trans_norm[msk]
    v_trans[msk, :] *= fact[:, None]
    v_trans_norm[msk] *= fact
    return v_trans_norm, v_trans
コード例 #5
0
ファイル: trop_cyclone.py プロジェクト: mmyrte/climada_python
def _vtrans(t_lat, t_lon, t_tstep):
    """Translational vector and velocity at each track node.

    Parameters
    ----------
    t_lat : np.array
        track latitudes
    t_lon : np.array
        track longitudes
    t_tstep : np.array
        track time steps

    Returns
    -------
    v_trans_norm : np.array
        Same shape as input, the first velocity is always 0.
    v_trans : np.array
        Directional vectors of velocity.
    """
    v_trans = np.zeros((t_lat.size, 2))
    v_trans_norm = np.zeros((t_lat.size, ))
    norm, vec = dist_approx(t_lat[:-1, None],
                            t_lon[:-1, None],
                            t_lat[1:, None],
                            t_lon[1:, None],
                            log=True,
                            method="geosphere")
    v_trans[1:, :] = vec[:, 0, 0]
    v_trans[1:, :] *= KMH_TO_MS / t_tstep[1:, None]
    v_trans_norm[1:] = norm[:, 0, 0]
    v_trans_norm[1:] *= KMH_TO_MS / t_tstep[1:]

    # limit to 30 nautical miles per hour
    msk = (v_trans_norm > 30 * KN_TO_MS)
    fact = 30 * KN_TO_MS / v_trans_norm[msk]
    v_trans[msk, :] *= fact[:, None]
    v_trans_norm[msk] *= fact
    return v_trans_norm, v_trans
コード例 #6
0
def compute_windfields(track, centroids, model, metric="equirect"):
    """Compute 1-minute sustained winds (in m/s) at 10 meters above ground

    In a first step, centroids within reach of the track are determined so that wind fields will
    only be computed and returned for those centroids.

    Parameters
    ----------
    track : xr.Dataset
        Track infomation.
    centroids : 2d np.array
        Each row is a centroid [lat, lon].
        Centroids that are not within reach of the track are ignored.
    model : int
        Holland model selection according to MODEL_VANG.
    metric : str, optional
        Specify an approximation method to use for earth distances: "equirect" (faster) or
        "geosphere" (more accurate). See `dist_approx` function in `climada.util.coordinates`.
        Default: "equirect".

    Returns
    -------
    windfields : np.array of shape (npositions, nreachable, 2)
        Directional wind fields for each track position on those centroids within reach
        of the TC track.
    reachable_centr_idx : np.array of shape (nreachable,)
        List of indices of input centroids within reach of the TC track.
    """
    # copies of track data
    # Note that max wind records are not used in the Holland wind field models!
    t_lat, t_lon, t_tstep, t_rad, t_env, t_cen = [
        track[ar].values.copy() for ar in [
            'lat', 'lon', 'time_step', 'radius_max_wind',
            'environmental_pressure', 'central_pressure'
        ]
    ]

    # start with the assumption that no centroids are within reach
    npositions = t_lat.shape[0]
    reachable_centr_idx = np.zeros((0, ), dtype=np.int64)
    windfields = np.zeros((npositions, 0, 2), dtype=np.float64)

    # the wind field model requires at least two track positions because translational speed
    # as well as the change in pressure are required
    if npositions < 2:
        return windfields, reachable_centr_idx

    # normalize longitude values (improves performance of `dist_approx` and `_close_centroids`)
    mid_lon = 0.5 * sum(u_coord.lon_bounds(t_lon))
    u_coord.lon_normalize(t_lon, center=mid_lon)
    u_coord.lon_normalize(centroids[:, 1], center=mid_lon)

    # restrict to centroids within rectangular bounding boxes around track positions
    track_centr_msk = _close_centroids(t_lat, t_lon, centroids)
    track_centr = centroids[track_centr_msk]
    nreachable = track_centr.shape[0]
    if nreachable == 0:
        return windfields, reachable_centr_idx

    # compute distances and vectors to all centroids
    [d_centr], [v_centr] = u_coord.dist_approx(t_lat[None],
                                               t_lon[None],
                                               track_centr[None, :, 0],
                                               track_centr[None, :, 1],
                                               log=True,
                                               normalize=False,
                                               method=metric)

    # exclude centroids that are too far from or too close to the eye
    close_centr_msk = (d_centr < CENTR_NODE_MAX_DIST_KM) & (d_centr > 1e-2)
    if not np.any(close_centr_msk):
        return windfields, reachable_centr_idx
    v_centr_normed = np.zeros_like(v_centr)
    v_centr_normed[close_centr_msk] = v_centr[close_centr_msk] / d_centr[
        close_centr_msk, None]

    # make sure that central pressure never exceeds environmental pressure
    pres_exceed_msk = (t_cen > t_env)
    t_cen[pres_exceed_msk] = t_env[pres_exceed_msk]

    # extrapolate radius of max wind from pressure if not given
    t_rad[:] = estimate_rmw(t_rad, t_cen) * NM_TO_KM

    # translational speed of track at every node
    v_trans = _vtrans(t_lat, t_lon, t_tstep, metric=metric)
    v_trans_norm = v_trans[0]

    # adjust pressure at previous track point
    prev_pres = t_cen[:-1].copy()
    msk = (prev_pres < 850)
    prev_pres[msk] = t_cen[1:][msk]

    # compute b-value and derive (absolute) angular velocity
    if model == MODEL_VANG['H1980']:
        # convert recorded surface winds to gradient-level winds without translational influence
        t_vmax = track.max_sustained_wind.values.copy()
        t_gradient_winds = np.fmax(
            0, t_vmax - v_trans_norm) / GRADIENT_LEVEL_TO_SURFACE_WINDS
        hol_b = _B_holland_1980(t_gradient_winds[1:], t_env[1:], t_cen[1:])
        v_ang_norm = _stat_holland_1980(d_centr[1:], t_rad[1:], hol_b,
                                        t_env[1:], t_cen[1:], t_lat[1:],
                                        close_centr_msk[1:])
        v_ang_norm *= GRADIENT_LEVEL_TO_SURFACE_WINDS
    elif model == MODEL_VANG['H08']:
        # this model doesn't use the recorded surface winds
        hol_b = _bs_holland_2008(v_trans_norm[1:], t_env[1:], t_cen[1:],
                                 prev_pres, t_lat[1:], t_tstep[1:])
        v_ang_norm = _stat_holland_1980(d_centr[1:], t_rad[1:], hol_b,
                                        t_env[1:], t_cen[1:], t_lat[1:],
                                        close_centr_msk[1:])
    elif model == MODEL_VANG['H10']:
        # this model doesn't use the recorded surface winds
        hol_b = _bs_holland_2008(v_trans_norm[1:], t_env[1:], t_cen[1:],
                                 prev_pres, t_lat[1:], t_tstep[1:])
        t_vmax = _v_max_s_holland_2008(t_env[1:], t_cen[1:], hol_b)
        hol_x = _x_holland_2010(d_centr[1:], t_rad[1:], t_vmax, hol_b,
                                close_centr_msk[1:])
        v_ang_norm = _stat_holland_2010(d_centr[1:], t_vmax, t_rad[1:], hol_b,
                                        close_centr_msk[1:], hol_x)
    else:
        raise NotImplementedError

    # vectorial angular velocity
    hemisphere = 'N'
    if np.count_nonzero(t_lat < 0) > np.count_nonzero(t_lat > 0):
        hemisphere = 'S'
    v_ang_rotate = [1.0, -1.0] if hemisphere == 'N' else [-1.0, 1.0]
    v_ang_dir = np.array(v_ang_rotate)[..., :] * v_centr_normed[1:, :, ::-1]
    v_ang = np.zeros_like(v_ang_dir)
    v_ang[close_centr_msk[1:]] = v_ang_norm[close_centr_msk[1:], None] \
                                 * v_ang_dir[close_centr_msk[1:]]

    # Influence of translational speed decreases with distance from eye.
    # The "absorbing factor" is according to the following paper (see Fig. 7):
    #
    #   Mouton, F., & Nordbeck, O. (1999). Cyclone Database Manager. A tool
    #   for converting point data from cyclone observations into tracks and
    #   wind speed profiles in a GIS. UNED/GRID-Geneva.
    #   https://unepgrid.ch/en/resource/19B7D302
    #
    t_rad_bc = np.broadcast_arrays(t_rad[:, None], d_centr)[0]
    v_trans_corr = np.zeros_like(d_centr)
    v_trans_corr[close_centr_msk] = np.fmin(
        1, t_rad_bc[close_centr_msk] / d_centr[close_centr_msk])

    # add angular and corrected translational velocity vectors
    v_full = v_trans[1][1:, None, :] * v_trans_corr[1:, :, None] + v_ang
    v_full[np.isnan(v_full)] = 0

    windfields = np.zeros((npositions, nreachable, 2), dtype=np.float64)
    windfields[1:, :, :] = v_full
    [reachable_centr_idx] = track_centr_msk.nonzero()
    return windfields, reachable_centr_idx
コード例 #7
0
ファイル: trop_cyclone.py プロジェクト: mmyrte/climada_python
def compute_windfields(track, centroids, model):
    """Compute 1-minute sustained winds (in m/s) at 10 meters above ground

    Parameters:
        track (xr.Dataset): track infomation
        centroids (2d np.array): each row is a centroid [lat, lon]
        model (int): Holland model selection according to MODEL_VANG

    Returns:
        np.array
    """
    # copies of track data
    t_lat, t_lon, t_tstep, t_rad, t_env, t_cen = [
        track[ar].values.copy() for ar in [
            'lat', 'lon', 'time_step', 'radius_max_wind',
            'environmental_pressure', 'central_pressure'
        ]
    ]

    ncentroids = centroids.shape[0]
    npositions = t_lat.shape[0]
    windfields = np.zeros((npositions, ncentroids, 2))

    if t_lon.size < 2:
        return windfields

    # never use longitudes at -180 degrees or below
    t_lon[t_lon <= -180] += 360

    # only use longitudes above 180, if 180 degree border is crossed
    if t_lon.min() > 180:
        t_lon -= 360

    # restrict to centroids in rectangular bounding box around track
    track_centr_msk = _close_centroids(t_lat, t_lon, centroids)
    track_centr_idx = track_centr_msk.nonzero()[0]
    track_centr = centroids[track_centr_msk]

    if track_centr.shape[0] == 0:
        return windfields

    # compute distances and vectors to all centroids
    d_centr, v_centr = [
        ar[0] for ar in dist_approx(t_lat[None],
                                    t_lon[None],
                                    track_centr[None, :, 0],
                                    track_centr[None, :, 1],
                                    log=True,
                                    method="geosphere")
    ]

    # exclude centroids that are too far from or too close to the eye
    close_centr = (d_centr < CENTR_NODE_MAX_DIST_KM) & (d_centr > 1e-2)
    if not np.any(close_centr):
        return windfields
    v_centr_normed = np.zeros_like(v_centr)
    v_centr_normed[close_centr] = v_centr[close_centr] / d_centr[close_centr,
                                                                 None]

    # make sure that central pressure never exceeds environmental pressure
    pres_exceed_msk = (t_cen > t_env)
    t_cen[pres_exceed_msk] = t_env[pres_exceed_msk]

    # extrapolate radius of max wind from pressure if not given
    t_rad[:] = estimate_rmw(t_rad, t_cen) * NM_TO_KM

    # translational speed of track at every node
    v_trans = _vtrans(t_lat, t_lon, t_tstep)
    v_trans_norm = v_trans[0]

    # adjust pressure at previous track point
    prev_pres = t_cen[:-1].copy()
    msk = (prev_pres < 850)
    prev_pres[msk] = t_cen[1:][msk]

    # compute b-value
    if model == 0:
        hol_b = _bs_hol08(v_trans_norm[1:], t_env[1:], t_cen[1:], prev_pres,
                          t_lat[1:], t_tstep[1:])
    else:
        raise NotImplementedError

    # derive angular velocity
    v_ang_norm = _stat_holland(d_centr[1:], t_rad[1:], hol_b, t_env[1:],
                               t_cen[1:], t_lat[1:], close_centr[1:])
    hemisphere = 'N'
    if np.count_nonzero(t_lat < 0) > np.count_nonzero(t_lat > 0):
        hemisphere = 'S'
    v_ang_rotate = [1.0, -1.0] if hemisphere == 'N' else [-1.0, 1.0]
    v_ang_dir = np.array(v_ang_rotate)[..., :] * v_centr_normed[1:, :, ::-1]
    v_ang = np.zeros_like(v_ang_dir)
    v_ang[close_centr[1:]] = v_ang_norm[close_centr[1:], None] \
                             * v_ang_dir[close_centr[1:]]

    # Influence of translational speed decreases with distance from eye.
    # The "absorbing factor" is according to the following paper (see Fig. 7):
    #
    #   Mouton, F., & Nordbeck, O. (1999). Cyclone Database Manager. A tool
    #   for converting point data from cyclone observations into tracks and
    #   wind speed profiles in a GIS. UNED/GRID-Geneva.
    #   https://unepgrid.ch/en/resource/19B7D302
    #
    t_rad_bc = np.broadcast_arrays(t_rad[:, None], d_centr)[0]
    v_trans_corr = np.zeros_like(d_centr)
    v_trans_corr[close_centr] = np.fmin(
        1, t_rad_bc[close_centr] / d_centr[close_centr])

    # add angular and corrected translational velocity vectors
    v_full = v_trans[1][1:, None, :] * v_trans_corr[1:, :, None] + v_ang
    v_full[np.isnan(v_full)] = 0

    windfields[1:, track_centr_idx, :] = v_full
    return windfields