summit/backend/venv/lib/python3.12/site-packages/rasterio/rio/edit_info.py

248 lines
8.6 KiB
Python

"""Fetch and edit raster dataset metadata from the command line."""
import json
import warnings
import click
import rasterio
import rasterio.crs
from rasterio.crs import CRS
from rasterio.dtypes import in_dtype_range
from rasterio.enums import ColorInterp
from rasterio.errors import CRSError
from rasterio.rio import options
from rasterio.transform import guard_transform
# Handlers for info module options.
def all_handler(ctx, param, value):
"""Get tags from a template file or command line."""
if ctx.obj and ctx.obj.get('like') and value is not None:
ctx.obj['all_like'] = value
value = ctx.obj.get('like')
return value
def crs_handler(ctx, param, value):
"""Get crs value from a template file or command line."""
retval = options.from_like_context(ctx, param, value)
if retval is None and value:
try:
retval = json.loads(value)
except ValueError:
retval = value
try:
if isinstance(retval, dict):
retval = CRS(retval)
else:
retval = CRS.from_string(retval)
except CRSError:
raise click.BadParameter(
"'%s' is not a recognized CRS." % retval,
param=param, param_hint='crs')
return retval
def tags_handler(ctx, param, value):
"""Get tags from a template file or command line."""
retval = options.from_like_context(ctx, param, value)
if retval is None and value:
try:
retval = dict(p.split('=') for p in value)
except Exception:
raise click.BadParameter(
"'%s' contains a malformed tag." % value,
param=param, param_hint='transform')
return retval
def transform_handler(ctx, param, value):
"""Get transform value from a template file or command line."""
retval = options.from_like_context(ctx, param, value)
if retval is None and value:
try:
value = json.loads(value)
except ValueError:
pass
try:
retval = guard_transform(value)
except Exception:
raise click.BadParameter(
"'%s' is not recognized as an Affine array." % value,
param=param, param_hint='transform')
return retval
def colorinterp_handler(ctx, param, value):
"""Validate a string like ``red,green,blue,alpha`` and convert to
a tuple. Also handle ``RGB`` and ``RGBA``.
"""
if value is None:
return value
# Using '--like'
elif value.lower() == 'like':
return options.from_like_context(ctx, param, value)
elif value.lower() == 'rgb':
return ColorInterp.red, ColorInterp.green, ColorInterp.blue
elif value.lower() == 'rgba':
return ColorInterp.red, ColorInterp.green, ColorInterp.blue, ColorInterp.alpha
else:
colorinterp = tuple(value.split(','))
for ci in colorinterp:
if ci not in ColorInterp.__members__:
raise click.BadParameter(
"color interpretation '{ci}' is invalid. Must be one of: "
"{valid}".format(
ci=ci, valid=', '.join(ColorInterp.__members__)))
return tuple(ColorInterp[ci] for ci in colorinterp)
@click.command('edit-info', short_help="Edit dataset metadata.")
@options.file_in_arg
@options.bidx_opt
@options.edit_nodata_opt
@click.option('--unset-nodata', default=False, is_flag=True,
help="Unset the dataset's nodata value.")
@click.option('--crs', callback=crs_handler, default=None,
help="New coordinate reference system")
@click.option('--unset-crs', default=False, is_flag=True,
help="Unset the dataset's CRS value.")
@click.option('--transform', callback=transform_handler,
help="New affine transform matrix")
@click.option('--units', help="Edit units of a band (requires --bidx)")
@click.option('--description',
help="Edit description of a band (requires --bidx)")
@click.option('--tag', 'tags', callback=tags_handler, multiple=True,
metavar='KEY=VAL', help="New tag.")
@click.option('--all', 'allmd', callback=all_handler, flag_value='like',
is_eager=True, default=False,
help="Copy all metadata items from the template file.")
@click.option(
'--colorinterp', callback=colorinterp_handler,
metavar="name[,name,...]|RGB|RGBA|like",
help="Set color interpretation for all bands like 'red,green,blue,alpha'. "
"Can also use 'RGBA' as shorthand for 'red,green,blue,alpha' and "
"'RGB' for the same sans alpha band. Use 'like' to inherit color "
"interpretation from '--like'.")
@options.like_opt
@click.pass_context
def edit(ctx, input, bidx, nodata, unset_nodata, crs, unset_crs, transform,
units, description, tags, allmd, like, colorinterp):
"""Edit a dataset's metadata: coordinate reference system, affine
transformation matrix, nodata value, and tags.
The coordinate reference system may be either a PROJ.4 or EPSG:nnnn
string,
--crs 'EPSG:4326'
or a JSON text-encoded PROJ.4 object.
--crs '{"proj": "utm", "zone": 18, ...}'
Transforms are JSON-encoded Affine objects like:
--transform '[300.038, 0.0, 101985.0, 0.0, -300.042, 2826915.0]'
Prior to Rasterio 1.0 GDAL geotransforms were supported for --transform,
but are no longer supported.
Metadata items may also be read from an existing dataset using a
combination of the --like option with at least one of --all,
`--crs like`, `--nodata like`, and `--transform like`.
rio edit-info example.tif --like template.tif --all
To get just the transform from the template:
rio edit-info example.tif --like template.tif --transform like
"""
# If '--all' is given before '--like' on the commandline then 'allmd'
# is the string 'like'. This is caused by '--like' not having an
# opportunity to populate metadata before '--all' is evaluated.
if allmd == 'like':
allmd = ctx.obj['like']
with ctx.obj['env'], rasterio.open(input, 'r+') as dst:
if allmd:
nodata = allmd['nodata']
crs = allmd['crs']
transform = allmd['transform']
tags = allmd['tags']
colorinterp = allmd['colorinterp']
if unset_nodata and nodata is not None:
raise click.BadParameter(
"--unset-nodata and --nodata cannot be used together."
)
if unset_crs and crs:
raise click.BadParameter("--unset-crs and --crs cannot be used together.")
if unset_nodata:
# Setting nodata to None will raise NotImplementedError
# if GDALDeleteRasterNoDataValue() isn't present in the
# GDAL library.
try:
dst.nodata = None
except NotImplementedError as exc: # pragma: no cover
raise click.ClickException(str(exc))
elif nodata is not None:
dtype = dst.dtypes[0]
if nodata is not None and not in_dtype_range(nodata, dtype):
raise click.BadParameter(
"outside the range of the file's data type (%s)." % dtype,
param=nodata,
param_hint="nodata",
)
dst.nodata = nodata
if unset_crs:
dst.crs = None
elif crs:
dst.crs = crs
if transform:
dst.transform = transform
if tags:
dst.update_tags(**tags)
if units:
dst.set_band_unit(bidx, units)
if description:
dst.set_band_description(bidx, description)
if colorinterp:
if like and len(colorinterp) != dst.count:
raise click.ClickException(
"When using '--like' for color interpretation the "
"template and target images must have the same number "
"of bands. Found {template} color interpretations for "
"template image and {target} bands in target "
"image.".format(
template=len(colorinterp),
target=dst.count))
try:
dst.colorinterp = colorinterp
except ValueError as e:
raise click.ClickException(str(e))
# Post check - ensure that crs was unset properly
if unset_crs:
with ctx.obj['env'], rasterio.open(input, 'r') as src:
if src.crs:
warnings.warn(
'CRS was not unset. Availability of his functionality '
'differs depending on GDAL version and driver')