Introduction¶
The caris Python API provides a consistent data model for Datasets
in a variety of formats.
This tutorial demonstrates how to use the caris module. The features in this module are commonly used in any CARIS Python API script.
Dataset Objects¶
Datasets are collections of Features
, and each Feature is defined by its Geometry
and attributes
.
Opening a Dataset from a file¶
Use the caris.open()
function to open a supported dataset. Supported file types include ESRI Shapefile, GML, KML, MapInfo, SQLite, and GeoPackage.
The caris.open()
function requires an argument for either a uri or a file_name path to a supported dataset.
import caris
shapefile_path = r"Data\example.shp"
shapefile = caris.open(file_name=shapefile_path)
You can use caris.open()
to open a file as a Dataset
in read/write mode by passing caris.OpenMode.READ_WRITE to its open_mode argument.
Changes to a Dataset
are not saved until its .commit()
method is called.
import caris
dataset = caris.open(file_name=r"Data\example.gpkg",
open_mode=caris.OpenMode.READ_WRITE)
for feature in dataset:
feature['INFORM'] = 'Edited by Cartographer'
dataset.commit()
Extended Dataset class¶
The caris.Dataset
class is extended by the caris.bathy.db.Dataset
class, which adds database-specific attributes and methods to the base Dataset
class.
For example, the attribute .current_time
, and the methods .query_journal()
and .query_offline()
, are only available in the extended caris.bathy.db.Dataset
class.
Iterating over a Dataset¶
Dataset
objects are iterable, and the result of each iteration step is a Feature
object.
import caris
shapefile_path = r"Data\example.shp"
shapefile_dataset = caris.open(file_name=shapefile_path)
for feature in shapefile_dataset:
print(feature)
<caris._py_caris.Feature object at 0x0000024A8C999190>
<caris._py_caris.Feature object at 0x0000024A8C9BB8C8>
<caris._py_caris.Feature object at 0x0000024A8C999190>
Querying a Dataset¶
The Dataset
class contains methods for querying its Features
. The results of a query are returned as FeatureRange
objects, which can be iterated over to get the results as Feature objects.
Query all Features¶
Use the .query_all()
method to retrieve all features in a Dataset
.
import caris
dataset = caris.open(file_name=r"Data\example.gpkg")
all_features = dataset.query_all()
print(all_features)
for feature in all_features:
print(feature)
<caris._py_caris.FeatureRange object at 0x000001F18F9775A0>
<caris._py_caris.Feature object at 0x000001F18F958190>
<caris._py_caris.Feature object at 0x000001F18F97B8C8>
<caris._py_caris.Feature object at 0x000001F18F958190>
Since FeatureRange
objects are iterators, you can convert them to a list using the built-in list() function.
import caris
dataset = caris.open(file_name=r"Data\example.gpkg")
all_features = list(dataset.query_all())
print(all_features)
[<caris._py_caris.Feature object at 0x00000284BA914190>, <caris._py_caris.Feature object at 0x00000284BA93C8C8>, <caris._py_caris.Feature object at 0x00000284BA93C920>, <caris._py_caris.Feature object at 0x00000284BA93C978>, <caris._py_caris.Feature object at 0x00000284BA93C9D0>]
Query by feature type¶
You can restrict your query to a single feature type using the .query()
method and passing the desired feature code as the first argument.
import caris
dataset = caris.open(file_name=r"Data\example.gpkg")
surfac_features = dataset.query('surfac')
for surfac_feature in surfac_features:
print(surfac_feature['OBJNAM'])
Area6-7125 400kHz-Fresh Water Seeps_1m
EM2040_NIWA_Sediment_types_05m
EM2040_NIWA_Seadefence_structure_05m
Query using CQL¶
The .query()
method accepts the keyword argument CQL into which you can pass a string written in Common Query Language by the Open Geospatial Consortium.
import caris
dataset = caris.open(file_name=r"Data\example.gpkg")
cql = "SUREND > 20110301"
surfac_features = dataset.query('surfac', CQL=cql)
for feature in surfac_features:
print(feature['SUREND'])
20110427
20110309
CQL queries support functions such as IS, CONTAINS, and LIKE. You can use the Advanced tab of the Add Layer by Filter dialog box in a CARIS application for help with CQL syntax.
import caris
dataset = caris.open(file_name=r"Data\example.gpkg")
cql = "OBJNAM LIKE '%NIWA%' AND TECSOU IS { 'found by multi-beam' }"
surfac_features = dataset.query('surfac', CQL=cql)
for feature in surfac_features:
print(feature['OBJNAM'])
EM2040_NIWA_Sediment_types_05m
EM2040_NIWA_Seadefence_structure_05m
For more information about CQL queries, visit:
https://docs.geoserver.org/stable/en/user/tutorials/cql/cql_tutorial.html
Dataset Catalogues¶
A catalogue sets up features, attributes, and relations that are part of a Dataset
.
The catalogue contains information about a Dataset
such as:
Attributes that belong to each
Feature
.Attributes that are mandatory.
Permitted attribute values.
Master-slave relations that may exist between features.
FeatureCatalogue Objects¶
FeatureCatalogue
objects provide information about which FeatureDefinitions
belong to the catalogue.
You can get the FeatureCatalogue
of a Dataset
using its .catalogue
attribute.
import caris
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path)
print(dataset.catalogue)
<caris._py_caris.FeatureCatalogue object at 0x000002045A8F1190>
The FeatureCatalogue.definitions
attribute is a list of FeatureDefinitions
in the catalogue.
import caris
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path)
catalogue = dataset.catalogue
print(catalogue.definitions)
[FeatureDefinition('surfac'), FeatureDefinition('survey')]
You can retrieve a particular FeatureDefinition
by passing its feature code to the FeatureCatalogue.get_definition()
method.
import caris
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path)
catalogue = dataset.catalogue
print(catalogue.get_definition('surfac'))
FeatureDefinition('surfac')
FeatureDefinition Objects¶
FeatureDefinition
objects provide information about feature type definitions in a FeatureCatalogue
.
import caris
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path)
surfac_definition = dataset.catalogue.get_definition('surfac')
print(surfac_definition.code)
print(surfac_definition.primitiveTypes)
surfac
[caris._py_caris.PrimitiveType.AREA_2D, caris._py_caris.PrimitiveType.LINE_2D, caris._py_caris.PrimitiveType.POINT_2D]
You can get the AttributeDefinitionDictionary
object representing attribute definitions belonging to the FeatureDefinition
from its .attributes
attribute.
import caris
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path)
surfac_definition = dataset.catalogue.get_definition('surfac')
print(type(surfac_definition.attributes))
print(surfac_definition.attributes)
<class 'caris._py_caris.AttributeDefinitionDictionary'>
[AttachedFiles,CATZOC,CPDATE,CoverageCRS,CoverageDescriptor,CoverageId,CoverageName,CoverageResolution,CoverageSource,CoverageType,CreatingUser,CreationTime,DRVAL1,DRVAL2,DUNITS,FeatureId,HORDAT,HUNITS,INFORM,MasterId,ModificationTime,NINFOM,OBJNAM,POSACC,RECDAT,RECIND,SORDAT,SORIND,SOUACC,STATUS,SUREND,SURSTA,SURTYP,TECSOU,VERDAT,planam,srfcat]
AttributeDefinitionDictionary Objects¶
AttributeDefinitionDictionary
objects provide information about what attributes belong to a FeatureDefinition
.
AttributeDefinitionDictionary
are dictionary-like objects where the key is an attribute code and the value is an AttributeDefinition
.
import caris
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path)
surfac_definition = dataset.catalogue.get_definition('surfac')
attribute_definition_dictionary = surfac_definition.attributes
print(type(attribute_definition_dictionary["OBJNAM"]))
print(attribute_definition_dictionary["OBJNAM"])
<class 'caris._py_caris.AttributeDefinition'>
<Code>OBJNAM</Code>
<Name>OBJNAM</Name>
<Description></Description>
<Type>
<Kind>String</Kind>
<Code>OBJNAM</Code>
<Name>OBJNAM</Name>
<Description></Description>
<MinLength></MinLength>
<MaxLength></MaxLength>
<Default></Default>
<Control></Control>
<Format>
</Format>
</Type>
<ReadOnly>0</ReadOnly>
<Hidden>0</Hidden>
<Conditional>0</Conditional>
<UnknownPermitted>0</UnknownPermitted>
<MinOccurs>0</MinOccurs>
<MaxOccurs> 1</MaxOccurs>
AttributeDefinitionDictionary
have many of the same features as standard dictionary objects. For example, you can get a view of its key/value pairs using the .items()
method.
import caris
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path)
surfac_definition = dataset.catalogue.get_definition('surfac')
attribute_definition_dictionary = surfac_definition.attributes
for code, attribute_definition in attribute_definition_dictionary.items():
print("key: {:<20}value: {}".format(code, attribute_definition))
key: AttachedFiles value: AttributeDefinition('AttachedFiles')
key: CATZOC value: AttributeDefinition('CATZOC')
key: CPDATE value: AttributeDefinition('CPDATE')
# ...
# OUTPUT PARTIALLY HIDDEN
# ...
key: VERDAT value: AttributeDefinition('VERDAT')
key: planam value: AttributeDefinition('planam')
key: srfcat value: AttributeDefinition('srfcat')
Feature Objects¶
Use Feature
objects to work with features. Features are defined by their geometry, attribute definition, and attribute values.
Creating a Feature in a Dataset¶
You can create a new Feature
in an existing Dataset
by calling the Dataset.create_feature()
method. Creating a Feature
requires a Geometry
object to specify its geometry.
import caris
# Create CoordinateReferenceSystem for Geometry
wgs84_crs = caris.CoordinateReferenceSystem('EPSG', '4326')
# Create Geometry of new feature representing CARIS office building footprint
office_wkt = """
POLYGON (( -66.6627 45.9590, -66.6634 45.9592,
-66.6632 45.9594, -66.6629 45.9593,
-66.6628 45.9595, -66.6625 45.9593,
-66.6627 45.9590 ))
"""
office_geom = caris.Geometry(wgs84_crs, office_wkt)
# Open Fredericton building footprints dataset
footprint_dataset = caris.open(file_name=r'Data\BuildingFootprint.shp',
open_mode=caris.OpenMode.READ_WRITE)
# Create CARIS office footprint feature
office_feature = footprint_dataset.create_feature('BuildingFootprint', office_geom)
# Set attributes
office_feature['KEYWORD'] = 'CARIS HQ'
# Save changes to dataset
footprint_dataset.commit()
Extended Feature Class¶
The caris.Feature
class is extended by the caris.bathy.db.Feature
class, which adds database-specific methods and attributes to the base Feature
class.
For example, the attributes .attachments
and .id
, and the methods .download_attachment()
and .purge()
are only available in the extended caris.bathy.db.Feature
class.
Features queried from the base caris.Dataset
class will belong to the base caris.Feature
class, while features queried from the extended caris.bathy.db.Dataset
class will belong to the extended caris.bathy.db.Feature
class.
AttributeDictionary Objects¶
You can work with Feature
attributes using caris.AttributeDictionary
objects. Every Feature
has an caris.AttributeDictionary
in its .attributes
attribute.
Accessing Feature Attributes¶
You can index Feature
objects in a manner similar to a dictionary. Enter an attribute code between square brackets to access its value.
import caris
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path)
# Get first surfac feature in dataset
feature = list(dataset.query('surfac'))[0]
print(feature['OBJNAM'])
Area6-7125 400kHz-Fresh Water Seeps_1m
Only attributes that have been set can be indexed. In the following example, both “OBJNAM” and “INFORM” are valid attributes, but only “OBJNAM” has been set. Attempting to index the “INFORM” attribute throws a KeyError.
import caris
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path)
# Get first surfac feature in dataset
feature = list(dataset.query('surfac'))[0]
print(feature['OBJNAM']) # Attribute has been set
print(feature['INFORM']) # Attribute has not been set
Area6-7125 400kHz-Fresh Water Seeps_1m
Traceback (most recent call last):
File "c:\Work\CPP\Projects\framework_5\python\py_caris\documentation\caris\samples\access_unset_feature_attribute.py", line 10, in <module>
print(feature['INFORM']) # Attribute has not been set
KeyError: 'INFORM'
caris.AttributeDictionary
objects have a .get()
method, which you can use as an alternative way to access an attribute value. Rather than throw an error, the .get()
method returns None if an attribute has not been set. Optionally, a default value may be returned instead.
import caris
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path)
# Get first surfac feature in dataset
feature = list(dataset.query('surfac'))[0]
print(feature.attributes.get('OBJNAM')) # Attribute has been set
print(feature.attributes.get('INFORM')) # Attribute has not been set
print(feature.attributes.get('INFORM', '<undefined>')) # Using optional default value
Area6-7125 400kHz-Fresh Water Seeps_1m
None
<undefined>
Setting Feature Attributes¶
You can set Feature
object attributes by assigning a value to its AttributeDictionary
. The changes are applied when the .commit()
method is called on the Dataset
.
The type of data accepted by an AttributeDictionary
depends on the attribute’s type in the Dataset
catalogue.
Note
The following examples use a Bathy DataBASE Dataset so that all attribute types are represented. For more information about working with the caris.bathy.db module, see BDB Server Module.
import caris.bathy.db as bdb
NM_USER, NM_PASS, NM_HOST, DB_NAME = ('dba', 'sql', 'localhost', 'Training')
node_manager = bdb.NodeManager(NM_USER, NM_PASS, NM_HOST)
dataset = node_manager.get_database(DB_NAME)
# Get first surfac and survey features in dataset
surfac = list(dataset.query('surfac'))[0]
survey = list(dataset.query('survey'))[0]
# Set STRING type attributes from str
surfac["OBJNAM"] = "New Name"
# Set ALPHANUMERIC type attributes from str
surfac["RECIND"] = "CA,1C,digi"
# Set FLOAT type attributes from str or number
surfac["SOUACC"] = "3.7"
surfac["POSACC"] = 1.1
# Set INTEGER type attributes from str or number
# Float inputs are truncated
survey["CSCALE"] = 10000
survey["SDISMX"] = "5"
# Set DATE type attributes from str
# Format is not validated, but should be YYYYMMDD
survey["RECDAT"] = "19890921"
# Set ENUMERATION type attributes from int or str
# Both value and description are accepted
surfac["CATZOC"] = 1
surfac["DUNITS"] = "3"
surfac["VERDAT"] = "Low Water"
# Set LIST type attributes from list of int or str
# Both value and description are accepted
surfac["TECSOU"] = [1, "3", "found by laser"]
# # list methods like .append() work on LIST type attributes
surfac["TECSOU"].append(5)
print("Saving dataset changes.")
dataset.commit()
You can set an attribute to undefined by assigning it to a value that depends on the attribute’s type.
import caris.bathy.db as bdb
NM_USER, NM_PASS, NM_HOST, DB_NAME = ('dba', 'sql', 'localhost', 'Training')
node_manager = bdb.NodeManager(NM_USER, NM_PASS, NM_HOST)
dataset = node_manager.get_database(DB_NAME)
# Get first surfac and survey features in dataset
surfac = list(dataset.query('surfac'))[0]
survey = list(dataset.query('survey'))[0]
# Unset STRING type attributes with empty str
surfac["OBJNAM"] = ""
# Unset ALPHANUMERIC type attributes with empty str or list, or None
surfac["RECIND"] = ""
surfac["SORIND"] = []
surfac["CPDATE"] = None
# Unset FLOAT type attributes from None or empty list
surfac["SOUACC"] = None
surfac["POSACC"] = []
# Unset INTEGER type attributes from None or empty list
survey["CSCALE"] = None
survey["SDISMX"] = []
# Unset DATE type attributes with empty str or list, or None
survey["RECDAT"] = ""
surfac["RECDAT"] = []
surfac["RECDAT"] = None
# Unset ENUMERATION type attributes with empty str or list
surfac["DUNITS"] = ""
surfac["CATZOC"] = []
# Unset LIST type attributes with empty str or list
surfac["TECSOU"] = ""
surfac["STATUS"] = []
print("Saving dataset changes.")
dataset.commit()
Validating Feature Attributes¶
Validate Feature
attributes before saving them using the AttributeDictionary.validate()
method. The .validate()
method throws a RuntimeError if there are invalid attributes in the AttributeDictionary
. Use it within a try block.
import caris.bathy.db as bdb
NM_USER, NM_PASS, NM_HOST, DB_NAME = ('dba', 'sql', 'localhost', 'Training')
node_manager = bdb.NodeManager(NM_USER, NM_PASS, NM_HOST)
dataset = node_manager.get_database(DB_NAME)
# Get first surfac feature in dataset
surfac = list(dataset.query('surfac'))[0]
# Setting ENUMERATION type attribute to invalid value
surfac["VERDAT"] = "Loww Water"
try:
surfac.attributes.validate()
except RuntimeError as e:
print("ERROR: Attributes are invalid, rolling back changes.")
dataset.rollback()
else:
print("Saving dataset changes.")
dataset.commit()
ERROR: Attributes are invalid, rolling back changes.
Storing Attributes as XML¶
The AttributeDictionary.to_xml()
method returns an xml formatted string. You can use this string later to set attributes using the AttributeDictionary.from_xml()
method.
In the following example, a Feature's
attributes are dumped into an xml formatted string and saved to a file.
import caris
import os
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path)
# Create CQL query for particular FeatureId
feature_id = "f49da13c-e5d9-4bd6-a525-5289f95b4759"
cql = "FeatureId = '{}'".format(feature_id)
# Get the feature
feature = list(dataset.query('surfac', CQL=cql))[0]
# Read XML string from AttributeDictionary
attributes_xml = feature.attributes.to_xml()
# Build path to attribute xml dump
attribute_xml_path = os.path.join("Data", feature_id) + ".xml"
with open(attribute_xml_path, mode="w") as attribute_xml_file:
attribute_xml_file.write(attributes_xml)
You can later load the attributes xml file to set the attributes of a different Feature
.
import caris
dataset_path = r"Data\example.gpkg"
dataset = caris.open(file_name=dataset_path,
open_mode=caris.OpenMode.READ_WRITE)
# Create a new Feature in Dataset
new_polygon = """
POLYGON (( 174.8226113309640700 -41.2852411386752750,
174.8148263698648200 -41.3014979692060250,
174.8331439253924200 -41.3088249914170620,
174.8226113309640700 -41.2852411386752750 ))
"""
new_geometry = caris.Geometry(dataset.crs, new_polygon)
new_feature = dataset.create_feature('surfac', new_geometry)
# Load attributes xml to string
attributes_xml_path = r"Data\f49da13c-e5d9-4bd6-a525-5289f95b4759.xml"
with open(attributes_xml_path) as attributes_xml_file:
attributes_xml = attributes_xml_file.read()
# Apply loaded attributes to new Feature
new_feature.attributes.from_xml(attributes_xml)
print("Saving dataset changes.")
dataset.commit()
CoordinateReferenceSystem Objects¶
CoordinateReferenceSystem
objects represent a known coordinate reference system. Coordinate reference systems are used to correctly place geometry in the real world.
Every Dataset
object has an associated CoordinateReferenceSystem
stored in its .crs
attribute.
CoordinateReferenceSystem Object Constructor¶
You can create a CoordinateReferenceSystem
object from a Well Know Text string, or from an EPSG code.
To create one from Well Known Text, the first argument to the constructor must be ‘WKT’.
import caris
wgs84_wkt = """
GEOGCS["WGS 84",
DATUM["World Geodetic System 1984",
SPHEROID["WGS 84",6378137,298.2572235629972,
AUTHORITY["EPSG","7030"]],
AUTHORITY["EPSG","6326"]],
PRIMEM["Greenwich",0,
AUTHORITY["EPSG","8901"]],
UNIT["degree (supplier to define representation)",0.0174532925199433,
AUTHORITY["EPSG","9122"]],
AUTHORITY["EPSG","4326"]]
"""
wgs84_crs = caris.CoordinateReferenceSystem('WKT', wgs84_wkt)
To create one from an EPSG code, the first argument to the constructor must be ‘EPSG’.
import caris
wgs84_crs = caris.CoordinateReferenceSystem('EPSG', '4326')
Geometry Objects¶
Geometry
objects represent geometric information about points, lines, and areas in a known coordinate reference system.
Every Feature
object has an associated Geometry
stored in its .geometry
attribute.
Geometry Object Constructor¶
You can create a new Geometry
object from a Well Known Text string and a CoordinateReferenceSystem
object.
import caris
# Create CoordinateReferenceSystem for Geometry
wgs84_crs = caris.CoordinateReferenceSystem('EPSG', '4326')
# Create Geometry of new feature representing CARIS office building footprint
office_wkt = """
POLYGON (( -66.6627 45.9590, -66.6634 45.9592,
-66.6632 45.9594, -66.6629 45.9593,
-66.6628 45.9595, -66.6625 45.9593,
-66.6627 45.9590 ))
"""
office_geom = caris.Geometry(wgs84_crs, office_wkt)
Geometry queries¶
You can use Geometry
objects to query a Dataset
. The Dataset.query()
method has a series of keyword arguments that accept Geometry
objects. Each of these filters the Features
returned by the query a different way.
For most common cases, the intersects query is appropriate.
import caris
# Create CoordinateReferenceSystem for Geometry
wgs84_crs = caris.CoordinateReferenceSystem('EPSG', '4326')
# Create Geometry of new feature representing CARIS office building footprint
office_wkt = """
POLYGON (( -66.6627 45.9590, -66.6634 45.9592,
-66.6632 45.9594, -66.6629 45.9593,
-66.6628 45.9595, -66.6625 45.9593,
-66.6627 45.9590 ))
"""
office_geom = caris.Geometry(wgs84_crs, office_wkt)
# Open Fredericton building footprints dataset
footprint_dataset = caris.open(file_name=r"Data\BuildingFootprint.shp")
# Query for intersecting features
query_results = footprint_dataset.query("BuildingFootprint", intersects=office_geom)
for feature in query_results:
print(feature["OBJECTID"])
164
Geometry Transformations¶
You can transform a Geometry
object into a different coordinate reference system using its .transform()
method.
import caris
# Create CoordinateReferenceSystem for Geometry
wgs84_crs = caris.CoordinateReferenceSystem('EPSG', '4326')
# Create Geometry of new feature representing CARIS office building footprint
office_wkt = """
POLYGON (( -66.6627 45.9590, -66.6634 45.9592,
-66.6632 45.9594, -66.6629 45.9593,
-66.6628 45.9595, -66.6625 45.9593,
-66.6627 45.9590 ))
"""
office_geom = caris.Geometry(wgs84_crs, office_wkt)
# Create NAD83-UTM19 CoordinateReferenceSystem for transformed Geometry
nad83_utm19_crs = caris.CoordinateReferenceSystem('EPSG', '26919')
transformed_office_geom = office_geom.transform(nad83_utm19_crs)
print(transformed_office_geom.wkt)
POLYGON (( 681114.5603214782900000 5092148.3724222146000000, 681059.6673699112100000 5092169.0018110983000000, 681074.5129666726600000 5092191.6770132380000000, 681098.0849759923300000 5092181.2486592364000000, 681105.1817335451500000 5092203.6966117388000000, 681129.0797691545400000 5092182.1580670280000000, 681114.5603214782900000 5092148.3724222146000000 ))