Introduction¶
This section outlines the general usage of the Python bindings for a Raster
.
Reading and Writing of Rasters uses Numpy arrays
so numpy must be installed for the version of Python being used.
All the examples assume the following imports has been done.
from caris.coverage import *
import numpy
Checking the type of a dataset¶
You can use caris.coverage.identify()
to determine the type of dataset a file contains.
from caris.coverage import *
file_path = r'C:\bathymetry.csar'
type = identify(file_path)
if type == DatasetType.RASTER:
raster = Raster(file_path)
elif type == DatasetType.CLOUD:
cloud = Cloud(file_path)
elif type == DatasetType.VRS:
vrs = VRS(file_path)
Opening a Raster¶
The following will open a raster for reading.
raster = Raster('source_raster.csar')
One can specify the named parameter.
raster = Raster(filename='source_raster.csar')
One can also supply a URI
raster = Raster(uri='file:///source_raster.csar')
Alternatively, the caris.open()
function can be used:
from caris.coverage import *
import caris
# open file read only
raster = caris.open(file_name='raster.csar')
# open file read write
raster = caris.open(file_name='raster.csar', open_mode=caris.OpenMode.READ_WRITE)
Opening from BDB Server¶
To open a database surface, a URI must be given formatted as:
bdb://username:password@hostname/database/boid
Note
The database name is case sensitive.
Boids (object IDs for database objects) can be found by via caris.bathy.db.DatabaseFeature.id
.
In BASE Editor the boid can be found by selecting a surfac, the boid will be shown in the Selection window. The URI for opened rasters or clouds is shown in the Properties window’s “Surface Name” field.
raster = caris.coverage.Raster(uri='bdb://dba:sql@example.com/MyDB/9245b052-634c-11e7-8dcf-1866da47ee87')
Listing Bands¶
The band information is found in the Raster.band_info
property. It is simply a dictionary where the key is the band name and the value is an instance of BandInfo
.
The band information contains properties such as its type, category, minimum, maximum, no-data-value, etc.
See BandInfo
for more info.
raster = Raster('source_raster.csar')
for band_name in raster.band_info:
print(str(raster.band_info[band_name]))
Reading from a Raster¶
Reading is done via the read()
method.
The following will read the entire band named Depth. Note that this is not a good idea for large rasters. Reading in smaller blocks would be advisable.
raster = Raster('source_raster.csar')
data = raster.read(band_name = 'Depth', area = ((0,0),raster.dims), level = raster.highest_level)
The level is optional. It defaults to the highest level. This is equivalent code.
raster = Raster('source_raster.csar')
data = raster.read(band_name = 'Depth', area = ((0,0),raster.dims))
The levels will be numerically lower for the coarser levels down to raster.lowest_level inclusive.
Note that in most cases, using named parameters is an optional feature. One may specify the arguments in order without the names.
raster = Raster('source_raster.csar')
data = raster.read('Depth', ((0,0),raster.dims))
Reading from a Raster into a Numpy array¶
Rasters can also be read into a Numpy
array via the read_np_array
.
raster = Raster('source_raster.csar')
data = raster.read_np_array(band_name = 'Depth', area = ((0,0),raster.dims), level = raster.highest_level)
for x in range(0, data.shape[0] - 1):
for y in range(0, data.shape[1] - 1):
depth = data[x][y]
Example counting no-data-values¶
raster = Raster('source_raster.csar')
area = ((0,0), raster.dims)
ndvCount = sum(1 for x in raster.read('Depth', area) if x == raster.band_info['Depth'].ndv)
print('no-data-value count: ' + str(ndvCount))
# Or using numpy array
data = raster.read_np_array('Depth', area)
ndvCount = sum(1 for x in data.reshape(data.shape[0] * data.shape[1]) if x == raster.band_info['Depth'].ndv)
Examples converting between grid and geographic coordinates¶
Grid coordinates go from (0,0) to Raster.dims
. They are in pixels. Suppose you wanted to know what the coordinate (100,100) is in geographic coordinates.
Use the convertGridToGeo()
method on the Raster’s transform.
raster = Raster('source_raster.csar')
geoPoint = raster.transform.convertGridToGeo((100,100), raster.highest_level)
Converting a grid area is similar.
raster = Raster('source_raster.csar')
gridArea = ((100,100),(200,200))
geoArea = raster.transform.convertGridToGeo(gridArea, raster.highest_level)
To convert in the other direction, use the convertGeoToGrid()
method.
raster = Raster('source_raster.csar')
gridPoint = raster.transform.convertGeoToGrid((417760.1, 5579348.0), raster.highest_level)
raster = Raster('source_raster.csar')
geoArea = ((417760.1, 5579348.0),(417770.1, 5579358.0))
gridArea = raster.transform.convertGeoToGrid(geoArea, raster.highest_level)
Examples converting areas to a different level¶
If you have an area defined at the highest resolution, but you now want to read from a coarser level that represents the same geographic area, you can simply convert your coordinates before reading.
raster = Raster('source_raster.csar')
gridArea = ((100,100),(200,200))
gridArea2 = raster.transform.convertGridBox(raster.highest_level, raster.highest_level - 1, gridArea)
data = raster.read('Depth', gridArea2, raster.highest_level - 1)
Example expanding a geographic area to pixel boundary¶
Geographic coordinates don’t necessarilly match up to pixel coordinates. It is often desirable to expand the geographic extents to fully encompass all of the underlying pixels. The following example demonstrates how to do such a read.
raster = Raster('source_raster.csar')
geoArea = ((417760.1, 5579348.0),(417770.1, 5579358.0))
# Expand extents before converting to grid coordinates.
geoArea = raster.transform.expandGeo(geoArea, raster.highest_level)
# Now convert to grid space
gridArea = raster.transform.convertGeoToGrid(geoArea, raster.highest_level)
# Finally, read our data
data = raster.read('Depth', gridArea)
Copying a Raster¶
There are many ways to copy a Raster
. The simplest way is to call the create_copy()
member function.
raster = Raster('source_raster.csar')
outputFilename = 'dest_raster.csar'
raster.create_copy(outputFileName)
Another way of doing it is opening a Raster
and copying all the required info into an Options
instance.
raster = Raster('source_raster.csar')
outputFilename = 'dest_raster.csar'
# Setup options
opts = Options()
opts.open_type = OpenType.WRITE
opts.band_info = raster.band_info
opts.dims = raster.dims
opts.wkt_cosys = raster.wkt_cosys
opts.iso19139_xml = raster.iso19139_xml
opts.raster2geo_matrix = raster.transform.matrix
opts.pixel_type = raster.transform.pixel_type
opts.raster_read_callback_func = raster.read
# Create Raster
raster2 = Raster(outputFilename, options = opts)
Converting between BandInfo and a dictionary¶
A BandInfo
class is not a dictionary and is not modifiable.
However, one can convert to a dictionary, apply the changes you want and then convert it back.
Here is how to convert to a dictionary.
raster = Raster('source_raster.csar')
bi = raster.band_info['Depth']
# Do the conversion
dbi = dict((x, getattr(bi, x)) for x in bi)
Converting back is much easier. Simply pass the dictionary to the BandInfo
‘s constructor.
raster = Raster('source_raster.csar')
bi = raster.band_info['Depth']
# Do the conversion
dbi = dict((x, getattr(bi, x)) for x in bi)
# Convert back
bandInfo = BandInfo(dbi)
Example modifying Level Policies¶
A Level Policy
is how to build the other pyramid levels of the raster. Each band can have its own level policy.
Here is an example where we copy a Raster
with known bands and we modify each band’s level policy.
raster = Raster('source_raster.csar')
outputFilename = 'dest_raster.csar'
# Setup options
opts = Options()
opts.open_type = OpenType.WRITE
opts.band_info = raster.band_info
# Change level policies
bi = opts.band_info['Deep']
dbi = dict((x, getattr(bi, x)) for x in bi)
dbi['level_policy'] = LevelPolicy.MIN
opts.band_info['Deep'] = BandInfo(dbi)
bi = opts.band_info['Density']
dbi = dict((x, getattr(bi, x)) for x in bi)
dbi['level_policy'] = LevelPolicy.FOLLOW
dbi['follow_band_name'] = 'Deep'
opts.band_info['Density'] = BandInfo(dbi)
bi = opts.band_info['Mean']
dbi = dict((x, getattr(bi, x)) for x in bi)
dbi['level_policy'] = LevelPolicy.FOLLOW
dbi['follow_band_name'] = 'Deep'
opts.band_info['Mean'] = BandInfo(dbi)
bi = opts.band_info['Shoal']
dbi = dict((x, getattr(bi, x)) for x in bi)
dbi['level_policy'] = LevelPolicy.MAX
opts.band_info['Shoal'] = BandInfo(dbi)
bi = opts.band_info['Weight']
dbi = dict((x, getattr(bi, x)) for x in bi)
dbi['level_policy'] = LevelPolicy.FOLLOW
dbi['follow_band_name'] = 'Shoal'
opts.band_info['Weight'] = BandInfo(dbi)
# Setup remaining options for copying
opts.dims = raster.dims
opts.wkt_cosys = raster.wkt_cosys
opts.iso19139_xml = raster.iso19139_xml
opts.raster2geo_matrix = raster.transform.matrix
opts.pixel_type = raster.transform.pixel_type
opts.raster_read_callback_func = raster.read
# Create raster
raster2 = Raster(outputFilename, options = opts)
Copying a Raster with Lambda¶
When creating a Raster
, we must always copy from another source. This need not be from a Raster
.
In this case, we will use a lambda function. It does read from a source raster, but can be redirected to whatever source the user wishes.
def proxy_read_function(raster, offset, *args, **kwargs):
in_box = args[1]
level = raster.highest_level
if (len(args) >= 3):
level = args[2]
if ('level' in kwargs):
level = kwargs['level']
box = ((in_box[0][0] + offset[0], in_box[0][1] + offset[1]), (in_box[1][0] + offset[0], in_box[1][1] + offset[1]))
return raster.read(args[0], box, level)
def create_read_function(raster, offset):
func = lambda args, kwargs, raster=raster, offset=offset: proxy_read_function(raster, offset, args, kwargs)
return func
def create_copy_with_lambda():
raster = Raster('source_raster.csar')
outputFilename = 'dest_raster.csar'
# Setup options
opts = Options()
opts.open_type = OpenType.WRITE
opts.band_info = raster.band_info
opts.dims = raster.dims
opts.wkt_cosys = raster.wkt_cosys
opts.iso19139_xml = raster.iso19139_xml
opts.raster2geo_matrix = raster.transform.matrix
opts.pixel_type = raster.transform.pixel_type
# Use lambda function for reading from source.
opts.raster_read_callback_func = create_read_function(raster, (0,0))
# Create raster
raster2 = Raster(outputFilename, options = opts)
Writing to a Raster¶
A Raster can be updated via Raster.write
import numpy
opts = Options()
opts.open_type = OpenType.WRITE
raster = Raster('source_raster.csar', options=opts)
area_to_write = ((0, 0), raster.dims)
band_name = 'Depth'
band_dtype = raster.band_info[band_name].numpy_dtype
# create some random data
num_values = raster.dims[0] * raster.dims[1]
array = numpy.arange(num_values, dtype=band_dtype).reshape(raster.dims[1], raster.dims[0])
# note raster.dims is columns x rows while numpy shapes are rows x columns
# write the data
raster.write(band_name, area_to_write, array)
Creating a Raster¶
A raster can be created from scratch via caris.coverage.create_raster()
.
uri = 'raster.csar'
origin = [0.0, 0.0]
resolution = [5.0, 5.0]
dimensions = [4, 4]
crs = """
PROJCS["WGS 84 / World Mercator",
GEOGCS["WGS 84",
DATUM["World Geodetic System 1984",
SPHEROID["WGS 84",6378137,298.2572235629972,
AUTHORITY["EPSG","7030"]],
TOWGS84[0,0,0,0,0,0,0],
AUTHORITY["EPSG","6326"]],
PRIMEM["Greenwich",0,
AUTHORITY["EPSG","8901"]],
UNIT["degree (supplier to define representation)",0.0174532925199433,
AUTHORITY["EPSG","9122"]],
AUTHORITY["EPSG","4326"],
EXTENSION["tx_authority","WG84"]],
PROJECTION["Mercator_1SP",
AUTHORITY["EPSG","19883"]],
PARAMETER["central_meridian",0],
PARAMETER["scale_factor",1],
PARAMETER["false_easting",0],
PARAMETER["false_northing",0],
UNIT["metre",1,
AUTHORITY["EPSG","9001"]],
AUTHORITY["EPSG","3395"]]
"""
band_info = BandInfo(name="Depth",
type = DataType.FLOAT32,
tuple_length = 1,
direction = Direction.DEPTH,
units = 'm',
category = Category.SCALAR,
level_policy = LevelPolicy.MAX)
bands = [band_info]
raster = create_raster(uri, crs, origin,
resolution, dimensions,
bands)
# write data
band_dtype = raster.band_info['Depth'].numpy_dtype
area = ((0, 0),(4, 4))
array = numpy.arange(16, dtype=band_dtype).reshape(4, 4)
raster.write("Depth", area, array)
Adding and removing bands¶
Bands can be added and removed with Raster.add_band
and Raster.remove_band
import numpy
from caris.coverage import *
opts = Options()
opts.open_type = OpenType.WRITE
raster = Raster('raster.csar', options=opts)
band_name = "Test"
band_info = BandInfo(name=band_name,
type = DataType.FLOAT32,
tuple_length = 1,
direction = Direction.DEPTH,
units = 'm',
category = Category.SCALAR,
level_policy = LevelPolicy.MAX)
# add the band
raster.add_band(band_info)
# prepare some data to write
area_to_write = ((0, 0), raster.dims)
band_dtype = raster.band_info[band_name].numpy_dtype
# create some random data
num_values = raster.dims[0] * raster.dims[1]
array = numpy.arange(num_values, dtype=band_dtype).reshape(raster.dims[1], raster.dims[0])
# note raster.dims is columns x rows while numpy shapes are rows x columns
# write the data
raster.write(band_name, area_to_write, array)
# remove the band
raster.remove_band(band_name)
Updating bounding polygon¶
A bounding polygon can be automatically generated for a Raster. Any valid polygon can be set to be used as the bounding polygon. Used together, a bounding polygon can be replaced with the automatically generated bounding polygon.
opts = Options()
opts.open_type = OpenType.WRITE
raster = Raster(file_path, options=opts)
new_bounding_polygon = generate_polygon(raster)
raster.bounding_polygon = new_bounding_polygon