def get_pixelsize_metres(self, xy=None): """ Compute the size (in metres) of the pixel at the specified xy position. :param xy: A tuple containing an (x, y) grid co-ordinates of a pixel. Defaults \ to the central pixel in the grid. :return: A tuple (x_size, y_size) gives the size of the pixel in metres """ if xy is None: xy = (self.shape[1] / 2, self.shape[0] / 2) (x, y) = xy spheroid, _ = setup_spheroid(self.crs.ExportToWkt()) (lon1, lat1) = self.transform * (x, y + 0.5) (lon2, lat2) = self.transform * (x + 1, y + 0.5) x_size, _, _ = vinc_dist(spheroid[1], spheroid[0], radians(lat1), radians(lon1), radians(lat2), radians(lon2)) (lon1, lat1) = self.transform * (x + 0.5, y) (lon2, lat2) = self.transform * (x + 0.5, y + 1) y_size, _, _ = vinc_dist(spheroid[1], spheroid[0], radians(lat1), radians(lon1), radians(lat2), radians(lon2)) return (x_size, y_size)
def calculate_cast_shadow(acquisition, dsm_group, satellite_solar_group, buffer_distance, out_group=None, compression=H5CompressionFilter.LZF, filter_opts=None, solar_source=True): """ This code is an interface to the fortran code cast_shadow_main.f90 written by Fuqin (and modified to work with F2py). The following was taken from the top of the Fotran program: "cast_shadow_main.f90": Creates a shadow mask for a standard Landsat scene the program was originally written by DLB Jupp in Oct. 2010 for a small sub_matrix and was modified by Fuqin Li in Oct. 2010 so that the program can be used for large landsat scene. Basically, a sub-matrix A is embedded in a larger DEM image and the borders must be large enough to find the shaded pixels. If we assume the solar azimuth and zenith angles change very little within the sub-matrix A, then the Landsat scene can be divided into several sub_matrix. For Australian region, with 0 .00025 degree resolution, the sub-marix A is set to 500x500 we also need to set extra DEM lines/columns to run the Landsat scene. This will change with elevation difference within the scene and solar zenith angle. For Australian region and Landsat scene with 0.00025 degree resolution, the maximum extra lines are set to 250 pixels/lines for each direction. This figure shold be sufficient for everywhere and anytime in Australia. Thus the DEM image will be larger than landsat image for 500 lines x 500 columns :param acquisition: An instance of an acquisition object. :param dsm_group: The root HDF5 `Group` that contains the Digital Surface Model data. The dataset pathnames are given by: * DatasetName.DSM_SMOOTHED The dataset must have the same dimensions as `acquisition` plus a margin of widths specified by margin. :param satellite_solar_group: The root HDF5 `Group` that contains the satellite and solar datasets specified by the pathnames given by: * DatasetName.SOLAR_ZENITH * DatasetName.SOLAR_AZIMUTH * DatasetName.SATELLITE_VIEW * DatasetName.SATELLITE_AZIMUTH :param buffer_distance: A number representing the desired distance (in the same units as the acquisition) in which to calculate the extra number of pixels required to buffer an image. Default is 8000. :param out_group: If set to None (default) then the results will be returned as an in-memory hdf5 file, i.e. the `core` driver. Otherwise, a writeable HDF5 `Group` object. The dataset names will be given by the format string detailed by: * DatasetName.CAST_SHADOW_FMT :param compression: The compression filter to use. Default is H5CompressionFilter.LZF :filter_opts: A dict of key value pairs available to the given configuration instance of H5CompressionFilter. For example H5CompressionFilter.LZF has the keywords *chunks* and *shuffle* available. Default is None, which will use the default settings for the chosen H5CompressionFilter instance. :param solar_source: A `bool` indicating whether or not the source for the line of sight comes from the sun (True; Default), or False indicating the satellite. :return: An opened `h5py.File` object, that is either in-memory using the `core` driver, or on disk. :warning: The Fortran code cannot be compiled with ``-O3`` as it produces incorrect results if it is. """ # Setup the geobox geobox = acquisition.gridded_geo_box() x_res, y_res = geobox.pixelsize x_origin, y_origin = geobox.origin # Are we in UTM or geographics? is_utm = not geobox.crs.IsGeographic() # Retrive the spheroid parameters # (used in calculating pixel size in metres per lat/lon) spheroid, _ = setup_spheroid(geobox.crs.ExportToWkt()) # Define Top, Bottom, Left, Right pixel buffer margins margins = pixel_buffer(acquisition, buffer_distance) if solar_source: zenith_name = DatasetName.SOLAR_ZENITH.value azimuth_name = DatasetName.SOLAR_AZIMUTH.value else: zenith_name = DatasetName.SATELLITE_VIEW.value azimuth_name = DatasetName.SATELLITE_AZIMUTH.value zenith_angle = satellite_solar_group[zenith_name][:] azimuth_angle = satellite_solar_group[azimuth_name][:] elevation = dsm_group[DatasetName.DSM_SMOOTHED.value][:] # block height and width of the window/submatrix used in the cast # shadow algorithm block_width = margins.left + margins.right block_height = margins.top + margins.bottom # Compute the cast shadow mask ierr, mask = cast_shadow_main(elevation, zenith_angle, azimuth_angle, x_res, y_res, spheroid, y_origin, x_origin, margins.left, margins.right, margins.top, margins.bottom, block_height, block_width, is_utm) if ierr: raise CastShadowError(ierr) source_dir = 'SUN' if solar_source else 'SATELLITE' # Initialise the output file if out_group is None: fid = h5py.File('cast-shadow-{}.h5'.format(source_dir), driver='core', backing_store=False) else: fid = out_group if GroupName.SHADOW_GROUP.value not in fid: fid.create_group(GroupName.SHADOW_GROUP.value) if filter_opts is None: filter_opts = {} else: filter_opts = filter_opts.copy() grp = fid[GroupName.SHADOW_GROUP.value] tile_size = satellite_solar_group[zenith_name].chunks filter_opts['chunks'] = tile_size kwargs = compression.config(**filter_opts).dataset_compression_kwargs() kwargs['dtype'] = 'bool' dname_fmt = DatasetName.CAST_SHADOW_FMT.value out_dset = grp.create_dataset(dname_fmt.format(source=source_dir), data=mask, **kwargs) # attach some attributes to the image datasets attrs = { 'crs_wkt': geobox.crs.ExportToWkt(), 'geotransform': geobox.transform.to_gdal() } desc = ("The cast shadow mask determined using the {} " "as the source direction.").format(source_dir) attrs['description'] = desc attrs['alias'] = 'cast-shadow-{}'.format(source_dir).lower() attach_image_attributes(out_dset, attrs) if out_group is None: return fid
def slope_aspect_arrays(acquisition, dsm_group, buffer_distance, out_group=None, compression=H5CompressionFilter.LZF, filter_opts=None): """ Calculates slope and aspect. :param acquisition: An instance of an acquisition object. :param dsm_group: The root HDF5 `Group` that contains the Digital Surface Model data. The dataset pathname is given by: * DatasetName.DSM_SMOOTHED The dataset must have the same dimensions as `acquisition` plus a margin of widths specified by margin. :param buffer_distance: A number representing the desired distance (in the same units as the acquisition) in which to calculate the extra number of pixels required to buffer an image. Default is 8000. :param out_group: If set to None (default) then the results will be returned as an in-memory hdf5 file, i.e. the `core` driver. Otherwise, a writeable HDF5 `Group` object. The dataset names will be given by the format string detailed by: * DatasetName.SLOPE * DatasetName.ASPECT :param compression: The compression filter to use. Default is H5CompressionFilter.LZF :filter_opts: A dict of key value pairs available to the given configuration instance of H5CompressionFilter. For example H5CompressionFilter.LZF has the keywords *chunks* and *shuffle* available. Default is None, which will use the default settings for the chosen H5CompressionFilter instance. :return: An opened `h5py.File` object, that is either in-memory using the `core` driver, or on disk. """ # Setup the geobox geobox = acquisition.gridded_geo_box() # Retrive the spheroid parameters # (used in calculating pixel size in metres per lat/lon) spheroid, _ = setup_spheroid(geobox.crs.ExportToWkt()) # Are we in projected or geographic space is_utm = not geobox.crs.IsGeographic() # Define Top, Bottom, Left, Right pixel margins margins = pixel_buffer(acquisition, buffer_distance) # Get the x and y pixel sizes _, y_origin = geobox.origin x_res, y_res = geobox.pixelsize # Get acquisition dimensions and add 1 pixel top, bottom, left & right cols, rows = geobox.get_shape_xy() ncol = cols + 2 nrow = rows + 2 # elevation dataset elevation = dsm_group[DatasetName.DSM_SMOOTHED.value] ele_rows, ele_cols = elevation.shape # TODO: check that the index is correct # Define the index to read the DEM subset ystart, ystop = (margins.top - 1, ele_rows - (margins.bottom - 1)) xstart, xstop = (margins.left - 1, ele_cols - (margins.right - 1)) idx = (slice(ystart, ystop), slice(xstart, xstop)) subset = as_array(elevation[idx], dtype=numpy.float32, transpose=True) # Define an array of latitudes # This will be ignored if is_utm == True alat = numpy.array([y_origin - i * y_res for i in range(-1, nrow - 1)], dtype=numpy.float64) # yes, I did mean float64. # Output the reprojected result # Initialise the output files if out_group is None: fid = h5py.File('slope-aspect.h5', driver='core', backing_store=False) else: fid = out_group if GroupName.SLP_ASP_GROUP.value not in fid: fid.create_group(GroupName.SLP_ASP_GROUP.value) if filter_opts is None: filter_opts = {} else: filter_opts = filter_opts.copy() filter_opts['chunks'] = acquisition.tile_size group = fid[GroupName.SLP_ASP_GROUP.value] # metadata for calculation param_group = group.create_group('PARAMETERS') param_group.attrs['dsm_index'] = ((ystart, ystop), (xstart, xstop)) param_group.attrs['pixel_buffer'] = '1 pixel' kwargs = compression.config(**filter_opts).dataset_compression_kwargs() no_data = -999 kwargs['fillvalue'] = no_data # Define the output arrays. These will be transposed upon input slope = numpy.zeros((rows, cols), dtype='float32') aspect = numpy.zeros((rows, cols), dtype='float32') slope_aspect(ncol, nrow, cols, rows, x_res, y_res, spheroid, alat, is_utm, subset, slope.transpose(), aspect.transpose()) # output datasets dname = DatasetName.SLOPE.value slope_dset = group.create_dataset(dname, data=slope, **kwargs) dname = DatasetName.ASPECT.value aspect_dset = group.create_dataset(dname, data=aspect, **kwargs) # attach some attributes to the image datasets attrs = {'crs_wkt': geobox.crs.ExportToWkt(), 'geotransform': geobox.transform.to_gdal(), 'no_data_value': no_data} desc = "The slope derived from the input elevation model." attrs['description'] = desc attach_image_attributes(slope_dset, attrs) desc = "The aspect derived from the input elevation model." attrs['description'] = desc attach_image_attributes(aspect_dset, attrs) if out_group is None: return fid