def get_radolan_coords(lon, lat, trig=False): """ Calculates x,y coordinates of radolan grid from lon, lat Parameters ---------- lon : float, :class:`numpy:numpy.ndarray` of floats longitude lat : float, :class:`numpy:numpy.ndarray` of floats latitude trig : boolean if True, uses trigonometric formulas for calculation, otherwise osr transformations if False, uses osr spatial reference system to transform between projections `trig` is recommended to be False, however, the two ways of computation are expected to be equivalent. """ if trig: # calculation of x_0 and y_0 coordinates of radolan grid # as described in the format description phi_0 = np.radians(60) phi_m = np.radians(lat) lam_0 = 10 lam_m = lon lam = np.radians(lam_m - lam_0) er = 6370.040 m_phi = (1 + np.sin(phi_0)) / (1 + np.sin(phi_m)) x = er * m_phi * np.cos(phi_m) * np.sin(lam) y = -er * m_phi * np.cos(phi_m) * np.cos(lam) else: # create radolan projection osr object proj_stereo = projection.create_osr("dwd-radolan") # create wgs84 projection osr object proj_wgs = projection.get_default_projection() x, y = projection.reproject(lon, lat, projection_source=proj_wgs, projection_target=proj_stereo) return x, y
def get_radolan_grid(nrows=None, ncols=None, trig=False, wgs84=False): """Calculates x/y coordinates of radolan grid of the German Weather Service Returns the x,y coordinates of the radolan grid positions (lower left corner of every pixel). The radolan grid is a polarstereographic projection, the projection information was taken from RADOLAN-RADVOR-OP Kompositformat_2.2.2 :cite:`DWD2009` .. table:: Coordinates for 900km x 900km grid +------------+-----------+------------+-----------+-----------+ | Coordinate | lon | lat | x | y | +============+===========+============+===========+===========+ | LowerLeft | 3.5889E | 46.9526N | -523.4622 | -4658.645 | +------------+-----------+------------+-----------+-----------+ | LowerRight | 14.6209E | 47.0705N | 376.5378 | -4658.645 | +------------+-----------+------------+-----------+-----------+ | UpperRight | 15.7208E | 54.7405N | 376.5378 | -3758.645 | +------------+-----------+------------+-----------+-----------+ | UpperLeft | 2.0715E | 54.5877N | -523.4622 | -3758.645 | +------------+-----------+------------+-----------+-----------+ .. table:: Coordinates for 1100km x 900km grid +------------+-----------+------------+-----------+-----------+ | Coordinate | lon | lat | x | y | +============+===========+============+===========+===========+ | LowerLeft | 4.6759E | 46.1929N | -443.4622 | -4758.645 | +------------+-----------+------------+-----------+-----------+ | LowerRight | 15.4801E | 46.1827N | 456.5378 | -4758.645 | +------------+-----------+------------+-----------+-----------+ | UpperRight | 17.1128E | 55.5342N | 456.5378 | -3658.645 | +------------+-----------+------------+-----------+-----------+ | UpperLeft | 3.0889E | 55.5482N | -433.4622 | -3658.645 | +------------+-----------+------------+-----------+-----------+ .. table:: Coordinates for 1500km x 1400km grid +------------+-----------+------------+-----------+-----------+ | Coordinate | lon | lat | x | y | +============+===========+============+===========+===========+ | LowerLeft | 2.3419E | 43.9336N | -673.4622 | -5008.645 | +------------+-----------+------------+-----------+-----------+ Parameters ---------- nrows : int number of rows (460, 900 by default, 1100, 1500) ncols : int number of columns (460, 900 by default, 1400) trig : boolean if True, uses trigonometric formulas for calculation if False, uses osr spatial reference system to transform between projections `trig` is recommended to be False, however, the two ways of computation are expected to be equivalent. wgs84 : boolean if True, output coordinates are in wgs84 lonlat format (default: False) Returns ------- radolan_grid : :class:`numpy:numpy.ndarray` Array of shape (rows, cols, 2) xy- or lonlat-grid. Examples -------- >>> # using osr spatial reference transformation >>> import wradlib.georef as georef # noqa >>> radolan_grid = georef.get_radolan_grid() >>> print("{0}, ({1:.4f}, {2:.4f})".format(radolan_grid.shape, *radolan_grid[0,0,:])) # noqa (900, 900, 2), (-523.4622, -4658.6447) >>> # using pure trigonometric transformations >>> import wradlib.georef as georef >>> radolan_grid = georef.get_radolan_grid(trig=True) >>> print("{0}, ({1:.4f}, {2:.4f})".format(radolan_grid.shape, *radolan_grid[0,0,:])) # noqa (900, 900, 2), (-523.4622, -4658.6447) >>> # using osr spatial reference transformation >>> import wradlib.georef as georef >>> radolan_grid = georef.get_radolan_grid(1500, 1400) >>> print("{0}, ({1:.4f}, {2:.4f})".format(radolan_grid.shape, *radolan_grid[0,0,:])) # noqa (1500, 1400, 2), (-673.4622, -5008.6447) >>> # using osr spatial reference transformation >>> import wradlib.georef as georef >>> radolan_grid = georef.get_radolan_grid(900, 900, wgs84=True) >>> print("{0}, ({1:.4f}, {2:.4f})".format(radolan_grid.shape, *radolan_grid[0,0,:])) # noqa (900, 900, 2), (3.5889, 46.9526) See :ref:`/notebooks/radolan/radolan_grid.ipynb#\ Polar-Stereographic-Projection`. Raises ------ TypeError, ValueError """ # setup default parameters in dicts tiny = {"j_0": 450, "i_0": 450, "res": 2} small = {"j_0": 460, "i_0": 460, "res": 2} normal = {"j_0": 450, "i_0": 450, "res": 1} normal_wx = {"j_0": 370, "i_0": 550, "res": 1} extended = {"j_0": 600, "i_0": 800, "res": 1} griddefs = { (450, 450): tiny, (460, 460): small, (900, 900): normal, (1100, 900): normal_wx, (1500, 1400): extended, } # type and value checking if nrows and ncols: if not (isinstance(nrows, int) and isinstance(ncols, int)): raise TypeError( "wradlib.georef: Parameter *nrows* " "and *ncols* not integer" ) if (nrows, ncols) not in griddefs.keys(): raise ValueError( "wradlib.georef: Parameter *nrows* " "and *ncols* mismatch." ) else: # fallback for call without parameters nrows = 900 ncols = 900 # tiny, small, normal or extended grid check # reference point changes according to radolan composit format j_0 = griddefs[(nrows, ncols)]["j_0"] i_0 = griddefs[(nrows, ncols)]["i_0"] res = griddefs[(nrows, ncols)]["res"] x_0, y_0 = get_radolan_coords(9.0, 51.0, trig=trig) x_arr = np.arange(x_0 - j_0, x_0 - j_0 + ncols * res, res) y_arr = np.arange(y_0 - i_0, y_0 - i_0 + nrows * res, res) x, y = np.meshgrid(x_arr, y_arr) radolan_grid = np.dstack((x, y)) if wgs84: if trig: # inverse projection lon0 = 10.0 # central meridian of projection lat0 = 60.0 # standard parallel of projection sinlat0 = np.sin(np.radians(lat0)) fac = (6370.040 ** 2.0) * ((1.0 + sinlat0) ** 2.0) lon = np.degrees(np.arctan((-x / y))) + lon0 lat = np.degrees( np.arcsin((fac - (x ** 2.0 + y ** 2.0)) / (fac + (x ** 2.0 + y ** 2.0))) ) radolan_grid = np.dstack((lon, lat)) else: # create radolan projection osr object proj_stereo = projection.create_osr("dwd-radolan") # create wgs84 projection osr object proj_wgs = projection.get_default_projection() radolan_grid = projection.reproject( radolan_grid, projection_source=proj_stereo, projection_target=proj_wgs ) return radolan_grid
def spherical_to_centroids(r, phi, theta, sitecoords, proj=None): """ Generate 3-D centroids of the radar bins from the sperical coordinates (r, phi, theta). Both azimuth and range arrays are assumed to be equidistant and to contain only unique values. The ranges are assumed to define the exterior boundaries of the range bins (thus they must be positive). The angles are assumed to describe the pointing direction fo the main beam lobe. For further information refer to the documentation of :meth:`~wradlib.georef.polar2lonlat`. Parameters ---------- r : :class:`numpy:numpy.ndarray` Array of ranges [m]; r defines the exterior boundaries of the range bins! (not the centroids). Thus, values must be positive! phi : :class:`numpy:numpy.ndarray` Array of azimuth angles containing values between 0° and 360°. The angles are assumed to describe the pointing direction fo the main beam lobe! The first angle can start at any values, but make sure the array is sorted continuously positively clockwise and the angles are equidistant. An angle if 0 degree is pointing north. theta : float Elevation angle of scan sitecoords : a sequence of three floats the lon/lat/alt coordinates of the radar location proj : osr object Destination Projection Returns ------- output : centroids :class:`numpy:numpy.ndarray` A 3-d array of bin centroids with shape(num_rays, num_bins, 3). The last dimension carries the xyz-coordinates either in `aeqd` or given proj. proj : osr object only returned if proj is None Note ---- Azimuth angles of 360 deg are internally converted to 0 deg. """ # make sure the range and azimuth angles have the right properties r, phi = _check_polar_coords(r, phi) r = r - 0.5 * _get_range_resolution(r) # generate a polar grid and convert to lat/lon r, phi = np.meshgrid(r, phi) coords, rad = spherical_to_xyz(r, phi, theta, sitecoords, squeeze=True) if proj is None: return coords, rad else: return projection.reproject(coords, projection_source=rad, projection_target=proj)
def spherical_to_polyvert(r, phi, theta, sitecoords, proj=None): """ Generate 3-D polygon vertices directly from spherical coordinates (r, phi, theta). This is an alternative to :func:`~wradlib.georef.centroid_to_polyvert` which does not use centroids, but generates the polygon vertices by simply connecting the corners of the radar bins. Both azimuth and range arrays are assumed to be equidistant and to contain only unique values. For further information refer to the documentation of :func:`~wradlib.georef.spherical_to_xyz`. Parameters ---------- r : :class:`numpy:numpy.ndarray` Array of ranges [m]; r defines the exterior boundaries of the range bins! (not the centroids). Thus, values must be positive! phi : :class:`numpy:numpy.ndarray` Array of azimuth angles containing values between 0° and 360°. The angles are assumed to describe the pointing direction fo the main beam lobe! The first angle can start at any values, but make sure the array is sorted continuously positively clockwise and the angles are equidistant. An angle if 0 degree is pointing north. theta : float Elevation angle of scan sitecoords : a sequence of three floats the lon/lat/alt coordinates of the radar location proj : osr object Destination Projection Returns ------- output : :class:`numpy:numpy.ndarray` A 3-d array of polygon vertices with shape(num_vertices, num_vertex_nodes, 2). The last dimension carries the xyz-coordinates either in `aeqd` or given proj. proj : osr object only returned if proj is None Examples -------- >>> import wradlib.georef as georef # noqa >>> import numpy as np >>> from matplotlib import collections >>> import matplotlib.pyplot as pl >>> #pl.interactive(True) >>> # define the polar coordinates and the site coordinates in lat/lon >>> r = np.array([50., 100., 150., 200.]) * 1000 >>> # _check_polar_coords fails in next line >>> # az = np.array([0., 45., 90., 135., 180., 225., 270., 315., 360.]) >>> az = np.array([0., 45., 90., 135., 180., 225., 270., 315.]) >>> el = 1.0 >>> sitecoords = (9.0, 48.0, 0) >>> polygons, proj = georef.spherical_to_polyvert(r, az, el, sitecoords) >>> # plot the resulting mesh >>> fig = pl.figure() >>> ax = fig.add_subplot(111) >>> #polycoll = mpl.collections.PolyCollection(vertices,closed=True, facecolors=None) # noqa >>> polycoll = collections.PolyCollection(polygons[...,:2], closed=True, facecolors='None') # noqa >>> ret = ax.add_collection(polycoll, autolim=True) >>> pl.autoscale() >>> pl.show() """ # prepare the range and azimuth array so they describe the boundaries of # a bin, not the centroid r, phi = _check_polar_coords(r, phi) r = np.insert(r, 0, r[0] - _get_range_resolution(r)) phi = phi - 0.5 * _get_azimuth_resolution(phi) phi = np.append(phi, phi[0]) phi = np.where(phi < 0, phi + 360., phi) # generate a grid of polar coordinates of bin corners r, phi = np.meshgrid(r, phi) coords, rad = spherical_to_xyz(r, phi, theta, sitecoords, squeeze=True, strict_dims=True) if proj is not None: coords = projection.reproject(coords, projection_source=rad, projection_target=proj) llc = coords[:-1, :-1] ulc = coords[:-1, 1:] urc = coords[1:, 1:] lrc = coords[1:, :-1] vertices = np.stack((llc, ulc, urc, lrc, llc), axis=-2).reshape((-1, 5, 3)) if proj is None: return vertices, rad else: return vertices
def spherical_to_proj(r, phi, theta, sitecoords, proj=None, re=None, ke=4. / 3.): """Transforms spherical coordinates (r, phi, theta) to projected coordinates centered at sitecoords in given projection. It takes the shortening of the great circle distance with increasing elevation angle as well as the resulting increase in height into account. Parameters ---------- r : :class:`numpy:numpy.ndarray` Contains the radial distances. phi : :class:`numpy:numpy.ndarray` Contains the azimuthal angles. theta: :class:`numpy:numpy.ndarray` Contains the elevation angles. sitecoords : a sequence of three floats the lon / lat coordinates of the radar location and its altitude a.m.s.l. (in meters) if sitecoords is of length two, altitude is assumed to be zero proj : osr object Destination Spatial Reference System (Projection). Defaults to wgs84 (epsg 4326). re : float earth's radius [m] ke : float adjustment factor to account for the refractivity gradient that affects radar beam propagation. In principle this is wavelength- dependent. The default of 4/3 is a good approximation for most weather radar wavelengths. Returns ------- coords : :class:`numpy:numpy.ndarray` Array of shape (..., 3). Contains projected map coordinates. Examples -------- A few standard directions (North, South, North, East, South, West) with different distances (amounting to roughly 1°) from a site located at 48°N 9°E >>> r = np.array([0., 0., 111., 111., 111., 111.,])*1000 >>> az = np.array([0., 180., 0., 90., 180., 270.,]) >>> th = np.array([0., 0., 0., 0., 0., 0.5,]) >>> csite = (9.0, 48.0) >>> coords = spherical_to_proj(r, az, th, csite) >>> for coord in coords: ... print( '{0:7.4f}, {1:7.4f}, {2:7.4f}'.format(*coord)) ... 9.0000, 48.0000, 0.0000 9.0000, 48.0000, 0.0000 9.0000, 48.9981, 725.7160 10.4872, 47.9904, 725.7160 9.0000, 47.0017, 725.7160 7.5131, 47.9904, 1694.2234 Here, the coordinates of the east and west directions won't come to lie on the latitude of the site because the beam doesn't travel along the latitude circle but along a great circle. See :ref:`/notebooks/basics/wradlib_workflow.ipynb#\ Georeferencing-and-Projection`. """ if proj is None: proj = projection.get_default_projection() xyz, rad = spherical_to_xyz(r, phi, theta, sitecoords, re=re, ke=ke, squeeze=True) # reproject aeqd to destination projection coords = projection.reproject(xyz, projection_source=rad, projection_target=proj) return coords
def reproject_raster_dataset(src_ds, **kwargs): """Reproject/Resample given dataset according to keyword arguments Parameters ---------- src_ds : gdal.Dataset raster image with georeferencing (GeoTransform at least) Keyword Arguments ----------------- spacing : float float or tuple of two floats pixel spacing of destination dataset, same unit as pixel coordinates size : int tuple of two ints X/YRasterSize of destination dataset resample : GDALResampleAlg defaults to GRA_Bilinear GRA_NearestNeighbour = 0, GRA_Bilinear = 1, GRA_Cubic = 2, GRA_CubicSpline = 3, GRA_Lanczos = 4, GRA_Average = 5, GRA_Mode = 6, GRA_Max = 8, GRA_Min = 9, GRA_Med = 10, GRA_Q1 = 11, GRA_Q3 = 12 projection_target : osr object destination dataset projection, defaults to None align : bool or Point If False, there is no destination grid aligment. If True, aligns the destination grid to the next integer multiple of destination grid. If Point (tuple, list of upper-left x,y-coordinate), the destination grid is aligned to this point. Returns ------- dst_ds : gdal.Dataset reprojected/resampled raster dataset """ # checking kwargs spacing = kwargs.pop('spacing', None) size = kwargs.pop('size', None) resample = kwargs.pop('resample', gdal.GRA_Bilinear) src_srs = kwargs.pop('projection_source', None) dst_srs = kwargs.pop('projection_target', None) align = kwargs.pop('align', False) # Get the GeoTransform vector src_geo = src_ds.GetGeoTransform() x_size = src_ds.RasterXSize y_size = src_ds.RasterYSize # get extent ulx = src_geo[0] uly = src_geo[3] lrx = src_geo[0] + src_geo[1] * x_size lry = src_geo[3] + src_geo[5] * y_size extent = np.array([[[ulx, uly], [lrx, uly]], [[ulx, lry], [lrx, lry]]]) if dst_srs: src_srs = osr.SpatialReference() src_srs.ImportFromWkt(src_ds.GetProjection()) # Transformation extent = projection.reproject(extent, projection_source=src_srs, projection_target=dst_srs) # wkt needed src_srs = src_srs.ExportToWkt() dst_srs = dst_srs.ExportToWkt() (ulx, uly, urx, ury, llx, lly, lrx, lry) = tuple(list(extent.flatten().tolist())) # align grid to destination raster or UL-corner point if align: try: ulx, uly = align except TypeError: pass ulx = int(max(np.floor(ulx), np.floor(llx))) uly = int(min(np.ceil(uly), np.ceil(ury))) lrx = int(min(np.ceil(lrx), np.ceil(urx))) lry = int(max(np.floor(lry), np.floor(lly))) # calculate cols/rows or xspacing/yspacing if spacing: try: x_ps, y_ps = spacing except TypeError: x_ps = spacing y_ps = spacing cols = int(abs(lrx - ulx) / x_ps) rows = int(abs(uly - lry) / y_ps) elif size: cols, rows = size x_ps = x_size * src_geo[1] / cols y_ps = y_size * abs(src_geo[5]) / rows else: raise NameError("Whether keyword 'spacing' or 'size' must be given") # create destination in-memory raster mem_drv = gdal.GetDriverByName('MEM') # and set RasterSize according ro cols/rows dst_ds = mem_drv.Create('', cols, rows, 1, gdal.GDT_Float32) # Create the destination GeoTransform with changed x/y spacing dst_geo = (ulx, x_ps, src_geo[2], uly, src_geo[4], -y_ps) # apply GeoTransform to destination dataset dst_ds.SetGeoTransform(dst_geo) # apply Projection to destination dataset if dst_srs is not None: dst_ds.SetProjection(dst_srs) # nodata handling, need to initialize dst_ds with nodata src_band = src_ds.GetRasterBand(1) nodata = src_band.GetNoDataValue() dst_band = dst_ds.GetRasterBand(1) if nodata is not None: dst_band.SetNoDataValue(nodata) dst_band.WriteArray(np.ones((rows, cols)) * nodata) dst_band.FlushCache() # resample and reproject dataset gdal.ReprojectImage(src_ds, dst_ds, src_srs, dst_srs, resample) return dst_ds