def sample( raster: rasterio.io.DatasetReader, coords: list[tuple[float, ...]]) -> list[tuple[float, float, float]]: """Sample a raster at given coordinates Given a list of coordinates, return a list of x,y,z triples with z coordinates sampled from an input raster Parameters: raster: raster dataset to sample coords: array of tuples containing coordinate pairs (x,y) or triples (x,y,z) Returns: array of tuples containing coordinate triples (x,y,z) """ if len(coords[0]) == 3: logging.info('Input is a 3D geometry, z coordinate will be updated.') z = raster.sample([(x, y) for x, y, z in coords], indexes=raster.indexes) else: z = raster.sample(coords, indexes=raster.indexes) result = [(vert[0], vert[1], vert_z) for vert, vert_z in zip(coords, z)] return result
def resample_band(dataset: rio.io.DatasetReader, target_resolution: float, resampling_method: Resampling, resampler: str) -> dict: """Resample a Band (2D NDArray) :param dataset: Input dataset :param target_resolution: Resolution of the output dataset: what you want to resample to :param resampling_method: Which method to use for resampling (only applicable to rasterio!) :param resampler: Which resampling function to use (e.g. Rasterio vs SciPy zoom) :return: Dictionary containing resampled data ("data") and the resampled profile ("profile") """ # Calculate scale factor scaling = int(dataset.res[0]) / float(target_resolution) # Calculate profile and transfor elements profile = copy.deepcopy(dataset.profile) trans = copy.deepcopy(dataset.transform) transform = Affine(trans.a / scaling, trans.b, trans.c, trans.d, trans.e / scaling, trans.f) height = copy.deepcopy(dataset.height) * scaling width = copy.deepcopy(dataset.width) * scaling profile.update( res=(float(target_resolution), float(target_resolution)), transform=transform, height=height, width=width ) # Resample data to target resolution if resampler is "rasterio": print("[INFO] Using Rasterio resampler...") resampled = dataset.read( out_shape=( dataset.count, int(height), int(width) ), resampling=resampling_method ) else: print("[INFO] Using scipy zoom resampler...") print("[WARNING] Zoom resampler does not honor resampling methods! Use the Rasterio resampler for this!") raw_read = dataset.read() resampled = np.array(list(map( lambda layer: zoom(layer, scaling, order=0, mode='nearest'), raw_read ))) # Create output dictionary output = { "data": resampled, "profile": profile } return output
def subdivide(self, root: str = "", name: str = "", tif: rio.io.DatasetReader = (), sub: int = 5) -> None: if sub is 0: return if type(tif) == tuple: arr, box, meta = tif #with open("./data_lookup.csv", "a") as csv: # csv.write("|".join((root,"ROOT", repr(box))) +"\n") else: arr, box, meta = np.array(tif.read(1)), Box(tif.bounds), tif.meta width = meta["width"] = box.width / 2 height = meta["height"] = box.height / 2 sub_bound = { 0: (0, 1, -1, 0), 1: (1, 1, 0, 0), 2: (0, 0, -1, -1), 3: (1, 0, 0, -1) } sub_slice = { 0: { "sx": slice(None, int(height)), "sy": slice(None, int(width)) }, 1: { "sx": slice(None, int(height)), "sy": slice(int(width), None) }, 2: { "sx": slice(int(height), None), "sy": slice(None, int(width)) }, 3: { "sx": slice(int(height), None), "sy": slice(int(width), None) } } for idx in range(4): sub_name = name + (str(idx) if name is "" else f"_{idx}") directory = f"./{root}/{sub_name}/" if not os.path.exists(directory): os.mkdir(directory) meta["transform"] = from_bounds( box.left + width * sub_bound[idx][0], box.bottom + height * sub_bound[idx][1], box.right + width * sub_bound[idx][2], box.top + height * sub_bound[idx][3], width, height) with rio.open(directory + f"{self.tif_type}.tif", "w+", **meta) as sub_tif: sub_tif.write(arr[sub_slice[idx]["sx"], sub_slice[idx]["sy"]], indexes=1) self.subdivide(root, sub_name, sub_tif, sub - 1) if sub > 1: os.remove(directory + f"{self.tif_type}.tif") os.rmdir(directory) continue
def _read_pixel_value(point: gpd.GeoSeries, source: rio.io.DatasetReader) -> np.ndarray: """Reads raster value from an open rasterio dataset. Designed to be run using a `geodataframe.apply()` function. Args: point: a row from gdf.apply() or gdf.iterrows() source: an open rasterio data source Returns: values: 1-d n-length array with the pixel values of each raster band. """ row, col = source.index(point.geometry.x, point.geometry.y) window = rio.windows.Window(col, row, 1, 1) values = source.read(window=window, boundless=True) return np.squeeze(values)
def drape_geojson( features: Union[Iterable[Dict], fiona.Collection], raster: rasterio.io.DatasetReader, interpolate: bool = False, ) -> Generator[Dict, None, None]: """ Drape with geojson as input, fiona uses geojson as interface. Parameters ---------- features : Iterable[Dict], fiona.Collection vector as geojson raster : rasterio.io.DatasetReader rasterio reader of raster file interpolate : bool, default True choose to interpolate or not * if True, value will be interpolated from nearest value * if False, value will be obtained from exact row and column Yields ------- dict geojson """ """ Drape with geojson as input, fiona uses geojson as interface. :param features: geojson :param raster: rasterio dataset reader :param interpolate: :return: """ dsm_array = raster.read(1) affine = raster.transform no_data = raster.nodatavals for i, feature in enumerate(features): draped_feature = feature.copy() geometry: Dict = feature["geometry"] geom_type: str = geometry["type"] draped_coordinates: Union[List[List[float]], List[List[List[float]]]] = [] if geom_type == "Polygon": draped_coordinates = [ list( drape_coordinates(ring, dsm_array, affine, interpolate, no_data)) for ring in geometry ] elif geom_type == "LineString": draped_coordinates = list( drape_coordinates(geometry, dsm_array, affine, interpolate, no_data)) else: raise ValueError("Unsupported geometry type") draped_feature["geometry"]["coordinates"] = draped_coordinates yield draped_feature
def load_raster_selection(raster_io: rasterio.io.DatasetReader, geom_list: List[ShapelyGeom]) -> np.ndarray: ''' rasterio allows loading of subselection of a tiff file, so given a list of geometries and an open raster DatasetReader, loads in the values within the geom_list. This allows for loading of small selections from otherwise huge tiff's ''' if not isinstance(geom_list, List): geom_list = [geom_list] # Find the window around the geom_list window = rasterio.features.geometry_window(raster_io, geom_list) transform = raster_io.window_transform(window) # Perform the windowed read sub_data = raster_io.read(window=window) return sub_data, transform
def _extract_data(self, image: rasterio.io.DatasetReader, fragment: Fragment) -> (np.ndarray, dict): """ The operation of spiting the images and copying its geo reference is carried out using a sliding window approach, where fragment specifies which part of the original image is to be processed :param image: :param fragment: the split1 size :return: """ split_image = image.read(window=fragment.position) kwargs_split_image = image.meta.copy() kwargs_split_image.update({ "height": self.split_size[0], "width": self.split_size[1], "transform": image.window_transform(fragment.position), }) return split_image, kwargs_split_image
def drape_shapely( geometry: Union[Polygon, LineString], raster: rasterio.io.DatasetReader, interpolate: bool = False, ) -> Union[Polygon, LineString]: """ Drape with shapely geometry as input Parameters ---------- geometry : shapely polygon, shapely linestring vector data as shapely object, currently only support polygon or linestring raster : rasterio.io.DatasetReader rasterio reader of raster file interpolate : bool, default True choose to interpolate or not * if True, value will be interpolated from nearest value * if False, value will be obtained from exact row and column Returns ------- shapely.Polygon or shapely.LineString """ dsm_array = raster.read(1) affine = raster.transform no_data = raster.nodatavals if geometry.type == "Polygon": draped_exterior = list( drape_coordinates(geometry.exterior.coords, dsm_array, affine, interpolate, no_data)) draped_interiors = [ list( drape_coordinates(interior.coords, dsm_array, affine, interpolate, no_data)) for interior in geometry.interiors ] return Polygon(draped_exterior, draped_interiors) elif geometry.type == "LineString": return LineString( list( drape_coordinates(geometry.coords, dsm_array, affine, interpolate, no_data))) else: raise ValueError("Unsupported geometry type")
def padded_read(fp: rasterio.io.DatasetReader, region: PixelWindow, band: Optional[int] = None, pad_value: Union[int, Sequence[int]] = 0) -> np.ndarray: """Read the specified region from the rasterio dataset. If any part of the region is out of bounds of the dataset, that part is padded with the padding value. If band is supplied, only that band is read, otherwise all bands are read.""" available_region = pixel_window_intersect(datafile_pixel_window(fp), region) dat = fp.read(indexes=band, window=available_region) # pad_dataset_to_window assumes we are dealing with multiple bands, and it would be tricky to change that. # instead, we just add-then-remove an extra level in the case that we are dealing with a single band if band: dat = dat[None] # add an extra dimension dat = pad_dataset_to_window(dat, available_region, region, pad_value) if band: dat = dat[0] return dat
def raster_to_geodataframe( raster_data: np.ndarray, transform: affine.Affine, window: rasterio.windows.Window, raster_io: rasterio.io.DatasetReader, ) -> gpd.GeoDataFrame: """ Takes in raster data as a numpy array and converts it to a geo df, then writes that to file """ if raster_data.ndim == 3: assert raster_data.shape[ 0] == 1, "ERROR - can't handle multichannel yet" raster_data = raster_data[0] # To convert when our raster_data is from a windowed # read we need to go from the windowed raster_data # coords -> orig dataset coords -> x,y spatial data_h, data_w = raster_data.shape all_geoms = [] data = [] for h in range(data_h): for w in range(data_w): # Convert to the coord sys of the raster_io dataset abs_h = h + window.row_off abs_w = w + window.col_off # And use its xy method now # abs_x1, abs_y1 = raster_io.xy(abs_h, abs_w) # abs_x0, abs_y0 = raster_io.xy(abs_h-1, abs_w-1) p_ul = Point(raster_io.xy(abs_h, abs_w, offset='ul')) p_lr = Point(raster_io.xy(abs_h, abs_w, offset='lr')) geom = MultiPoint([p_ul, p_lr]).envelope all_geoms.append(geom) data.append(raster_data[h, w]) return gpd.GeoDataFrame.from_dict({'geometry': all_geoms, 'pop': data})
def sample_from_rasterio(raster: rasterio.io.DatasetReader, point_x: Union[float, int, list, np.ndarray], point_y: Union[float, int, list, np.ndarray], sample_outside_extent: bool = True) -> Union[list, float]: """Sampling the value of a rasterio object at a given point within the extent of the raster Parameters __________ raster : rasterio.io.DatasetReader Rasterio Object containing the height information point_x : list, np.ndarray, float, int Object containing the x coordinates of a point or points at which the array value is obtained point_y : list, np.ndarray, float, int Object containing the y coordinates of a point or points at which the array value is obtained sample_outside_extent : bool Allow sampling outside the extent of the rasterio object, default is True Returns _______ sample : list, float Value/s of the raster at the provided position/s """ # Checking that the raster is a rasterio object if not isinstance(raster, rasterio.io.DatasetReader): raise TypeError('Raster must be provided as rasterio object') # Checking if the point coordinates are stored as a list if not isinstance(point_x, (list, np.ndarray, float, int)): raise TypeError('Point_x must be of type list or np.ndarray') # Checking if the point coordinates are stored as a list if not isinstance(point_y, (list, np.ndarray, float, int)): raise TypeError('Point_y must be of type list or np.ndarray') # Checking the length of the point list if not isinstance(point_x, (float, int)) and not isinstance(point_y, (float, int)): if len(point_x) != len(point_y): raise ValueError('Length of both point lists/arrays must be equal') # Checking that all elements of the point list are of type int or float if isinstance(point_x, (list, np.ndarray)): if not all(isinstance(n, (int, float, np.int32, np.float64)) for n in point_x): raise TypeError('Point values must be of type int or float') # Checking that all elements of the point list are of type int or float if isinstance(point_y, (list, np.ndarray)): if not all(isinstance(n, (int, float, np.int32, np.float64)) for n in point_y): raise TypeError('Point values must be of type int or float') # Checking that sample_outside_extent is of type bool if not isinstance(sample_outside_extent, bool): raise TypeError('Sample_outside_extent argument must be of type bool') # If sample_outside extent is true, a nodata value will be assigned if not sample_outside_extent: # Checking if the point is located within the provided raster extent if isinstance(point_x, (list, np.ndarray)): if any(x < raster.bounds[0] for x in point_x) or any(x > raster.bounds[2] for x in point_x): raise ValueError('One or multiple points are located outside of the extent') if isinstance(point_y, (list, np.ndarray)): if any(y < raster.bounds[1] for y in point_y) or any(y > raster.bounds[3] for y in point_y): raise ValueError('One or multiple points are located outside of the extent') # Checking if the point is located within the provided raster extent if isinstance(point_x, (float, int)): if point_x < raster.bounds[0] or point_x > raster.bounds[2]: raise ValueError('One or multiple points are located outside of the extent') if isinstance(point_y, (float, int)): if point_y < raster.bounds[1] or point_y > raster.bounds[3]: raise ValueError('One or multiple points are located outside of the extent') # Converting lists of coordinates to np.ndarrays if isinstance(point_x, list) and isinstance(point_y, list): point_x = np.array(point_x) point_y = np.array(point_y) # Converting points into array coordinates = np.array([point_x, point_y]).T if isinstance(point_x, (float, int)) and isinstance(point_y, (float, int)): sample = float(list(next(raster.sample([coordinates])))[0]) else: # Sampling from the raster using list comprehension sample = [float(z[0]) for z in raster.sample(coordinates)] return sample