def volcoords_from_polar(sitecoords, elevs, azimuths, ranges, proj=None): """Create Cartesian coordinates for regular polar volumes Parameters ---------- sitecoords : sequence of three floats indicating the radar position (longitude in decimal degrees, latitude in decimal degrees, height a.s.l. in meters) elevs : sequence of elevation angles azimuths : sequence of azimuth angles ranges : sequence of ranges proj : osr spatial reference object GDAL OSR Spatial Reference Object describing projection Returns ------- output : :class:`numpy:numpy.ndarray` (num volume bins, 3) Examples -------- See :ref:`/notebooks/workflow/recipe2.ipynb`. """ # make sure that elevs is an array elevs = np.array([elevs]).ravel() # create polar grid el, az, r = util.meshgrid_n(elevs, azimuths, ranges) # get projected coordinates coords = georef.spherical_to_proj(r, az, el, sitecoords, proj=proj) coords = coords.reshape(-1, 3) return coords
def plot_ppi_crosshair(site, ranges, angles=None, proj=None, elev=0., ax=None, **kwargs): """Plots a Crosshair for a Plan Position Indicator (PPI). Parameters ---------- site : tuple Tuple of coordinates of the radar site. If `proj` is not used, this simply becomes the offset for the origin of the coordinate system. If `proj` is used, values must be given as (longitude, latitude, altitude) tuple of geographical coordinates. ranges : list List of ranges, for which range circles should be drawn. If ``proj`` is None arbitrary units may be used (such that they fit with the underlying PPI plot. Otherwise the ranges must be given in meters. angles : list List of angles (in degrees) for which straight lines should be drawn. These lines will be drawn starting from the center and until the largest range. proj : osr spatial reference object GDAL OSR Spatial Reference Object describing projection The function will calculate lines and circles according to georeferenced coordinates taking beam propagation, earth's curvature and scale effects due to projection into account. Depending on the projection, crosshair lines might not be straight and range circles might appear elliptical (also check if the aspect of the axes might not also be responsible for this). elev : float or array of same shape as az Elevation angle of the scan or individual azimuths. May improve georeferencing coordinates for larger elevation angles. ax : :class:`matplotlib:matplotlib.axes.Axes` If given, the crosshair will be plotted into this axes object. If None matplotlib's current axes (function gca()) concept will be used to determine the axes. Keyword Arguments ----------------- line : dict dictionary, which will be passed to the crosshair line objects using the standard keyword inheritance mechanism. If not given defaults will be used. circle : dict dictionary, which will be passed to the range circle line objects using the standard keyword inheritance mechanism. If not given defaults will be used. See also -------- :func:`~wradlib.vis.plot_ppi` - plotting a PPI in cartesian coordinates Returns ------- ax : :class:`matplotlib:matplotlib.axes.Axes` The axes object into which the PPI was plotted Examples -------- See :ref:`/notebooks/visualisation/wradlib_plot_ppi_example.ipynb`. """ # check coordinate tuple if site and len(site) < 3: raise ValueError("WRADLIB: `site` need to be a tuple of coordinates " "(longitude, latitude, altitude).") # if we didn't get an axes object, find the current one if ax is None: ax = pl.gca() if angles is None: angles = [0, 90, 180, 270] # set default line keywords linekw = dict(color='gray', linestyle='dashed') # update with user settings linekw.update(kwargs.get('line', {})) # set default circle keywords circkw = dict(edgecolor='gray', linestyle='dashed', facecolor='none') # update with user settings circkw.update(kwargs.get('circle', {})) # determine coordinates for 'straight' lines if proj: # projected # reproject the site coordinates psite = georef.reproject(*site, projection_target=proj) # these lines might not be straigt so we approximate them with 10 # segments. Produce polar coordinates rr, az = np.meshgrid(np.linspace(0, ranges[-1], 10), angles) # convert from spherical to projection coords = georef.spherical_to_proj(rr, az, elev, site, proj=proj) nsewx = coords[..., 0] nsewy = coords[..., 1] else: # no projection psite = site rr, az = np.meshgrid(np.linspace(0, ranges[-1], 2), angles) # use simple trigonometry to calculate coordinates nsewx, nsewy = (psite[0] + rr * np.cos(np.radians(90 - az)), psite[1] + rr * np.sin(np.radians(90 - az))) # mark the site, just in case nothing else would be drawn ax.plot(*psite[:2], marker='+', **linekw) # draw the lines for i in range(len(angles)): ax.add_line(lines.Line2D(nsewx[i, :], nsewy[i, :], **linekw)) # draw the range circles if proj: # produce an approximation of the circle x, y = np.meshgrid(ranges, np.arange(360)) poly = georef.spherical_to_proj(ranges, np.arange(360), elev, site, proj=proj)[..., :2] poly = np.swapaxes(poly, 0, 1) for p in poly: ax.add_patch(patches.Polygon(p, **circkw)) else: # in the unprojected case, we may use 'true' circles. for r in ranges: ax.add_patch(patches.Circle(psite, r, **circkw)) # there should be not much wrong, setting the axes aspect to equal # by default ax.set_aspect('equal') # return the axes object for later use return ax
def test_spherical_to_proj(self): coords = georef.spherical_to_proj(self.r, self.az, self.th, self.csite) self.assertTrue(np.allclose(coords[..., 0], self.result_n[0])) self.assertTrue(np.allclose(coords[..., 1], self.result_n[1])) self.assertTrue(np.allclose(coords[..., 2], self.result_n[2]))
def volcoords_from_polar_irregular(sitecoords, elevs, azimuths, ranges, proj=None): """Create Cartesian coordinates for polar volumes with irregular \ sweep specifications Parameters ---------- sitecoords : sequence of three floats indicating the radar position (longitude in decimal degrees, latitude in decimal degrees, height a.s.l. in meters) elevs : sequence of elevation angles azimuths : sequence of azimuth angles ranges : sequence of ranges proj : object GDAL OSR Spatial Reference Object describing projection Returns ------- output : :class:`numpy:numpy.ndarray` (num volume bins, 3) """ # check structure: Are azimuth angles and range bins the same for each # elevation angle? oneaz4all = True onerange4all = True # check elevs array, first: must be one-dimensional try: elevs = np.array(elevs) except Exception: print("Could not create an array from argument <elevs>.") print("The following exception was raised:") raise assert (elevs.ndim == 1) and (elevs.dtype != np.dtype("object")), \ "Argument <elevs> in wradlib.volcoords_from_polar must be a 1-D array." # now: is there one azimuths array for all elevation angles # or one for each? try: azimuths = np.array(azimuths) except Exception: print("Could not create an array from argument <azimuths>.") print("The following exception was raised:") raise if len(azimuths) == len(elevs): # are the items of <azimuths> arrays themselves? isseq = [util.issequence(elem) for elem in azimuths] assert not ((False in isseq) and (True in isseq)), \ "Argument <azimuths> contains both iterable " \ "and non-iterable items." if True in isseq: # we expect one azimuth array for each elevation angle oneaz4all = False # now: is there one ranges array for all elevation angles or one for each? try: ranges = np.array(ranges) except Exception: print("Could not create an array from argument <ranges>.") print("The following exception was raised:") raise if len(ranges) == len(elevs): # are the items of <azimuths> arrays themselves? isseq = [util.issequence(elem) for elem in ranges] assert not ((False in isseq) and (True in isseq)), \ "Argument <azimuths> contains both iterable " \ "and non-iterable items." if True in isseq: # we expect one azimuth array for each elevation angle onerange4all = False if oneaz4all and onerange4all: # this is the simple way return volcoords_from_polar(sitecoords, elevs, azimuths, ranges, proj) # No simply way, so we need to construct the coordinates arrays for # each elevation angle # but first adapt input arrays to this task if onerange4all: ranges = np.array([ranges for i in range(len(elevs))]) if oneaz4all: azimuths = np.array([azimuths for i in range(len(elevs))]) # and second create the corresponding polar volume grid el = np.array([]) az = np.array([]) r = np.array([]) for i, elev in enumerate(elevs): az_tmp, r_tmp = np.meshgrid(azimuths[i], ranges[i]) el = np.append(el, np.repeat(elev, len(azimuths[i]) * len(ranges[i]))) az = np.append(az, az_tmp.ravel()) r = np.append(r, r_tmp.ravel()) # get projected coordinates coords = georef.spherical_to_proj(r, az, el, sitecoords, proj=proj) coords = coords.reshape(-1, 3) return coords