Create contour geometries at specified edges for the given raster data.
Notes
This function is similar to geokit.geom.polygonizeMatrix, although it only
operates on the user-specified edges AND applies the 'Marching Squares'
algorithm
See the gdal function "GDALContourGenerateEx" for more information on the
specifics of this algorithm
The edges to search for within the raster dataset
* This parameter can be set as "None", in which case an additional
argument should be given to specify how the edges should be determined
- See the documentation of "GDALContourGenerateEx"
- Ex. "LEVEL_INTERVAL=10", contourEdges=None
defcontours(source:load_raster_input,contourEdges:list[float]|None,polygonize:bool=True,unpack:bool=True,raster_band_index:int=1,**kwargs,)->pd.DataFrame:"""Create contour geometries at specified edges for the given raster data. Notes ----- This function is similar to geokit.geom.polygonizeMatrix, although it only operates on the user-specified edges AND applies the 'Marching Squares' algorithm See the gdal function "GDALContourGenerateEx" for more information on the specifics of this algorithm Parameters ---------- source : Anything acceptable by loadRaster() The raster datasource to operate on contourEdges : list[float] | None The edges to search for within the raster dataset * This parameter can be set as "None", in which case an additional argument should be given to specify how the edges should be determined - See the documentation of "GDALContourGenerateEx" - Ex. "LEVEL_INTERVAL=10", contourEdges=None polygons : bool If true, contours are returned as polygons instead of linstrings unpack : bool If True, Multipolygon/MultiLinestring objects are decomposed raster_band_index : int The index of the raster which should be loaded from the raster band. **kwargs: * All keyword arguments are passed on to a call to gdal.ContourGenerateEx * They are used to construct the 'options' parameter * Example keys include: LEVEL_INTERVAL, LEVEL_BASE, LEVEL_EXP_BASE, NODATA * Do not use the key "ID_FIELD", since this is employed already Returns ------- pandas.DataFrame * The column 'geom' corresponds to generated geometry objects * The columns 'ID' corresponds to the associated contour edge for each object """warnings.warn(message="The current behavior of geokits's contours function is deprecated. GDAL has changed"" how contours are drawn close to minimum and maximum values, and will discontinue"" the current behavior in GDAL 3.11 or 3.12. Geokit will also drop the current"" behavior with the next GDAL update. For more information, please see the"" following discussion: https://github.com/OSGeo/gdal/issues/12938.",category=DeprecationWarning,)# Open rasterraster=loadRaster(source)band=raster.GetRasterBand(raster_band_index)rasterSRS=SRS.loadSRS(raster.GetProjectionRef())# Make temporary vectordriver:Driver=gdal.GetDriverByName("Memory")source:gdal.Dataset=driver.Create("",0,0,0,gdal.GDT_Unknown)layer:ogr.Layer=source.CreateLayer("",rasterSRS,ogr.wkbPolygonifpolygonizeelseogr.wkbLineString)field=ogr.FieldDefn("DN",ogr.OFTInteger)layer.CreateField(field)# Setup contour functionargs=["{}={}".format(a,b)fora,binkwargs.items()]args.append("ID_FIELD=DN")ifpolygonize:args.append("POLYGONIZE=YES")ifcontourEdgesisnotNone:opt="FIXED_LEVELS="foredgeincontourEdges:opt+=str(edge)+","args.append(opt[:-1])result=gdal.ContourGenerateEx(band,layer,options=args)ifnotresult==gdal.CE_None:raiseGeoKitRasterError("Failed to compute raster contours")layer.CommitTransaction()IDs=[]geoms=[]iterator_n=0forftridinrange(layer.GetFeatureCount()):ftr:ogr.Feature=layer.GetFeature(ftrid)geom=ftr.GetGeometryRef()value=ftr.GetField(0)print(iterator_n)iterator_n=iterator_n+1ifunpack:forgiinrange(geom.GetGeometryCount()):geoms.append(geom.GetGeometryRef(gi).Clone())IDs.append(value)else:geoms.append(geom.Clone())IDs.append(value)countour_data_frame=pd.DataFrame(dict(geom=geoms,ID=IDs))# return geomsreturncountour_data_frame
Raster datasets are always written in the 'yAtTop' orientation. Meaning that
the first row of data values (either written to or read from the dataset) will
refer to the TOP of the defined boundary, and will then move downward from
there
If a data matrix is given, and a negative pixelWidth is defined, the data
will be flipped automatically
The pixel width of the raster in units of the input srs
* The keyword 'dx' can be used as well and will override anything given
assigned to 'pixelWidth'
The pixel height of the raster in units of the input srs
* The keyword 'dy' can be used as well and will override anything given
assigned to 'pixelHeight'
A path to an output file
* If output is None, the raster will be created in memory and a dataset
handle will be returned
* If output is given, the raster will be written to disk and nothing will
be returned
The datatype of the represented by the created raster's band
* Options are: Byte, Int16, Int32, Int64, Float32, Float64
* If dtype is None and data is None, the assumed datatype is a 'Byte'
* If dtype is None and data is not None, the datatype will be inferred
from the given data
(Anything acceptable to geokit.srs.loadSRS(); optional, default:
None
)
–
The srs of the point to create
* If not given, no srs will be assigned to the created raster. Please be
aware that some operations may not work correctly if no SRS is given.
* If 'bounds' is an Extent object, the bounds' internal srs will override
this input
A flag instructing the output raster to use a compression algorithm
* only useful if 'output' has been defined
* "DEFLATE" used for Linux/Mac, "LZW" used for Windows
Specifies which value should be considered as 'no data' in the created
raster
* Must be the same datatype as the 'dtype' input (or that which is derived)
defcreateRaster(bounds:tuple[numeric,numeric,numeric,numeric],output:None|str|pathlib.Path=None,pixelWidth:numeric=100,pixelHeight:numeric=100,dtype:None|geokit_c_data_types_literal=None,srs:srs_input|None=None,compress:bool=True,noData:numeric|None=None,overwrite:bool=True,fill:numeric|None=None,data:np.ndarray|None=None,meta:dict|None=None,scale:numeric|None=1,offset:numeric=0,creationOptions:dict=dict(),raster_band_index:int=1,)->gdal.Dataset|str:"""Create a raster file. NOTE: ----- Raster datasets are always written in the 'yAtTop' orientation. Meaning that the first row of data values (either written to or read from the dataset) will refer to the TOP of the defined boundary, and will then move downward from there If a data matrix is given, and a negative pixelWidth is defined, the data will be flipped automatically Parameters ---------- bounds : (xMin, yMin, xMax, yMax) or Extent The geographic extents spanned by the raster pixelWidth : numeric The pixel width of the raster in units of the input srs * The keyword 'dx' can be used as well and will override anything given assigned to 'pixelWidth' pixelHeight : numeric The pixel height of the raster in units of the input srs * The keyword 'dy' can be used as well and will override anything given assigned to 'pixelHeight' output : str, pathlib.Path, None A path to an output file * If output is None, the raster will be created in memory and a dataset handle will be returned * If output is given, the raster will be written to disk and nothing will be returned dtype : geokit_c_data_types_literal, none The datatype of the represented by the created raster's band * Options are: Byte, Int16, Int32, Int64, Float32, Float64 * If dtype is None and data is None, the assumed datatype is a 'Byte' * If dtype is None and data is not None, the datatype will be inferred from the given data srs : Anything acceptable to geokit.srs.loadSRS(); optional The srs of the point to create * If not given, no srs will be assigned to the created raster. Please be aware that some operations may not work correctly if no SRS is given. * If 'bounds' is an Extent object, the bounds' internal srs will override this input compress : bool A flag instructing the output raster to use a compression algorithm * only useful if 'output' has been defined * "DEFLATE" used for Linux/Mac, "LZW" used for Windows noData : numeric; optional Specifies which value should be considered as 'no data' in the created raster * Must be the same datatype as the 'dtype' input (or that which is derived) fill : numeric; optional The initial value given to all pixels in the created raster band - numeric * Must be the same datatype as the 'dtype' input (or that which is derived) overwrite : bool A flag to overwrite a pre-existing output file * If set to False and an 'output' is specified which already exists, an error will be raised data : matrix_like A 2D matrix to write into the resulting raster * array dimensions must fit raster dimensions as calculated by the bounds and the pixel resolution meta : dictionary, None of key values pairs that is stored in the raster as meta data. Is written to the raster using the scale : numeric; optional The scaling value given to apply to all values - numeric * Must be the same datatype as the 'dtype' input (or that which is derived) offset : numeric; optional The offset value given to apply to all values - numeric * Must be the same datatype as the 'dtype' input (or that which is derived) raster_band_index: int, defaults to 1 Determines which band is written to in the output raster dataset. Returns ------- * If 'output' is None: gdal.Dataset * If 'output' is a string: The path to the output is returned (for easy opening). It has to be saved as geotiff with .tif suffix. """# Check for existing fileifoutputisnotNone:output=str(output)output_path=pathlib.Path(output)ifoutput_path.exists():ifoverwriteisTrue:output_path.unlink()output_path_with_aux_extension=output_path.with_suffix(".aux.xml")ifoutput_path_with_aux_extension.exists():output_path_with_aux_extension.unlink()else:raiseGeoKitRasterError("Output file already exists: %s"%output)# check if the directory existselifnotoutput_path.parent.is_dir():raiseFileNotFoundError(f"Output directory does not exist: {os.path.dirname(output)}")# check if writeable:ifnotos.access(os.path.dirname(output),os.W_OK):print(os.path.dirname(output))raisePermissionError(f"Writing permission error for path: {os.path.dirname(output)}")# Ensure bounds is okay# bounds = UTIL.fitBoundsTo(bounds, pixelWidth, pixelHeight)# Make a raster dataset and pull the band/maskBand objectsx_min=bounds[0]y_min=bounds[1]x_max=bounds[2]y_max=bounds[3]# Always use the "Y-at-Top" orientationcols=int(round((x_max-x_min)/pixelWidth))rows=int(round((y_max-y_min)/abs(pixelHeight)))list_of_numbers=[]minimum_gdal_type_list=[]ifisinstance(dtype,str):minimum_gdal_type_list.append(dtype)ifisinstance(noData,numeric):list_of_numbers.append(noData)ifisinstance(fill,numeric):list_of_numbers.append(fill)ifisinstance(data,np.ndarray):numpy_data_type=str(data.dtype)minimum_gdal_type_list.append(numpy_data_type)list_of_numbers.append(data.min())list_of_numbers.append(data.max())elifdataisNone:passelse:raiseGeoKitRasterError("Data must be given as a numpy ndarray or None")data_type_constant=MinimumCDataTypeHandler.get_valid_gdal_data_type_as_constant(list_of_numbers=list_of_numbers,minimum_gdal_type_list=minimum_gdal_type_list)# # Get DataType# if dtype is not None: # a dtype was given, use it!# dtype = gdalType(dtype)# elif data is not None: # a data matrix was give, use it's dtype! (assume a numpy array or derivative)# dtype = gdalType(data.dtype)# else: # Otherwise, just assume we want a Byte# dtype = "GDT_Byte"# Open the driveropts=OrderedDict()ifcompressandoutputisnotNone:opts["COMPRESS"]=COMPRESSION_OPTION_STRifcreationOptionsisnotNone:opts.update(creationOptions)opts=["{}={}".format(k,v)fork,vinopts.items()]ifoutputisNone:driver:gdal.Driver=gdal.GetDriverByName("Mem")# create a raster in memoryraster:gdal.Dataset=driver.Create("",cols,rows,1,data_type_constant,opts)else:driver:gdal.Driver=gdal.GetDriverByName("GTiff")# Create a raster in storageraster:gdal.Dataset=driver.Create(output,cols,rows,1,data_type_constant,opts)ifrasterisNone:raiseGeoKitRasterError("Failed to create raster")# Do the rest in a "try" statement so that a failure won't bind the sourcetry:raster.SetGeoTransform((x_min,abs(pixelWidth),0,y_max,0,-1*abs(pixelHeight)))# Set the SRSifsrsisnotNone:rasterSRS=SRS.loadSRS(srs)raster.SetProjection(rasterSRS.ExportToWkt())else:warnings.warn(message="No srs given when creating raster. Please be aware that some operations may not work correctly.",category=UserWarning,)# Fill the raster will zeros, null values, or initial values (if given)band:gdal.Band=raster.GetRasterBand(raster_band_index)ifscaleisnotNone:band.SetScale(scale)ifoffsetisnotNone:band.SetOffset(offset)ifnoDataisnotNone:band.SetNoDataValue(noData)iffillisNoneanddataisNone:band.Fill(noData)ifdataisNone:iffillisNone:band.Fill(0)else:band.Fill(fill)else:# make sure dimension size is goodifnot(data.shape[0]==rowsanddata.shape[1]==cols):raiseGeoKitRasterError("Raster dimensions and input data dimensions do not match.The data has rows="+str(data.shape[0])+" and columns="+str(data.shape[1])+". The raster specification expects rows="+str(rows)+" and columns="+str(cols))# See if data needs flippingifpixelHeight<0:data=data[::-1,:]# Write it!band.WriteArray(data)band.FlushCache()band.ComputeRasterMinMax(0)band.ComputeBandStats(0)# Write MetaData, maybeifmetaisnotNone:fork,vinmeta.items():raster.SetMetadataItem(k,v)# writes the raster to the hard driveraster.FlushCache()# Return raster if in memoryifoutputisNone:returnraster# Donereturnoutput# Handle the fail caseexceptExceptionase:raster=Noneraisee
defcreateRasterLike(source:load_raster_input|RasterInfo,copyMetadata:bool=True,metadata:dict|None=None,data:None|np.ndarray=None,dtype:None|geokit_c_data_types_literal=None,**kwargs,):"""Create a raster described by the given raster info (as returned from a call to rasterInfo() ). * This copies all characteristics of the given raster, including: bounds, pixelWidth, pixelHeight, dtype, srs, noData, and meta. * Any keyword argument which is given will override values found in the source """ifUTIL.isRaster(source):raster_info=rasterInfo(source)elifisinstance(source,RasterInfo):raster_info=sourceelse:raiseGeoKitRasterError("Could not understand source")ifcopyMetadataandmetadataisnotNone:raiseGeoKitRasterError("If metadata is given, copyMetadata cannot be True!")bounds=kwargs.pop("bounds",raster_info.bounds)pixelWidth=kwargs.pop("pixelWidth",raster_info.pixelWidth)pixelHeight=kwargs.pop("pixelHeight",raster_info.pixelHeight)srs=kwargs.pop("srs",raster_info.srs)noData=kwargs.pop("noData",raster_info.noData)data_type_as_string=kwargs.pop("data_type_as_string",dtype)ifcopyMetadata:meta=kwargs.pop("meta",raster_info.meta)else:meta=metadatareturncreateRaster(bounds=bounds,pixelWidth=pixelWidth,pixelHeight=pixelHeight,srs=srs,noData=noData,meta=meta,dtype=data_type_as_string,data=data,**kwargs,)
(Anything acceptable to geokit.srs.loadSRS(); optional, default:
None
)
–
The srs of the drawn raster data
* If not given, the raster's internal srs is assumed
* If the drawing resolution does not match the source's inherent
resolution, the source will be warped to the correct format
The resolution of the plotted raster data
* Lower resolution means more pixels to draw and can be a burden on
memory
* If a tuple is given, resolutions in the X and Y direction are expected
* Changing the resolution from the inherent resolution requires a warp
The cutline to limit the drawn data too
* If a string is given, it must be a path to a vector file
* Values outside of the cutline are given the noData value of the raster
* Requires a warp
The figure size to create when generating a new axis
* If resultign figure looks weird, altering the figure size is your best
bet to make it look nicer
The title to give to the generated colorbar
* If not given, but 'colorBy' is given, the same string for 'colorBy'
is used
* Only useful when 'colorBy' is given
defdrawRaster(source:load_raster_input|pd.DataFrame,srs:srs_input|None=None,ax:matplotlib.axes._axes.Axes|None|AxHands=None,resolution:numeric|None=None,cutline=None,figsize:tuple[numeric,numeric]=(12,12),xlim:tuple[numeric,numeric]|None=None,ylim:tuple[numeric,numeric]|None=None,fontsize:int=16,hideAxis:bool=False,cbar:bool=True,cbarTitle:str|None=None,vmin:float|None=None,vmax:float|None=None,cmap="viridis",cbargs=None,noData:numeric|None=None,zorder=0,resampleAlg:Literal["near","bilinear","cubic","cubicspline","lanczos","average","rms","mode","max","min","med","Q1","Q3","sum",]="med",**warp_kwargs,)->AxHands:"""Draw a raster as an image on a matplotlib canvas. Parameters ---------- source : Anything acceptable by loadRaster() The raster datasource to draw srs : Anything acceptable to geokit.srs.loadSRS(); optional The srs of the drawn raster data * If not given, the raster's internal srs is assumed * If the drawing resolution does not match the source's inherent resolution, the source will be warped to the correct format ax : matplotlib axis; optional The axis to draw the geometries on * If not given, a new axis is generated and returned resolution : numeric or tuple; optional The resolution of the plotted raster data * Lower resolution means more pixels to draw and can be a burden on memory * If a tuple is given, resolutions in the X and Y direction are expected * Changing the resolution from the inherent resolution requires a warp cutline : str or ogr.Geometry; optional The cutline to limit the drawn data too * If a string is given, it must be a path to a vector file * Values outside of the cutline are given the noData value of the raster * Requires a warp figsize : (int, int); optional The figure size to create when generating a new axis * If resultign figure looks weird, altering the figure size is your best bet to make it look nicer xlim : (float, float); optional The x-axis limits ylim : (float, float); optional The y-axis limits fontsize : int; optional A base font size to apply to tick marks which appear * Titles and labels are given a size of 'fontsize' + 2 cbarTitle : str; optional The title to give to the generated colorbar * If not given, but 'colorBy' is given, the same string for 'colorBy' is used * Only useful when 'colorBy' is given vmin : float; optional The minimum value to color * Only useful when 'colorBy' is given vmax : float; optional The maximum value to color * Only useful when 'colorBy' is given cmap : str or matplotlib ColorMap; optional The colormap to use when coloring * Only useful when 'colorBy' is given cbargs : dict; optional noData : numeric; optional Replaces all previous noData values with this value in the output raster. resampleAlg : str, optional The resampleAlg passed on to a call of warp() if needed, by default "med" **kwargs : Passed on to a call to warp() * Determines how the warping is carried out * Consider using 'resampleAlg' or 'workingType' for finer control Returns ------- A namedtuple containing: 'ax' -> The map axis 'handles' -> All geometry handles which were created in the order they were drawn 'cbar' -> The colorbar handle if it was drawn """ifisinstance(ax,AxHands):ax=ax.axifaxisNone:new_main_axis=Truefig,ax=plt.subplots(figsize=figsize)elifisinstance(ax,matplotlib.axes._axes.Axes):new_main_axis=Falseelse:raiseException("Expected None or matplotlib.axes._axes.Axes object for the 'ax' argument. However, an object of type: "+str(type(ax))+" has been provided.")ifhideAxis:ax.axis("off")ifcbarisFalse:cbax=Noneelse:divider=make_axes_locatable(ax)cbax=divider.append_axes("right",size="2.5%",pad=0.05)ax.tick_params(labelsize=fontsize)# Load the raster datasource and check for transformationsource=loadRaster(source)info=rasterInfo(source)ifnot(srsisNoneandresolutionisNoneandcutlineisNoneandxlimisNoneandylimisNone):ifnot(xlimisNoneandylimisNone):bounds=(xlim[0],ylim[0],xlim[1],ylim[1],)else:bounds=NoneifresolutionisNone:xres,yres=None,Noneelse:try:xres,yres=resolutionexcept:xres,yres=resolution,resolutionsource=warp(source,cutline=cutline,pixelHeight=yres,pixelWidth=xres,srs=srs,bounds=bounds,noData=noData,resampleAlg=resampleAlg,**warp_kwargs,)info=rasterInfo(source)# Read the Datadata=extractMatrix(source).astype(float)data[data==info.noData]=np.nan# Draw imageext=(info.xMin,info.xMax,info.yMin,info.yMax,)h=ax.imshow(data,extent=ext,vmin=vmin,vmax=vmax,cmap=cmap,zorder=zorder,interpolation="none",)# Draw ColorbarifcbarisTrue:tmp=dict(cmap=cmap,orientation="vertical")ifcbargsisnotNone:tmp.update(cbargs)cbar_output=plt.colorbar(h,cax=cbax,**tmp)cbar_output.ax.tick_params(labelsize=fontsize)ifcbarTitleisnotNone:cbar_output.set_label(cbarTitle,fontsize=fontsize+2)else:cbar_output=None# Do some formattingax.set_aspect("equal")ax.autoscale(enable=True)ifxlimisnotNone:ax.set_xlim(*xlim)ifylimisnotNone:ax.set_ylim(*ylim)# Done!returnUTIL.AxHands(ax,h,cbar_output)
NOTE:
* The basemap is drawn using the Smopy python package. See here: https://github.com/rossant/smopy
* Be careful to adhere to the usage guidelines of the chosen tile source
- By default, this source is OSM. See here: https://wiki.openstreetmap.org/wiki/Tile_servers
Extract all or part of a raster's band as a numpy matrix.
Note:
Unless one is trying to get the entire matrix from the raster dataset, usage
of this function requires intimate knowledge of the raster's characteristics.
In such a case it is probably easier to use Extent.extractMatrix
The boundary to clip the raster to before mutating
* If given as an Extent, the extent is always cast to the source's
- native srs before mutating
* If given as a tuple, (xMin, yMin, xMax, yMax) is expected
- Units must be in the srs specified by 'boundsSRS'
* This boundary must fit within the boundary of the rasters source
* The boundary is always fitted to the source's grid, so the returned
values do not necessarily match to the boundary which is provided
If True, the matrix will search for no data values and change them to
numpy.nan
* Data type will always result in a float, so be careful with large
matrices
defextractMatrix(source:load_raster_input,bounds=None,boundsSRS:srs_input="latlon",maskBand:bool=False,autocorrect:bool=False,returnBounds:bool=False,)->np.ndarray|tuple[np.ndarray,tuple[float,float,float,float]|None]:"""Extract all or part of a raster's band as a numpy matrix. Note: ----- Unless one is trying to get the entire matrix from the raster dataset, usage of this function requires intimate knowledge of the raster's characteristics. In such a case it is probably easier to use Extent.extractMatrix Parameters ---------- source : Anything acceptable by loadRaster() The raster datasource bounds: tuple or Extent The boundary to clip the raster to before mutating * If given as an Extent, the extent is always cast to the source's - native srs before mutating * If given as a tuple, (xMin, yMin, xMax, yMax) is expected - Units must be in the srs specified by 'boundsSRS' * This boundary must fit within the boundary of the rasters source * The boundary is always fitted to the source's grid, so the returned values do not necessarily match to the boundary which is provided boundsSRS: Anything acceptable to geokit.srs.loadSRS(); optional The srs of the 'bounds' argument * This is ignored if the 'bounds' argument is an Extent object or is None autocorrect : bool; optional If True, the matrix will search for no data values and change them to numpy.nan * Data type will always result in a float, so be careful with large matrices returnBounds : bool; optional If True, return the computed bounds along with the matrix data Returns ------- * If returnBounds is False: numpy.ndarray -> Two dimensional matrix * If returnBounds is True: (numpy.ndarray, tuple) - ndarray is matrix data - tuple is the (xMin, yMin, xMax, yMax) of the computed bounds """sourceDS=loadRaster(source)# BE sure we have a rasterdsInfo=rasterInfo(sourceDS)ifmaskBand:mb=sourceDS.GetMaskBand()else:sourceBand=sourceDS.GetRasterBand(1)# get band# Handle the boundariesifboundsisnotNone:# check for extenttry:isExtent=bounds._whatami=="Extent"except:isExtent=False# Ensure srs is okayifisExtent:bounds=bounds.castTo(dsInfo.srs).fit((dsInfo.dx,dsInfo.dy)).xyXYelse:boundsSRS=SRS.loadSRS(boundsSRS)ifnotdsInfo.srs.IsSame(boundsSRS):bounds=GEOM.boundsToBounds(bounds,boundsSRS,dsInfo.srs)bounds=UTIL.fitBoundsTo(bounds,dsInfo.dx,dsInfo.dy,expand=True,startAtZero=True)# Find offsetsxoff=int(np.round((bounds[0]-dsInfo.xMin)/dsInfo.dx))ifxoff<0:raiseGeoKitRasterError("The given boundary exceeds the raster's xMin value")xwin=int(np.round((bounds[2]-dsInfo.xMin)/dsInfo.dx))-xoffifxwin>dsInfo.xWinSize:raiseGeoKitRasterError("The given boundary exceeds the raster's xMax value")ifdsInfo.yAtTop:yoff=int(np.round((dsInfo.yMax-bounds[3])/dsInfo.dy))ifyoff<0:raiseGeoKitRasterError("The given boundary exceeds the raster's yMax value")ywin=int(np.round((dsInfo.yMax-bounds[1])/dsInfo.dy))-yoffifywin>dsInfo.yWinSize:raiseGeoKitRasterError("The given boundary exceeds the raster's yMin value")else:yoff=int(np.round((bounds[1]-dsInfo.yMin)/dsInfo.dy))ifyoff<0:raiseGeoKitRasterError("The given boundary exceeds the raster's yMin value")ywin=int(np.round((bounds[3]-dsInfo.yMin)/dsInfo.dy))-yoffifywin>dsInfo.yWinSize:raiseGeoKitRasterError("The given boundary exceeds the raster's yMax value")else:xoff=0yoff=0xwin=Noneywin=None# get DataifmaskBand:data=mb.ReadAsArray(xoff=xoff,yoff=yoff,win_xsize=xwin,win_ysize=ywin)else:data=sourceBand.ReadAsArray(xoff=xoff,yoff=yoff,win_xsize=xwin,win_ysize=ywin)ifdsInfo.scaleisnotNoneanddsInfo.scale!=1.0:data=data*dsInfo.scaleifdsInfo.offsetisnotNoneanddsInfo.offset!=0.0:data=data+dsInfo.offset# Correct 'nodata' valuesifautocorrect:noData=sourceBand.GetNoDataValue()data=data.astype(np.float64)data[data==noData]=np.nan# make sure we are returning data in the 'flipped-y' orientationifnotisFlipped(source):data=data[::-1,:]# DoneifreturnBounds:returndata,boundselse:returndata
The raster datasource, can be a filepath, a raster dataset etc., see
RASTER.loadRaster() for details. Alternatively, a list of multiple
such raster datasources.
Coordinates for the points to extract
* All points must be in the same SRS
* !REMEMBER! For lat and lon coordinates, X is lon and Y is lat
(opposite of what you may think...)
The window range (in pixels) to extract the values centered around the
closest raster index to the indicated locations.
* A winRange of 0 will only extract the closest raster value
* A winRange of 1 will extract a window of shape (3,3)
* A winRange of 3 will extract a window of shape (7,7)
Return only the extracted data and omit xOffeset, yOffset, inBounds
Returns:
* If only a single location is given:
–
namedtuple -> (data : The extracted data at the location
xOffset : The X index distance from the location to the
center of the closest raster pixel
yOffset : The Y index distance from the location to the
center of the closest raster pixel
inBounds: Flag for whether or not the location is within
The raster's bounds
)
* If Multiple locations are given:
–
pandas.DataFrame
* Columns are (data, xOffset, yOffset, inBounds)
- See above for column descriptions
* Index is 0...N if 'points' input is not a LocationSet
or returns numpy array if _onlyValues=True
defextractValues(source:load_raster_input|list[load_raster_input],points:tuple[float,float]|ogr.Geometry|list[tuple[float,float]]|list[ogr.Geometry]|Location|LocationSet,pointSRS:srs_input|None=None,winRange:int=0,noDataOkay:bool=True,_onlyValues:bool=False,)->ptValue|pd.DataFrame|numeric|np.ndarray:"""Extracts the value of a raster at a given point or collection of points. Can also extract a window of values if desired. * If the given raster is not in the 'flipped-y' orientation, the result will be automatically flipped Notes ----- Generally speaking, interpolateValues() should be used instead of this function Parameters ---------- source : Anything acceptable by loadRaster() or list The raster datasource, can be a filepath, a raster dataset etc., see RASTER.loadRaster() for details. Alternatively, a list of multiple such raster datasources. points : (X,Y) or [(X1,Y1), (X2,Y2), ...] or Location or LocationSet() Coordinates for the points to extract * All points must be in the same SRS * !REMEMBER! For lat and lon coordinates, X is lon and Y is lat (opposite of what you may think...) pointSRS : Anything acceptable to geokit.srs.loadSRS(); optional The srs of the point to create * If not given, longitude/latitude is assumed * Only useful when 'points' is not a LocationSet winRange : int The window range (in pixels) to extract the values centered around the closest raster index to the indicated locations. * A winRange of 0 will only extract the closest raster value * A winRange of 1 will extract a window of shape (3,3) * A winRange of 3 will extract a window of shape (7,7) noDataOkay: bool If True, an error is raised if a 'noData' value is extracted If False, numpy.nan is inserted whenever a 'noData' value is extracted _onlyValues: bool Return only the extracted data and omit xOffeset, yOffset, inBounds Returns ------- * If only a single location is given: namedtuple -> (data : The extracted data at the location xOffset : The X index distance from the location to the center of the closest raster pixel yOffset : The Y index distance from the location to the center of the closest raster pixel inBounds: Flag for whether or not the location is within The raster's bounds ) * If Multiple locations are given: pandas.DataFrame * Columns are (data, xOffset, yOffset, inBounds) - See above for column descriptions * Index is 0...N if 'points' input is not a LocationSet or returns numpy array if _onlyValues=True """# Be sure we have a raster and srsifnotisinstance(source,list):source=[source]# make sure all source entries can be interpreted by loadRastertry:source=[loadRaster(s)forsinsource]except:raiseTypeError("At least one source cannot be loaded by geokit.raster.loadRaster().")raster_srs=Noneforsinsource:# load file to make sure it can be interpreted by loadRasterifraster_srsisNone:raster_srs=rasterInfo(s).srselse:assertraster_srs.IsSame(rasterInfo(s).srs),"All source entries must have the same SRS."# Ensure we have a list of point geometriespoints_as_list_of_geom=_convertPointsToListOfOGRPoints(points=points,point_input_SRS=pointSRS,output_srs=raster_srs)iflen(points_as_list_of_geom)>1:asSingle=Falseeliflen(points_as_list_of_geom)==1:asSingle=Trueelse:raiseGEOM.GeoKitGeomError("No points have been passed to the function extractValues()")# get the srcs that are actually overlapping with our pointssrc_mapper={}iflen(source)>1:forsinsource:# get bounds and filter indices of points that fall into bounds_bounds=rasterInfo(s).bounds_indices=[ifori,pinenumerate(points_as_list_of_geom)if(_bounds[0]<=p.GetX()<=_bounds[2])and(_bounds[1]<=p.GetY()<=_bounds[3])]# add source as key plus list of overlapped point indicessrc_mapper[s]=_indiceselse:# we have only one source which must be applied to all pointssrc_mapper[source[0]]=list(range(len(points_as_list_of_geom)))# srcs = source * len(points)# iterate over all unique srcs and extract the raster values for the affected points _src by _srcvalues=[np.nan]*len(points_as_list_of_geom)# initialize values with nan for every pointinBounds=[False]*len(points_as_list_of_geom)# initialize inbounds with False for every pointxOffset=[np.nan]*len(points_as_list_of_geom)# initialize offsets with nan for every pointyOffset=[np.nan]*len(points_as_list_of_geom)# initialize offsets with nan for every pointfor_src,_indsinsrc_mapper.items():# get the indices of the points for which data has been extracted already_xtrct=[ifori,vinenumerate(values)ifnp.all(pd.notnull(v))]# deduct them from the inds here (they may have been extracted already from an overlapping _src tile)_inds=list(set(_inds)-set(_xtrct))iflen(_inds)==0:# no indices left to extract from this _srccontinue# get the points with this _src via list indices_points=[points_as_list_of_geom[i]foriin_inds]# get the raster info for this _src_info=rasterInfo(_src)# Get x/y values as numpy arraysx=np.array([pt.GetX()forptin_points])y=np.array([pt.GetY()forptin_points])# Calculate x/y indexesxValues=(x-(_info.xMin+0.5*_info.pixelWidth))/_info.pixelWidthxIndexes=np.round(xValues)_xOffset=xValues-xIndexesif_info.yAtTop:yValues=((_info.yMax-0.5*_info.pixelWidth)-y)/abs(_info.pixelHeight)yIndexes=np.round(yValues)_yOffset=yValues-yIndexeselse:yValues=(y-(_info.yMin+0.5*_info.pixelWidth))/_info.pixelHeightyIndexes=np.round(yValues)_yOffset=-1*(yValues-yIndexes)# Calculate the starts and window sizexStarts=xIndexes-winRangeyStarts=yIndexes-winRangewindow=2*winRange+1_inBounds=xStarts>=0_inBounds=_inBounds&(yStarts>=0)_inBounds=_inBounds&(xStarts+window<=_info.xWinSize)_inBounds=_inBounds&(yStarts+window<=_info.yWinSize)# Read values_values=[]band=_src.GetRasterBand(1)forxi,yi,ibinzip(xStarts,yStarts,_inBounds):ifnotib:data=np.ones((window,window))*np.nanelse:# Open and read from rasterdata=band.ReadAsArray(xoff=xi,yoff=yi,win_xsize=window,win_ysize=window)if(_info.scale!=None)and(_info.scale!=1.0):data=data*_info.scaleif(_info.offset!=None)and(_info.offset!=0.0):data=data+_info.offset# Look for nodataif_info.noDataisnotNone:nodata=data==_info.noDataifnodata.any():ifnoDataOkay:# data will neaed to be a float type to represent a nodata valuedata=data.astype(np.float64)data[nodata]=np.nanelse:raiseGeoKitRasterError("No data values found in extractValues with 'noDataOkay' set to False")# flip if not in the 'flipped-y' orientationifnot_info.yAtTop:data=data[::-1,:]ifwinRange==0:# If winRange is 0, there's no need to return a 2D matrixdata=data[0][0]# Append to values_values.append(data)# check if not _inbounds, then replace values with nanforiinrange(len(_values)):ifnot_inBounds[i]:_values[i]=np.nan*np.ones_like(_values[i])# write _values, _inbounds and offsets into the overall container at the respective indicesfori,v,ib,x,yinzip(_inds,_values,_inBounds,_xOffset,_yOffset):values[i]=vinBounds[i]=ibxOffset[i]=xyOffset[i]=yifnp.isnan(values).any():# we still have NaN values leftmsg=f"WARNING: {np.isnan(values).sum()} of the given points (or extraction windows) exceed/s the source's limits. Values were replaced with nan."warnings.warn(msg,UserWarning)# Done!ifasSingle:# A single point was given, so return a single resultif_onlyValues:returnvalues[0]else:returnptValue(values[0],xOffset[0],yOffset[0],inBounds[0])else:if_onlyValues:returnnp.array(values)else:returnpd.DataFrame(dict(data=values,xOffset=xOffset,yOffset=yOffset,inBounds=inBounds),index=None,)
The scaling factor relating the units of the x & y dimensions to the z
dimension
* If factor is 'latlonToM', the x & y units are assumed to be degrees
(lat & lon) and the z units are assumed to be meters. A factor is then
computed for coordinates at the source's center.
* Example: If x,y units are meters and z units are feet, factor should
be 0.3048
defgradient(source,mode="total",factor=1,asMatrix=False,**kwargs):"""Calculate a raster's gradient and return as a new dataset or simply a matrix. Parameters ---------- source : Anything acceptable by loadRaster() The raster datasource mode : str; optional Determines the type of gradient to compute * Options are.... "total" : Calculates the absolute gradient as a ratio "slope" : Same as 'total' "north-south" : Calculates the "north-facing" gradient as a ratio where negative numbers indicate a south facing gradient "east-west" : Calculates the "east-facing" gradient as a ratio where negative numbers indicate a west facing gradient "aspect" : calculates the gradient's direction in radians (0 is east) "dir" : same as 'aspect' factor : numeric or 'latlonToM' The scaling factor relating the units of the x & y dimensions to the z dimension * If factor is 'latlonToM', the x & y units are assumed to be degrees (lat & lon) and the z units are assumed to be meters. A factor is then computed for coordinates at the source's center. * Example: If x,y units are meters and z units are feet, factor should be 0.3048 asMatrix : bool If True, makes the returned value a matrix If False, makes the returned value a raster dataset **kwargs : All extra key word arguments are passed on to a final call to 'createRaster' * Only useful when 'asMatrix' is True Returns ------- * If 'asMatrix' is True: numpy.ndarray * If 'asMatrix' is False: gdal.Dataset """# Make sure source is a sourcesource=loadRaster(source)# Check modeacceptable=["total","slope","north-south","east-west","dir","ew","ns","aspect",]ifmodenotinacceptable:raiseValueError("'mode' not understood. Must be one of: ",acceptable)# Get the factorsourceInfo=rasterInfo(source)iffactor=="latlonToM":latMid=(sourceInfo.yMax+sourceInfo.yMin)/2R_EARTH=6371000DEGtoRAD=np.pi/180yFactor=R_EARTH*DEGtoRAD# Get arc length in meters/DegreexFactor=R_EARTH*DEGtoRAD*np.cos(latMid*DEGtoRAD)# ditto...else:try:xFactor,yFactor=factorexcept:yFactor=factorxFactor=factor# Calculate gradientarr=extractMatrix(source)ifmodein["north-south","ns","total","slope","dir","aspect"]:ns=np.zeros(arr.shape)ns[1:-1,:]=(arr[2:,:]-arr[:-2,:])/(2*sourceInfo.dy*yFactor)ifmodein["north-south","ns"]:output=nsifmodein["east-west","total","slope","dir","aspect"]:ew=np.zeros(arr.shape)ew[:,1:-1]=(arr[:,:-2]-arr[:,2:])/(2*sourceInfo.dx*xFactor)ifmodein["east-west","ew"]:output=ewifmode=="total"ormode=="slope":output=np.sqrt(ns*ns+ew*ew)ifmode=="dir"ormode=="aspect":output=np.arctan2(ns,ew)# Done!ifasMatrix:returnoutputelse:returncreateRaster(bounds=sourceInfo.bounds,pixelWidth=sourceInfo.dx,pixelHeight=sourceInfo.dy,srs=sourceInfo.srs,data=output,**kwargs,)
defindexToCoord(yi:int|np.ndarray,xi:int|np.ndarray,source=None,asPoint:bool=False,bounds=None,dx=None,dy=None,yAtTop=True,srs=None,)->ogr.Geometry|tuple[int|int]|np.ndarray:"""Convert the index of a raster to coordinate values. Parameters ---------- xi : int The x index * a numpy array of ints is also acceptable yi : int The y index * a numpy array of ints is also acceptable source : Anything acceptable by loadRaster() The contentual raster datasource asPoint : bool Instruct program to return point geometries instead of x,y coordinates Returns ------- * If 'asPoint' is True: ogr.Geometry * If 'asPoint' is False: tuple -> (x,y) coordinates """ifsourceisnotNone:# Get source infoifnotisinstance(source,RasterInfo):source=rasterInfo(source)xMin=source.xMin# xMax = source.xMaxyMin=source.yMinyMax=source.yMaxdx=source.dxdy=source.dyyAtTop=source.yAtTopelse:try:xMin,yMin,xMax,yMax=boundsexcept:xMin,yMin,xMax,yMax=bounds.xyXY# Calculate coordinatesifyAtTop:x=xMin+dx*(xi+0.5)y=yMax-dy*(yi+0.5)else:x=xMin+dx*(xi+0.5)y=yMin+dy*(yi+0.5)# make the outputifasPoint:try:# maybe x and y are iterableoutput=[GEOM.point((xx,yy),srs=srs)forxx,yyinzip(x,y)]exceptTypeError:# x and y should be a single pointoutput=GEOM.point((x,y),srs=srs)else:output=np.column_stack([x,y])# Done!returnoutput
The raster datasource, can be a filepath, a raster dataset etc., see
RASTER.loadRaster() for details. Alternatively, a list of multiple
such raster datasources.
Coordinates for the points to extract
* All points must be in the same SRS
* !REMEMBER! For lat and lon coordinates, X is lon and Y is lat
(opposite of what you may think...)
The interpolation scheme to use
* options are...
"near" - Just gets the nearest value (this is default)
"linear-spline" - calculates a linear spline in between points
"cubic-spline" - calculates a cubic spline in between points
"average" - calculates average across a window
"func" - uses user-provided calculator
A user defined interpolation function
* Only utilized when 'mode' equals "func"
* The function must take three arguments in this order...
- A 2 dimensional data matrix
- A x-index-offset
- A y-index-offset
* See the example below for more information
The window range (in pixels) to extract the values centered around the
closest raster index to the indicated locations.
* A winRange of 0 will only extract the closest raster value
* A winRange of 1 will extract a window of shape (3,3)
* A winRange of 3 will extract a window of shape (7,7)
* Only utilized when 'mode' equals "func"
* All interpolation schemes have a predefined window range which is
appropriate to their use
- near -> 0
- linear-spline -> 2
- cubic-spline -> 4
- average -> 3
- func -> 3
Returns:
* If only a single location is given: numeric
–
namedtuple -> (data : The extracted data at the location
xOffset : The X index distance from the location to the
center of the closest raster pixel
yOffset : The Y index distance from the location to the
center of the closest raster pixel
inBounds: Flag for whether or not the location is within
The raster's bounds
)
* If Multiple locations are given: numpy.ndrray -> (N,)
–
where N is the number of locations
Example:
"Interpolate" according to the median value in a 5x5 window
definterpolateValues(source:load_raster_input,points:tuple[float,float]|ogr.Geometry|list[tuple[float,float]]|list[ogr.Geometry]|Location|LocationSet,pointSRS:srs_input="latlon",mode:Literal["near","linear-spline","cubic-spline","average","func"]="near",func:Callable|None=None,winRange:int|None=None,**kwargs,):"""Interpolates the value of a raster at a given point or collection of points. Supports various interpolation schemes: 'near', 'linear-spline', 'cubic-spline', 'average', or user-defined Parameters ---------- source : Anything acceptable by loadRaster() or list The raster datasource, can be a filepath, a raster dataset etc., see RASTER.loadRaster() for details. Alternatively, a list of multiple such raster datasources. points : (X,Y) or [(X1,Y1), (X2,Y2), ...] or Location or LocationSet() Coordinates for the points to extract * All points must be in the same SRS * !REMEMBER! For lat and lon coordinates, X is lon and Y is lat (opposite of what you may think...) pointSRS : Anything acceptable to geokit.srs.loadSRS(); optional The srs of the point to create * If not given, longitude/latitude is assumed * Only useful when 'points' is not a LocationSet mode : str; optional The interpolation scheme to use * options are... "near" - Just gets the nearest value (this is default) "linear-spline" - calculates a linear spline in between points "cubic-spline" - calculates a cubic spline in between points "average" - calculates average across a window "func" - uses user-provided calculator func - function A user defined interpolation function * Only utilized when 'mode' equals "func" * The function must take three arguments in this order... - A 2 dimensional data matrix - A x-index-offset - A y-index-offset * See the example below for more information winRange : int The window range (in pixels) to extract the values centered around the closest raster index to the indicated locations. * A winRange of 0 will only extract the closest raster value * A winRange of 1 will extract a window of shape (3,3) * A winRange of 3 will extract a window of shape (7,7) * Only utilized when 'mode' equals "func" * All interpolation schemes have a predefined window range which is appropriate to their use - near -> 0 - linear-spline -> 2 - cubic-spline -> 4 - average -> 3 - func -> 3 Returns ------- * If only a single location is given: numeric namedtuple -> (data : The extracted data at the location xOffset : The X index distance from the location to the center of the closest raster pixel yOffset : The Y index distance from the location to the center of the closest raster pixel inBounds: Flag for whether or not the location is within The raster's bounds ) * If Multiple locations are given: numpy.ndrray -> (N,) - where N is the number of locations Example: -------- "Interpolate" according to the median value in a 5x5 window >>> def medianFinder( data, xOff, yOff ): >>> return numpy.median(data) >>> >>> result = interpolateValues( <source>, <points>, mode='func', >>> func=medianFinder, winRange=2) """# Determine what the user probably wants as an outputifisinstance(points,(tuple,ogr.Geometry,Location)):asSingle=True# make points a list of length 1 so that the rest works (will be unpacked later)elifisinstance(points,list):iflen(points)==1:asSingle=Trueelse:asSingle=Falseelifisinstance(points,LocationSet):ifpoints.count==1:asSingle=Trueelse:asSingle=Falseelse:# Assume points is already an iterable of some sortraiseGeoKitRasterError("The following datatype has been provided as point but something different was expected: "+str(type(points)))# Do interpolationifmode=="near":# Simple get the nearest valuewin=0ifwinRangeisNoneelsewinRangevalues=extractValues(source,points,pointSRS=pointSRS,winRange=win,_onlyValues=True,**kwargs)ifasSingleisTrue:result=[values]else:result=valueselifmode=="linear-spline":# use a spline interpolation scheme# setup inputswin=2ifwinRangeisNoneelsewinRangex=np.linspace(-1*win,win,2*win+1)y=np.linspace(-1*win,win,2*win+1)# get raw datavalues=extractValues(source,points,pointSRS=pointSRS,winRange=win,**kwargs)ifasSingleisTrue:assertisinstance(values,ptValue)data_frame_values=pd.DataFrame(dict(dict(data=[values.data],xOffset=values.xOffset,yOffset=values.yOffset,inBounds=values.inBounds),),index=None,)else:data_frame_values=values# Calculate interpolated valuesresult=[]forvindata_frame_values.itertuples(index=False):rbs=RectBivariateSpline(y,x,v.data,kx=1,ky=1)result.append(rbs(v.yOffset,v.xOffset)[0][0])elifmode=="cubic-spline":# use a spline interpolation scheme# setup inputswin=4ifwinRangeisNoneelsewinRangex=np.linspace(-1*win,win,2*win+1)y=np.linspace(-1*win,win,2*win+1)# Get raw datavalues=extractValues(source,points,pointSRS=pointSRS,winRange=win,**kwargs)ifasSingleisTrue:assertisinstance(values,ptValue)data_frame_values=pd.DataFrame(dict(dict(data=[values.data],xOffset=values.xOffset,yOffset=values.yOffset,inBounds=values.inBounds),),index=None,)else:data_frame_values=values# Calculate interpolated valuesresult=[]forvindata_frame_values.itertuples(index=False):rbs=RectBivariateSpline(y,x,v.data)result.append(rbs(v.yOffset,v.xOffset)[0][0])elifmode=="average":# Get the average in a windowwin=3ifwinRangeisNoneelsewinRangevalues=extractValues(source,points,pointSRS=pointSRS,winRange=win,**kwargs)ifasSingleisTrue:assertisinstance(values,ptValue)data_frame_values=pd.DataFrame(dict(dict(data=[values.data],xOffset=values.xOffset,yOffset=values.yOffset,inBounds=values.inBounds),),index=None,)else:data_frame_values=valuesresult=[]forvindata_frame_values.itertuples(index=False):result.append(v.data.mean())elifmode=="func":# Use a general function processoriffuncisNone:raiseGeoKitRasterError("'func' mode chosen, but no func kwargs was given")win=3ifwinRangeisNoneelsewinRangevalues=extractValues(source,points,pointSRS=pointSRS,winRange=win,**kwargs)ifasSingleisTrue:assertisinstance(values,ptValue)data_frame_values=pd.DataFrame(dict(dict(data=[values.data],xOffset=values.xOffset,yOffset=values.yOffset,inBounds=values.inBounds),),index=None,)else:data_frame_values=valuesresult=[]forvindata_frame_values.itertuples(index=False):result.append(func(v.data,v.xOffset,v.yOffset))else:raiseGeoKitRasterError("Interpolation mode not understood: ",mode)# Done!ifasSingle:returnresult[0]else:returnnp.array(result)
defloadRaster(source:load_raster_input,mode=0)->gdal.Dataset:""" Load a raster dataset from a path to a file on disc. Parameters ---------- source : str or gdal.Dataset * If a string is given, it is assumed as a path to a raster file on disc * If a gdal.Dataset is given, it is assumed to already be an open raster and is returned immediately Returns ------- gdal.Dataset """ifisinstance(source,pathlib.Path):source=str(source)ifisinstance(source,str):ds=gdal.Open(source,mode)else:ds=sourceifdsisNone:raiseGeoKitRasterError("Could not load input dataSource: ",str(source))returnds
Process all pixels in a raster according to a given function. The boundaries
of the resulting raster can be changed as long as the new boundaries are within
the scope of the original raster, but the resolution cannot.
The function performing the mutation of the raster's data
* The function will take single argument (a 2D numpy.ndarray)
* The function must return a numpy.ndarray of the same size as the input
* The return type must also be containable within a Float32 (int and
boolean is okay)
* See example below for more info
The boundary to clip the raster to before mutating
* If given as an Extent, the extent is always cast to the source's native
srs before mutating
* If given as a tuple, (xMin, yMin, xMax, yMax) is expected
- Units must be in the srs specified by 'boundsSRS'
* This boundary must fit within the boundary of the rasters source
* The boundary is always fitted to the source's grid, so the returned
values do not necessarily match to the boundary which is provided
If True, then before mutating the matrix extracted from the source will have
pixels equal to its 'noData' value converted to numpy.nan
* Data type will always result in a float, so be careful with large
matrices
A path to an output file
* If output is None, the raster will be created in memory and a dataset
handle will be returned
* If output is given, the raster will be written to disk and nothing will
be returned
(Type, str, or numpy-dtype; optional, default:
None
)
–
If given, forces the processed data to be a particular datatype
* Example
- A python numeric type such as bool, int, or float
- A Numpy datatype such as numpy.uint8 or numpy.float64
- a String such as "Byte", "UInt16", or "Double"
defmutateRaster(source:load_raster_input,processor:Callable|None=None,bounds:tuple[numeric,numeric,numeric,numeric]|None=None,boundsSRS:srs_input="latlon",autocorrect:bool=False,output:str|None=None,dtype:geokit_c_data_types_literal|None=None,**create_raster_kwargs,):"""Process all pixels in a raster according to a given function. The boundaries of the resulting raster can be changed as long as the new boundaries are within the scope of the original raster, but the resolution cannot. Parameters ---------- source : Anything acceptable by loadRaster() The raster datasource processor: function; optional The function performing the mutation of the raster's data * The function will take single argument (a 2D numpy.ndarray) * The function must return a numpy.ndarray of the same size as the input * The return type must also be containable within a Float32 (int and boolean is okay) * See example below for more info bounds: tuple or Extent The boundary to clip the raster to before mutating * If given as an Extent, the extent is always cast to the source's native srs before mutating * If given as a tuple, (xMin, yMin, xMax, yMax) is expected - Units must be in the srs specified by 'boundsSRS' * This boundary must fit within the boundary of the rasters source * The boundary is always fitted to the source's grid, so the returned values do not necessarily match to the boundary which is provided boundsSRS: Anything acceptable to geokit.srs.loadSRS(); optional The srs of the 'bounds' argument * This is ignored if the 'bounds' argument is an Extent object or is None autocorrect : bool; optional If True, then before mutating the matrix extracted from the source will have pixels equal to its 'noData' value converted to numpy.nan * Data type will always result in a float, so be careful with large matrices output : str; optional A path to an output file * If output is None, the raster will be created in memory and a dataset handle will be returned * If output is given, the raster will be written to disk and nothing will be returned dtype : Type, str, or numpy-dtype; optional If given, forces the processed data to be a particular datatype * Example - A python numeric type such as bool, int, or float - A Numpy datatype such as numpy.uint8 or numpy.float64 - a String such as "Byte", "UInt16", or "Double" **kwargs: * All kwargs are passed on to a call to createRaster() Example: -------- If you wanted to assign suitability factors based on a raster containing integer identifiers >>> def calcSuitability( data ): >>> # create an output matrix >>> outputMatrix = numpy.zeros( data.shape ) >>> >>> # do the processing >>> outputMatrix[ data == 1 ] = 0.1 >>> outputMatrix[ data == 2 ] = 0.2 >>> outputMatrix[ data == 10] = 0.4 >>> outputMatrix[ np.logical_and(data > 15, data < 20) ] = 0.5 >>> >>> # return the output matrix >>> return outputMatrix >>> >>> result = processRaster( <source-path>, processor=calcSuitability ) """# open the dataset and get SRSworkingDS=loadRaster(source)# Get ds infodsInfo=rasterInfo(workingDS)# Read data into arraysourceData,bounds=extractMatrix(source,bounds=bounds,boundsSRS=boundsSRS,autocorrect=autocorrect,returnBounds=True,)workingExtent=dsInfo.boundsif(boundsisNone)elsebounds# Perform processingifprocessor:processedData=processor(sourceData)else:processedData=sourceData# Ensure returned matrix is okayifprocessedData.shape!=sourceData.shape:raiseGeoKitRasterError("Processed matrix does not have the correct shape \nIs {0}\nShould be {1}",format(processedData.shape,sourceData.shape),)delsourceDatalist_of_numbers=[processedData.min(),processedData.max()]minimum_gdal_type_list=[str(processedData.dtype)]ifisinstance(dtype,str):minimum_gdal_type_list.append(dtype)gdal_data_string=MinimumCDataTypeHandler.get_valid_gdal_data_type_as_string(list_of_numbers=list_of_numbers,minimum_gdal_type_list=minimum_gdal_type_list)# Create an output rasterifoutputisNone:returnUTIL.quickRaster(dy=dsInfo.dy,dx=dsInfo.dx,bounds=workingExtent,dtype=gdal_data_string,srs=dsInfo.srs,data=processedData,**create_raster_kwargs,)else:createRaster(pixelHeight=dsInfo.dy,pixelWidth=dsInfo.dx,bounds=workingExtent,srs=dsInfo.srs,data=processedData,output=output,dtype=gdal_data_string,**create_raster_kwargs,)returnoutput
If True, shrink all geoms by a tiny amount in order to avoid geometry
overlapping issues
* The total amount shrunk should be very very small
* Generally this should be left as True unless it is ABSOLUTELY
necessary to maintain the same area
Returns:
pandas.DataFrame -> With columns:
–
'geom' -> The contiguous-valued geometries
'value' -> The value for each geometry
defpolygonizeRaster(source,srs=None,flat=False,shrink=True):"""Polygonize a raster or an integer-valued data matrix. Parameters ---------- source : Anything acceptable by loadRaster() The raster datasource to polygonize * The Datatype MUST be of boolean of integer type srs : Anything acceptable to geokit.srs.loadSRS(); optional The srs of the polygons to create * If not given, the raster's internal srs is assumed flat : bool If True, flattens the resulting geometries which share a contiguous value into a single geometry object shrink : bool If True, shrink all geoms by a tiny amount in order to avoid geometry overlapping issues * The total amount shrunk should be very very small * Generally this should be left as True unless it is ABSOLUTELY necessary to maintain the same area Returns ------- pandas.DataFrame -> With columns: 'geom' -> The contiguous-valued geometries 'value' -> The value for each geometry """# Load the information we will needsource=loadRaster(source)band=source.GetRasterBand(1)maskBand=band.GetMaskBand()ifsrsisNone:srs=SRS.loadSRS(source.GetProjectionRef())# Do polygonizevecDS=gdal.GetDriverByName("Memory").Create("",0,0,0,gdal.GDT_Unknown)vecLyr=vecDS.CreateLayer("mem",srs=srs)# vecDS = gdal.GetDriverByName("ESRI Shapefile").Create("deleteme.tif", 0, 0, 0, gdal.GDT_Unknown )# vecLyr = vecDS.CreateLayer("layer",srs=srs)vecField=ogr.FieldDefn("DN",ogr.OFTInteger)vecLyr.CreateField(vecField)# Polygonize geometryresult=gdal.Polygonize(band,maskBand,vecLyr,0)ifresult!=0:raiseGeoKitGeomError("Failed to polygonize geometry")# Check the geomsftrN=vecLyr.GetFeatureCount()ifftrN==0:# raise GlaesError("No features in created in temporary layer")msg="No features in created in temporary layer"warnings.warn(msg,UserWarning)return# Extract geometries and valuesgeoms=[]rid=[]foriinrange(ftrN):ftr=vecLyr.GetFeature(i)geoms.append(ftr.GetGeometryRef().Clone())rid.append(ftr.items()["DN"])# Do shrink, maybeifshrink:# Compute shrink factorshrinkFactor=-0.00001geoms=[g.Buffer(float(shrinkFactor))forgingeoms]# Do flatten, maybeifflat:geoms=np.array(geoms)rid=np.array(rid)finalGeoms=[]finalRID=[]for_ridinset(rid):smallGeomSet=geoms[rid==_rid]finalGeoms.append(GEOM.flatten(smallGeomSet)iflen(smallGeomSet)>1elsesmallGeomSet[0])finalRID.append(_rid)else:finalGeoms=geomsfinalRID=rid# CleanupvecLyr=NonevecDS=NonemaskBand=None# Done!returnpd.DataFrame(dict(geom=finalGeoms,value=finalRID))
Returns the raster cell number for one or multiple points defined by geometry or lon/lat. Cell numeration
starting in the top left corner cell of the raster with (0,0). Cells with (-1,-1) are out of bounds.
Args:
points (osgeo.ogr.Geometry, tuple, iterable): Can be an osgeo.ogr.Geometry point or an iterable thereof, else a (lon, lat) tuple (in EPSG:4326) or an iterable thereof.
source (gdal.Dataset, str, optional): A gdal.Dataset type raster or a str formatted path to a raster file. Defaults to None.
bounds (tuple, optional): Raster boundaries in EPSG:4326 in the form of (minX, minY, maxX, maxY). Defaults to None.
cellWidth (int, float, optional): The cell width in EPSG:4326 units. Defaults to None.
cellHeight (int, float, optional): The cell height in EPSG:4326 units. Defaults to None.
NOTE: If source is given, all of the others must be None, else they must be given.
Returns:
tuple or iterable: tuple with (X, Y) cell No or an iterable thereof if multiple points were given.
–
defrasterCellNo(points,source=None,bounds=None,cellWidth=None,cellHeight=None):""" Returns the raster cell number for one or multiple points defined by geometry or lon/lat. Cell numeration starting in the top left corner cell of the raster with (0,0). Cells with (-1,-1) are out of bounds. Args: points (osgeo.ogr.Geometry, tuple, iterable): Can be an osgeo.ogr.Geometry point or an iterable thereof, else a (lon, lat) tuple (in EPSG:4326) or an iterable thereof. source (gdal.Dataset, str, optional): A gdal.Dataset type raster or a str formatted path to a raster file. Defaults to None. bounds (tuple, optional): Raster boundaries in EPSG:4326 in the form of (minX, minY, maxX, maxY). Defaults to None. cellWidth (int, float, optional): The cell width in EPSG:4326 units. Defaults to None. cellHeight (int, float, optional): The cell height in EPSG:4326 units. Defaults to None. NOTE: If source is given, all of the others must be None, else they must be given. Returns ------- tuple or iterable: tuple with (X, Y) cell No or an iterable thereof if multiple points were given. """# check and preprocess points inputsifisinstance(points,ogr.Geometry):points=[points]elifisinstance(points,tuple)andlen(points)==2:points=[points]asserthasattr(points,"__iter__"),("points must be an osgeo.ogr.Geometry POINT object, a tuple of (lon, lat) or an iterable of any of them.")ifisinstance(points[0],ogr.Geometry):ifnotall([p.GetGeometryName()=="POINT"forpinpoints]):raiseTypeError("Only POINT geometries allowed")ifnotall([p.GetSpatialReference().IsSame(SRS.loadSRS(4326))forpinpoints]):raiseValueError("SRS of all points must be EPSG:4326")# convert to lons and latspoints=[(p.GetX(),p.GetY())forpinpoints]else:assertall([isinstance(x,tuple)andlen(x)==2andall([isinstance(_x,(int,float))for_xinx])forxinpoints]),("All entries in points must be (lon, lat) tuples with length of 2 and int or float coordinates if given as tuples or iterable thereof.")# get bounds, cellWidth and cellHeight from the inputsifsourceisnotNone:ifnot(boundsisNoneandcellWidthisNoneandcellHeightisNone):raiseValueError("bounds, cellWidth and cellHeight must be None when source raster is given.")ifisinstance(source,str)andnotos.path.isfile(source):raiseFileNotFoundError("source must be an existing raster file if given as string.")try:sourceRasterInfo=rasterInfo(source)bounds=sourceRasterInfo.boundscellWidth=sourceRasterInfo.pixelWidthcellHeight=sourceRasterInfo.pixelHeightrasterWidth=sourceRasterInfo.xWinSizerasterHeight=sourceRasterInfo.yWinSizeexcept:raiseTypeError("source must be path to a gdal.Dataset raster or a gdal.Dataset raster itself if not None.")ifnotsourceRasterInfo.srs.IsSame(SRS.loadSRS(4326)):raiseValueError("raster source must be in epsg:4326")else:ifboundsisNoneorcellWidthisNoneorcellHeightisNone:raiseValueError("If source is None, bounds, cellWidth and cellHeight must be given.")assert(isinstance(bounds,tuple)andlen(bounds)==4andall([isinstance(x,(int,float))forxinbounds])andbounds[0]<bounds[2]andbounds[1]<bounds[3]),"bounds must be a tuple of length = 4 with int or float entries like such: (minX, minY, maxX, maxY)"assertisinstance(cellHeight,(int,float)),"cellHeight must be an int or float."assertisinstance(cellWidth,(int,float)),"cellWidth must be an int or float."# calculate the raster width and height in Nos of cellsrasterWidth=(bounds[2]-bounds[0])/cellWidthrasterHeight=(bounds[3]-bounds[1])/cellHeightassertnp.isclose(rasterWidth%cellWidth,0)ornp.isclose(rasterWidth%cellWidth,cellWidth),(f"rasterWidth {rasterWidth} is not a multiple of cellWidth {cellWidth}")assertnp.isclose(rasterHeight%cellHeight,0)ornp.isclose(rasterHeight%cellHeight,cellHeight),(f"rasterHeight {rasterHeight} is not a multiple of cellHeight {cellHeight}")# calculate the cell NoscellNos=list()outOfBounds=Falseforpointinpoints:X=int(np.floor((point[0]-bounds[0])/cellWidth))Y=int(np.floor((bounds[3]-point[1])/cellHeight))# if any dimension out of boundsif(X<0orX>=rasterWidth)or(Y<0orY>=rasterHeight):X=Y=-1outOfBounds=True# append to cell No listcellNos.append((X,Y))ifoutOfBounds:warnings.warn("points contain out-of-bounds locations!")# return cellNos as tuple for single point, else as list of tuplesiflen(cellNos)==1:returncellNos[0]else:returncellNos
If True, the maximum and minimum value of the raster data are computed.
This is computationally expensive for large rasters and repeated calls of
this function on the same raster should be avoided.
Returns:
namedtuple -> ( srs: The spatial reference system (as an OGR object)
–
dtype: The datatype
flipY: A flag which indicates that the raster starts at the
'bottom' as opposed to at the 'top'
bounds: The (xMin, yMin, xMax, and yMax) values as a tuple
xMin: The minimal X boundary
yMin:The minimal Y boundary
xMax:The maximal X boundary
yMax: The maximal Y boundary
pixelWidth: The raster's pixelWidth
pixelHeight: The raster's pixelHeight
dx:The raster's pixelWidth
dy: The raster's pixelHeight
noData: The noData value used by the raster
scale: The scale value used by the raster
offset: The offset value used by the raster
xWinSize: The width of the raster is pixels
yWinSize: The height of the raster is pixels
meta: The raster's meta data )
defrasterInfo(sourceDS:load_raster_input,compute_statistics:bool=False)->RasterInfo:"""Returns a named tuple containing information relating to the input raster. Parameters ---------- sourceDS : Anything acceptable by loadRaster() The raster datasource compute_statistics : bool; optional If True, the maximum and minimum value of the raster data are computed. This is computationally expensive for large rasters and repeated calls of this function on the same raster should be avoided. Returns ------- namedtuple -> ( srs: The spatial reference system (as an OGR object) dtype: The datatype flipY: A flag which indicates that the raster starts at the 'bottom' as opposed to at the 'top' bounds: The (xMin, yMin, xMax, and yMax) values as a tuple xMin: The minimal X boundary yMin:The minimal Y boundary xMax:The maximal X boundary yMax: The maximal Y boundary pixelWidth: The raster's pixelWidth pixelHeight: The raster's pixelHeight dx:The raster's pixelWidth dy: The raster's pixelHeight noData: The noData value used by the raster scale: The scale value used by the raster offset: The offset value used by the raster xWinSize: The width of the raster is pixels yWinSize: The height of the raster is pixels meta: The raster's meta data ) """output={}sourceDS=loadRaster(sourceDS)# get srsifsourceDS.GetProjectionRef()=="":# return None directly if raster has no srssrs=Noneelse:# generate an srs object if we have srs informationsrs=SRS.loadSRS(sourceDS.GetProjectionRef())output["srs"]=srs# get extent and resolutionsourceBand=sourceDS.GetRasterBand(1)# Is required to get the maximum and minimum value of the raster dataoutput["dtype"]=sourceBand.DataTypeoutput["noData"]=sourceBand.GetNoDataValue()output["scale"]=sourceBand.GetScale()output["offset"]=sourceBand.GetOffset()ifcompute_statisticsisTrue:try:sourceBand.ComputeStatistics(0)maximum_value=sourceBand.GetMaximum()minimum_value=sourceBand.GetMinimum()output["maximum_value"]=maximum_valueoutput["minimum_value"]=minimum_valueexcept:output["maximum_value"]=output["noData"]output["minimum_value"]=output["noData"]else:output["maximum_value"]=Noneoutput["minimum_value"]=NonexSize=sourceBand.XSizeySize=sourceBand.YSizexOrigin,dx,_,yOrigin,_,dy=sourceDS.GetGeoTransform()xMin=xOriginxMax=xOrigin+dx*xSizeifdy<0:yMax=yOriginyMin=yMax+dy*ySizedy=-1*dyoutput["flipY"]=Trueoutput["yAtTop"]=Trueelse:yMin=yOriginyMax=yOrigin+dy*ySizeoutput["flipY"]=Falseoutput["yAtTop"]=Falseoutput["pixelWidth"]=dxoutput["pixelHeight"]=dyoutput["dx"]=dxoutput["dy"]=dyoutput["xMin"]=xMinoutput["xMax"]=xMaxoutput["yMin"]=yMinoutput["yMax"]=yMaxoutput["xWinSize"]=xSizeoutput["yWinSize"]=ySizeoutput["bounds"]=(xMin,yMin,xMax,yMax)output["meta"]=sourceDS.GetMetadata_Dict()output["source"]=sourceDS.GetDescription()output["data_type_name_str"]=gdal.GetDataTypeName(output["dtype"])# clean updelsourceBand,sourceDSraster_info=RasterInfo(**output)returnraster_info
defrasterStats(source,cutline=None,ignoreValue=None,**kwargs):"""Compute basic statistics of the values contained in a raster dataset. Parameters ---------- source : Anything acceptable by loadRaster() The raster datasource cutline : ogr.Geometry; optional The geometry over which to cut out the raster's data * Must be a Polygon or MultiPolygon ignoreValue : numeric A value to ignore when computing the statistics * If the raster source has a 'no Data' value, it is automatically ignored **kwargs * All kwargs are passed on to warp() when 'geom' is given * See gdal.WarpOptions for more details * For example, 'allTouched' may be useful Returns ------- Results from a call to scipy.stats.describe """fromscipy.statsimportdescribesource=loadRaster(source)# Get the matrix to calculate overifcutlineisnotNone:source=warp(source,cutline=cutline,noData=ignoreValue,**kwargs)rawData=extractMatrix(source)dataInfo=rasterInfo(source)# exclude nodata and ignore valuessel=np.ones(rawData.shape,dtype="bool")ifignoreValueisnotNone:np.logical_and(rawData!=ignoreValue,sel,sel)ifdataInfo.noDataisnotNone:np.logical_and(rawData!=dataInfo.noData,sel,sel)# compute statisticsdata=rawData[sel].flatten()returndescribe(data)
defsaveRasterAsTif(source:gdal.Dataset,output:str,**kwargs):"""Write a osgeo.gdal.Dataset in memory to a GeoTiff file to disk. Parameters ---------- source : osgeo.gdal.Dataset output : str A path to an output file Returns ------- str Path to the saved file on disk. """# assert os.path.isdir(os.path.dirname(output)), 'Output folder does not exist!'assertoutput.split(".")[-1]in["tif","tiff",],"Wrong type specified, use *.tif or *.tiff"sourceInfo=rasterInfo(source)data=extractMatrix(source)returncreateRaster(bounds=sourceInfo.bounds,pixelWidth=sourceInfo.dx,pixelHeight=sourceInfo.dy,noData=sourceInfo.noData,dtype=sourceInfo.data_type_name_str,srs=sourceInfo.srs,data=data,output=output,**kwargs,)
Removes raster polygons smaller than a provided threshold size (in pixels) and
replaces them with the pixel value of the largest neighbour polygon.
It is useful if you have a large amount of small areas on your raster map.
Returns:
* If 'output' is None : gdal.Dataset
–
* If 'output' is a string : The path to the output is returned (for easy opening)
–
defsieve(source,threshold:int=100,connectedness:Literal[4,8]=8,mask="none",quiet_flag:bool=False,output:str|None=None,**kwargs,)->gdal.Dataset|str:""" Removes raster polygons smaller than a provided threshold size (in pixels) and replaces them with the pixel value of the largest neighbour polygon. It is useful if you have a large amount of small areas on your raster map. Returns ------- * If 'output' is None : gdal.Dataset * If 'output' is a string : The path to the output is returned (for easy opening) Parameters ---------- source : Anything acceptable by loadRaster() threshold (int): minimum polygon size (number of pixels) to retain. connectedness (int): either 4 indicating that diagonal pixels are not considered directly adjacent for polygon membership purposes or 8 indicating they are. mask (str): 'none' or 'default'. An optional mask band. All pixels in the mask band with a value other than zero will be considered suitable for inclusion in polygons. quiet_flag (bool): 0 or 1. Callback for reporting algorithm progress output : str; optional The path on disk where the new raster should be created **kwargs: * All kwargs are passed on to SieveFilter() * See gdal.SieveFilter for more details """gdal.AllRegister()try:gdal.SieveFilterexcept:print("")print('gdal.SieveFilter() not available. You are likely using "old gen"')print("bindings or an older version of the next gen bindings.")print("")### Open source file# if output is None:# src_ds = gdal.Open( source, gdal.GA_Update )# else:# src_ds = gdal.Open( source, gdal.GA_ReadOnly )src_ds=loadRaster(source)ifsrc_dsisNone:print("Unable to open %s "%source)srcband=src_ds.GetRasterBand(1)ifmask=="default":maskband=srcband.GetMaskBand()elifmask=="none":maskband=Noneelse:mask_ds=loadRaster(mask)maskband=mask_ds.GetRasterBand(1).GetMaskBand()### Create output file if one is specified.ifoutputisnotNone:format="GTiff"driver=gdal.GetDriverByName(format)dst_ds=driver.Create(output,src_ds.RasterXSize,src_ds.RasterYSize,1,srcband.DataType,COMPRESSION_OPTION,)# ["COMPRESS=LZW"]else:format="Mem"driver=gdal.GetDriverByName(format)# create a raster in memorydst_ds=driver.Create("",src_ds.RasterXSize,src_ds.RasterYSize,1,srcband.DataType,COMPRESSION_OPTION,)# ["COMPRESS=LZW"]wkt=src_ds.GetProjection()ifwkt!="":dst_ds.SetProjection(wkt)dst_ds.SetGeoTransform(src_ds.GetGeoTransform())dstband=dst_ds.GetRasterBand(1)ifquiet_flag:prog_func=Noneelse:prog_func=gdal.TermProgress_nocbresult=gdal.SieveFilter(srcband,maskband,dstband,threshold,connectedness,callback=prog_func,**kwargs,)dst_ds.FlushCache()# Return raster if in memoryifoutputisNone:returndst_ds# Return output path to raster if on diskelse:src_ds=Nonedst_ds=Nonemask_ds=Nonereturnoutput
The resampling algorithm to use when translating pixel values
* Knowing which option to use can have significant impacts!
* Options are: near , bilinear, cubic,
cubicspline, lanczos, average, rms, mode,
max, min, med, Q1, Q3, sum
The cutline to limit the drawn data too
* If a string is given, it must be a path to a vector file
* Values outside of the cutline are given the value 'cutlineFillValue'
* Requires a warp
(Type, str, or numpy-dtype; optional, default:
None
)
–
If given, forces the processed data to be a particular datatype
* Only required if this value should be changed
* Example
- A python numeric type such as bool, int, or float
- A Numpy datatype such as numpy.uint8 or numpy.float64
- a String such as "Byte", "UInt16", or "Double"
All keyword arguments are passed on to a call to gdal.WarpOptions
Use these to fine-tune the warping procedure
Key Options are (from gdal.WarpOptions):
format --- output format ("GTiff", etc...)
targetAlignedPixels --- whether to force output bounds to be multiple
of output resolution
workingType --- working type (gdal.GDT_Byte, etc...)
warpMemoryLimit --- size of working buffer in bytes
creationOptions --- list of creation options
srcNodata --- source nodata value(s)
dstNodata --- output nodata value(s)
multithread --- whether to multithread computation and I/O operations
cutlineWhere --- cutline WHERE clause
cropToCutline --- whether to use cutline extent for output bounds
setColorInterpretation --- whether to force color interpretation of
input bands to output bands
Returns:
* If 'output' is None: gdal.Dataset
–
* If 'output' is a string: The path to the output is returned (for easy opening)
–
defwarp(source:load_raster_input,resampleAlg:Literal["near","bilinear","cubic","cubicspline","lanczos","average","rms","mode","max","min","med","Q1","Q3","sum",]="bilinear",cutline:str|ogr.Geometry|None=None,output:str|pathlib.Path|None=None,pixelHeight:numeric|None=None,pixelWidth:numeric|None=None,srs:srs_input|None=None,bounds:tuple[numeric,numeric,numeric,numeric]|None=None,dtype:None|geokit_c_data_types_literal=None,noData:numeric|None=None,overwrite:bool=True,meta:None|dict[str,str]=None,**kwargs,)->gdal.Dataset|str:"""Warps a given raster source to another context. * Can be used to 'warp' a raster in memory to a raster on disk Note: ----- Unless manually altered as keyword arguments, the gdal.Warp options 'targetAlignedPixels' and 'copyMetadata' are both set to True Parameters ---------- source : Anything acceptable by loadRaster() The raster datasource to draw srs : Anything acceptable to geokit.srs.loadSRS(); optional The srs of the resulting raster * If not given, the raster's internal srs is assumed resampleAlg : str; optional The resampling algorithm to use when translating pixel values * Knowing which option to use can have significant impacts! * Options are: near , bilinear, cubic, cubicspline, lanczos, average, rms, mode, max, min, med, Q1, Q3, sum cutline : str or ogr.Geometry; optional The cutline to limit the drawn data too * If a string is given, it must be a path to a vector file * Values outside of the cutline are given the value 'cutlineFillValue' * Requires a warp output : str; pathlib.Path optional The path on disk where the new raster should be created pixelHeight : numeric; optional The pixel height (y-resolution) of the output raster * Only required if this value should be changed pixelWidth : numeric; optional The pixel width (x-resolution) of the output raster * Only required if this value should be changed bounds : tuple; optional The (xMin, yMin, xMax, yMax) limits of the output raster * Only required if this value should be changed dtype : Type, str, or numpy-dtype; optional If given, forces the processed data to be a particular datatype * Only required if this value should be changed * Example - A python numeric type such as bool, int, or float - A Numpy datatype such as numpy.uint8 or numpy.float64 - a String such as "Byte", "UInt16", or "Double" noData : numeric; optional Replaces all previous noData values with this value in the output raster. meta: dict; optional: contains a key value pair that is passed to the output gdal.dataset using the SetMetadataItem method. **kwargs: * All keyword arguments are passed on to a call to gdal.WarpOptions * Use these to fine-tune the warping procedure * Key Options are (from gdal.WarpOptions): format --- output format ("GTiff", etc...) targetAlignedPixels --- whether to force output bounds to be multiple of output resolution workingType --- working type (gdal.GDT_Byte, etc...) warpMemoryLimit --- size of working buffer in bytes creationOptions --- list of creation options srcNodata --- source nodata value(s) dstNodata --- output nodata value(s) multithread --- whether to multithread computation and I/O operations cutlineWhere --- cutline WHERE clause cropToCutline --- whether to use cutline extent for output bounds setColorInterpretation --- whether to force color interpretation of input bands to output bands Returns ------- * If 'output' is None: gdal.Dataset * If 'output' is a string: The path to the output is returned (for easy opening) """# open source and get infosource=loadRaster(source)dsInfo=rasterInfo(sourceDS=source,compute_statistics=True)ifdsInfo.scale!=1.0ordsInfo.offset!=0.0:isAdjusted=Trueelse:isAdjusted=False# Handle potentially missing argumentsifsrsisnotNone:srs=SRS.loadSRS(srs)ifsrsisNone:srs=dsInfo.srssrsOkay=Trueelse:ifsrs.IsSame(dsInfo.srs):srsOkay=Trueelse:srsOkay=FalseifboundsisNone:ifsrsOkay:bounds=dsInfo.boundselse:bounds=GEOM.boundsToBounds(dsInfo.bounds,dsInfo.srs,srs)ifpixelHeightisNone:ifsrsOkay:pixelHeight=dsInfo.dyelse:pixelHeight=(bounds[3]-bounds[1])/(dsInfo.yWinSize*1.1)ifpixelWidthisNone:ifsrsOkay:pixelWidth=dsInfo.dxelse:pixelWidth=(bounds[2]-bounds[0])/(dsInfo.xWinSize*1.1)bounds=UTIL.fitBoundsTo(bounds,pixelWidth,pixelHeight)ifnoDataisNone:noDataRead=dsInfo.noDataelse:noDataRead=noDatalist_of_numbers=[]ifisinstance(noDataRead,(numeric,bool)):list_of_numbers.append(noDataRead)elifnoDataReadisNone:passelse:raiseGeoKitRasterError("noData must be a numeric or boolean value but got: %s"%str(type(noDataRead)))list_of_datatypes=[]ifisinstance(dtype,str):list_of_datatypes.append(dtype)elifdtypeisNone:passelse:raiseGeoKitRasterError("dtype must be a gdal data type, string or None value but got: %s"%str(type(dtype)))list_of_datatypes.append(dsInfo.data_type_name_str)list_of_numbers.append(dsInfo.minimum_value)list_of_numbers.append(dsInfo.maximum_value)# If a cutline is given, create the outputifcutlineisnotNone:ifisinstance(cutline,ogr.Geometry):tempdir=TemporaryDirectory()cutline=UTIL.quickVector(cutline,output=os.path.join(tempdir.name,"tmp.shp"))# cutline is already a path to a vectorelifUTIL.isVector(cutline):tempdir=Noneelse:raiseGeoKitRasterError("cutline must be a Geometry or a path to a shape file")# Workflow depends on whether or not we have an outputifisinstance(output,pathlib.Path):output=str(output)ifisinstance(output,str):# Simply do a translateifos.path.isfile(output):ifoverwriteisTrue:os.remove(output)ifos.path.isfile(output+".aux.xml"):# Because QGIS....os.remove(output+".aux.xml")else:raiseGeoKitRasterError("Output file already exists: %s"%output)gdal_data_type_constant=MinimumCDataTypeHandler.get_valid_gdal_data_type_as_constant(list_of_numbers=list_of_numbers,minimum_gdal_type_list=list_of_datatypes,user_defined_minimum_gdal_type=dtype,)# # Check some for bad input configurations# if not srs is None:# if (pixelHeight is None or pixelWidth is None):# raise GeoKitRasterError("When warping between srs's and writing to a file, pixelWidth and pixelHeight must be given")# Arange inputsco=kwargs.pop("creationOptions",COMPRESSION_OPTION)copyMeta=kwargs.pop("copyMetadata",True)aligned=kwargs.pop("targetAlignedPixels",True)# Fix the bounds issue by making them just a little bit smaller, which should be fixed by gdalwarpbounds=(bounds[0]+0.001*pixelWidth,bounds[1]+0.001*pixelHeight,bounds[2]-0.001*pixelWidth,bounds[3]-0.001*pixelHeight,)# Let gdalwarp do everything...gdal_warp_options=gdal.WarpOptions(outputType=gdal_data_type_constant,xRes=pixelWidth,yRes=pixelHeight,creationOptions=co,outputBounds=bounds,dstSRS=srs,dstNodata=noDataRead,resampleAlg=resampleAlg,copyMetadata=copyMeta,targetAlignedPixels=aligned,cutlineDSName=cutline,**kwargs,)result_dataset=gdal.Warp(destNameOrDestDS=output,srcDSOrSrcDSTab=source,options=gdal_warp_options)ifnotUTIL.isRaster(result_dataset):raiseGeoKitRasterError("Failed to translate raster")destination_raster=outputelse:if"cropToCutline"inkwargs:msg="The 'cropToCutline' option is not taken into account when writing to a raster in memory. Try using geokit.Extent.warp instead"warnings.warn(msg,UserWarning)gdal_data_type_string=MinimumCDataTypeHandler.get_valid_gdal_data_type_as_string(list_of_numbers=list_of_numbers,minimum_gdal_type_list=list_of_datatypes,user_defined_minimum_gdal_type=dtype,)# Warp to a raster in memorydestination_raster=UTIL.quickRaster(bounds=bounds,srs=srs,dx=pixelWidth,dy=pixelHeight,dtype=gdal_data_type_string,noData=noDataRead,)# Do a warpresult_dataset=gdal.Warp(destination_raster,source,resampleAlg=resampleAlg,cutlineDSName=cutline,**kwargs)destination_raster.FlushCache()# Do we have meta data?ifmetaisnotNone:ifisinstance(destination_raster,str):loaded_output_raster=loadRaster(destination_raster,1)else:loaded_output_raster=destination_rasterfork,vinmeta.items():loaded_output_raster.SetMetadataItem(k,v)# FlushCache writes all changes to diskloaded_output_raster.FlushCache()ifcutlineisnotNone:deltempdirreturndestination_raster
Convenience function to warp a raster to the context of another raster
as returned from a call to rasterInfo(contextSource).
dataSource : Anything acceptable by loadRaster()
The raster data source to draw
contextSource : Anything acceptable by loadRaster()
The raster context source to draw, i.e. the data source raster will be
warped to this pixelWidth, pixelHeight, bounds, extent, srs etc.
copyMetadata : bool, optional
If True, the metadata of the dataSource raster will be copied, else
metadata will be empty or as possibly provided in kwargs. Defaults to False.
**kwargs
All kwargs will be passed on to raster.warp()
NOTE: If no 'dtype' value as kwargs is given, dtype will be defined
automatically based on the value range, this can be time-consuming
depending on data size. Avoid by specifying dtype explicitly.
defwarpLike(dataSource:load_raster_input,contextSource:load_raster_input,copyMetadata:bool=False,**kwargs):""" Convenience function to warp a raster to the context of another raster as returned from a call to rasterInfo(contextSource). dataSource : Anything acceptable by loadRaster() The raster data source to draw contextSource : Anything acceptable by loadRaster() The raster context source to draw, i.e. the data source raster will be warped to this pixelWidth, pixelHeight, bounds, extent, srs etc. copyMetadata : bool, optional If True, the metadata of the dataSource raster will be copied, else metadata will be empty or as possibly provided in kwargs. Defaults to False. **kwargs All kwargs will be passed on to raster.warp() NOTE: If no 'dtype' value as kwargs is given, dtype will be defined automatically based on the value range, this can be time-consuming depending on data size. Avoid by specifying dtype explicitly. """ifUTIL.isRaster(dataSource):dataInfo=rasterInfo(dataSource)ifnotisinstance(dataInfo,RasterInfo):raiseGeoKitRasterError("Could not understand dataSource")ifUTIL.isRaster(contextSource):contextInfo=rasterInfo(contextSource)ifnotisinstance(contextInfo,RasterInfo):raiseGeoKitRasterError("Could not understand contextSource")# first get data-related parameters from DATA sourceifcopyMetadata:if"meta"inkwargs:raiseGeoKitRasterError("If metadata is given as 'meta' in kwargs, copyMetadata cannot be True!")meta=dataInfo.metaelse:meta=kwargs.pop("meta",None)# then get context related parameters from CONTEXT sourcebounds=kwargs.pop("bounds",contextInfo.bounds)pixelWidth=kwargs.pop("pixelWidth",contextInfo.pixelWidth)pixelHeight=kwargs.pop("pixelHeight",contextInfo.pixelHeight)srs=kwargs.pop("srs",contextInfo.srs)noData=kwargs.pop("noData",contextInfo.noData)returnwarp(source=dataSource,bounds=bounds,pixelWidth=pixelWidth,pixelHeight=pixelHeight,srs=srs,noData=noData,meta=meta,**kwargs,)