def test_bin_altitude(self): altitude = georef.bin_altitude(np.arange(10., 101., 10.) * 1000., 2., 0, 6370040.) altref = np.array([354.87448647, 721.50702113, 1099.8960815, 1490.04009656, 1891.93744678, 2305.58646416, 2730.98543223, 3168.13258613, 3617.02611263, 4077.66415017]) np.testing.assert_allclose(altref, altitude)
def test_bin_altitude(self): altitude = georef.bin_altitude(np.arange(10., 101., 10.) * 1000., 2., 0, 6370040.) altref = np.array([354.87448647, 721.50702113, 1099.8960815, 1490.04009656, 1891.93744678, 2305.58646416, 2730.98543223, 3168.13258613, 3617.02611263, 4077.66415017]) np.testing.assert_allclose(altref, altitude)
def test_site_distance(self): altitude = georef.bin_altitude(np.arange(10., 101., 10.) * 1000., 2., 0, 6370040.) distance = georef.site_distance(np.arange(10., 101., 10.) * 1000., 2., altitude, 6370040.) distref = np.array([9993.49302358, 19986.13717891, 29977.90491409, 39968.76869178, 49958.70098959, 59947.6743006, 69935.66113377, 79922.63401441, 89908.5654846, 99893.4281037]) np.testing.assert_allclose(distref, distance)
def test_site_distance(self): altitude = georef.bin_altitude(np.arange(10., 101., 10.) * 1000., 2., 0, 6370040.) distance = georef.site_distance(np.arange(10., 101., 10.) * 1000., 2., altitude, 6370040.) distref = np.array([9993.49302358, 19986.13717891, 29977.90491409, 39968.76869178, 49958.70098959, 59947.6743006, 69935.66113377, 79922.63401441, 89908.5654846, 99893.4281037]) np.testing.assert_allclose(distref, distance)
def blindspots(center, gridcoords, minelev, maxelev, maxrange): """Masks blind regions of the radar, marked on a 3-D grid The radar is blind below the radar, above the radar and beyond the range. The function returns three boolean arrays which indicate whether (1) the grid node is below the radar, (2) the grid node is above the radar, (3) the grid node is beyond the maximum range. Parameters --------- center : tuple radar location gridcoords : :class:`numpy:numpy.ndarray` array of 3-D coordinates with shape (num voxels, 3) minelev : float The minimum elevation angle of the volume (degree) maxelev : float The maximum elevation angle of the volume (degree) maxrange : float maximum range (same unit as gridcoords) Returns ------- output : tuple of three Boolean arrays each of length (num grid points) """ # distances of 3-D grid nodes from radar site (center) dist_from_rad = np.sqrt(((gridcoords - center) ** 2).sum(axis=-1)) # below the radar below = gridcoords[:, 2] < (georef.bin_altitude(dist_from_rad, minelev, 0, re=6371000) + center[:, 2]) # above the radar above = gridcoords[:, 2] > (georef.bin_altitude(dist_from_rad, maxelev, 0, re=6371000) + center[:, 2]) # out of range out_of_range = dist_from_rad > maxrange return below, above, out_of_range
def maximum_intensity_projection(data, r=None, az=None, angle=None, elev=None, autoext=True): """Computes the maximum intensity projection along an arbitrary cut \ through the ppi from polar data. Parameters ---------- data : :class:`numpy:numpy.ndarray` Array containing polar data (azimuth, range) r : :class:`numpy:numpy.ndarray` Array containing range data az : array Array containing azimuth data angle : float angle of slice, Defaults to 0. Should be between 0 and 180. 0. means horizontal slice, 90. means vertical slice elev : float elevation angle of scan, Defaults to 0. autoext : True | False This routine uses numpy.digitize to bin the data. As this function needs bounds, we create one set of coordinates more than would usually be provided by `r` and `az`. Returns ------- xs : :class:`numpy:numpy.ndarray` meshgrid x array ys : :class:`numpy:numpy.ndarray` meshgrid y array mip : :class:`numpy:numpy.ndarray` Array containing the maximum intensity projection (range, range*2) """ from wradlib.georef import bin_altitude as bin_altitude # this may seem odd at first, but d1 and d2 are also used in several # plotting functions and thus it may be easier to compare the functions d1 = r d2 = az # providing 'reasonable defaults', based on the data's shape if d1 is None: d1 = np.arange(data.shape[1], dtype=np.float) if d2 is None: d2 = np.arange(data.shape[0], dtype=np.float) if angle is None: angle = 0.0 if elev is None: elev = 0.0 if autoext: # the ranges need to go 'one bin further', assuming some regularity # we extend by the distance between the preceding bins. x = np.append(d1, d1[-1] + (d1[-1] - d1[-2])) # the angular dimension is supposed to be cyclic, so we just add the # first element y = np.append(d2, d2[0]) else: # no autoext basically is only useful, if the user supplied the correct # dimensions himself. x = d1 y = d2 # roll data array to specified azimuth, assuming equidistant azimuth angles ind = (d2 >= angle).nonzero()[0][0] data = np.roll(data, ind, axis=0) # build cartesian range array, add delta to last element to compensate for # open bound (np.digitize) dc = np.linspace(-np.max(d1), np.max(d1) + 0.0001, num=d1.shape[0] * 2 + 1) # get height values from polar data and build cartesian height array # add delta to last element to compensate for open bound (np.digitize) hp = np.zeros((y.shape[0], x.shape[0])) hc = bin_altitude(x, elev, 0, re=6370040.) hp[:] = hc hc[-1] += 0.0001 # create meshgrid for polar data xx, yy = np.meshgrid(x, y) # create meshgrid for cartesian slices xs, ys = np.meshgrid(dc, hc) # xs, ys = np.meshgrid(dc,x) # convert polar coordinates to cartesian xxx = xx * np.cos(np.radians(90. - yy)) # yyy = xx * np.sin(np.radians(90.-yy)) # digitize coordinates according to cartesian range array range_dig1 = np.digitize(xxx.ravel(), dc) range_dig1.shape = xxx.shape # digitize heights according polar height array height_dig1 = np.digitize(hp.ravel(), hc) # reshape accordingly height_dig1.shape = hp.shape # what am I doing here?! range_dig1 = range_dig1[0:-1, 0:-1] height_dig1 = height_dig1[0:-1, 0:-1] # create height and range masks height_mask = [(height_dig1 == i).ravel().nonzero()[0] for i in range(1, len(hc))] range_mask = [(range_dig1 == i).ravel().nonzero()[0] for i in range(1, len(dc))] # create mip output array, set outval to inf mip = np.zeros((d1.shape[0], 2 * d1.shape[0])) mip[:] = np.inf # fill mip array, # in some cases there are no values found in the specified range and height # then we fill in nans and interpolate afterwards for i in range(0, len(range_mask)): mask1 = range_mask[i] found = False for j in range(0, len(height_mask)): mask2 = np.intersect1d(mask1, height_mask[j]) # this is to catch the ValueError from the max() routine when # calculating on empty array try: mip[j, i] = data.ravel()[mask2].max() if not found: found = True except ValueError: if found: mip[j, i] = np.nan # interpolate nans inside image, do not touch outvals good = ~np.isnan(mip) xp = good.ravel().nonzero()[0] fp = mip[~np.isnan(mip)] x = np.isnan(mip).ravel().nonzero()[0] mip[np.isnan(mip)] = np.interp(x, xp, fp) # reset outval to nan mip[mip == np.inf] = np.nan return xs, ys, mip
def maximum_intensity_projection(data, r=None, az=None, angle=None, elev=None, autoext=True): """Computes the maximum intensity projection along an arbitrary cut \ through the ppi from polar data. Parameters ---------- data : :class:`numpy:numpy.ndarray` Array containing polar data (azimuth, range) r : :class:`numpy:numpy.ndarray` Array containing range data az : array Array containing azimuth data angle : float angle of slice, Defaults to 0. Should be between 0 and 180. 0. means horizontal slice, 90. means vertical slice elev : float elevation angle of scan, Defaults to 0. autoext : True | False This routine uses numpy.digitize to bin the data. As this function needs bounds, we create one set of coordinates more than would usually be provided by `r` and `az`. Returns ------- xs : :class:`numpy:numpy.ndarray` meshgrid x array ys : :class:`numpy:numpy.ndarray` meshgrid y array mip : :class:`numpy:numpy.ndarray` Array containing the maximum intensity projection (range, range*2) """ from wradlib.georef import bin_altitude as bin_altitude # this may seem odd at first, but d1 and d2 are also used in several # plotting functions and thus it may be easier to compare the functions d1 = r d2 = az # providing 'reasonable defaults', based on the data's shape if d1 is None: d1 = np.arange(data.shape[1], dtype=np.float) if d2 is None: d2 = np.arange(data.shape[0], dtype=np.float) if angle is None: angle = 0.0 if elev is None: elev = 0.0 if autoext: # the ranges need to go 'one bin further', assuming some regularity # we extend by the distance between the preceding bins. x = np.append(d1, d1[-1] + (d1[-1] - d1[-2])) # the angular dimension is supposed to be cyclic, so we just add the # first element y = np.append(d2, d2[0]) else: # no autoext basically is only useful, if the user supplied the correct # dimensions himself. x = d1 y = d2 # roll data array to specified azimuth, assuming equidistant azimuth angles ind = (d2 >= angle).nonzero()[0][0] data = np.roll(data, ind, axis=0) # build cartesian range array, add delta to last element to compensate for # open bound (np.digitize) dc = np.linspace(-np.max(d1), np.max(d1) + 0.0001, num=d1.shape[0] * 2 + 1) # get height values from polar data and build cartesian height array # add delta to last element to compensate for open bound (np.digitize) hp = np.zeros((y.shape[0], x.shape[0])) hc = bin_altitude(x, elev, 0, re=6370040.) hp[:] = hc hc[-1] += 0.0001 # create meshgrid for polar data xx, yy = np.meshgrid(x, y) # create meshgrid for cartesian slices xs, ys = np.meshgrid(dc, hc) # xs, ys = np.meshgrid(dc,x) # convert polar coordinates to cartesian xxx = xx * np.cos(np.radians(90. - yy)) # yyy = xx * np.sin(np.radians(90.-yy)) # digitize coordinates according to cartesian range array range_dig1 = np.digitize(xxx.ravel(), dc) range_dig1.shape = xxx.shape # digitize heights according polar height array height_dig1 = np.digitize(hp.ravel(), hc) # reshape accordingly height_dig1.shape = hp.shape # what am I doing here?! range_dig1 = range_dig1[0:-1, 0:-1] height_dig1 = height_dig1[0:-1, 0:-1] # create height and range masks height_mask = [(height_dig1 == i).ravel().nonzero()[0] for i in range(1, len(hc))] range_mask = [(range_dig1 == i).ravel().nonzero()[0] for i in range(1, len(dc))] # create mip output array, set outval to inf mip = np.zeros((d1.shape[0], 2 * d1.shape[0])) mip[:] = np.inf # fill mip array, # in some cases there are no values found in the specified range and height # then we fill in nans and interpolate afterwards for i in range(0, len(range_mask)): mask1 = range_mask[i] found = False for j in range(0, len(height_mask)): mask2 = np.intersect1d(mask1, height_mask[j]) # this is to catch the ValueError from the max() routine when # calculating on empty array try: mip[j, i] = data.ravel()[mask2].max() if not found: found = True except ValueError: if found: mip[j, i] = np.nan # interpolate nans inside image, do not touch outvals good = ~np.isnan(mip) xp = good.ravel().nonzero()[0] fp = mip[~np.isnan(mip)] x = np.isnan(mip).ravel().nonzero()[0] mip[np.isnan(mip)] = np.interp(x, xp, fp) # reset outval to nan mip[mip == np.inf] = np.nan return xs, ys, mip