API How-to Guide¶
This how-to guide described how to use the BAG API, from either C++ or Python, to perform some common tasks, including:
- Opening an existing BAG file:
Opening read-only
Opening read-write
Creating a BAG file
Working with georeferenced metadata
For more example usage, please refer to these sections of the BAG source code:
- Tests:
- Examples:
Note: if you have experience using GDAL, the BAG-GDAL compatibility tests may be particularly useful as they show how to use the BAG API side-by-side with the GDAL API.
A quick tour of the BAG API¶
For most purposes, the entry point to the BAG API is the BAG::Dataset
(bagPy.Dataset
).
Attributes for a BAG are described by the BAG::Descriptor
(bagPy.Descriptor
), which can be
accessed via BAG::Dataset::getDescriptor()
. BAG attributes include: (1) a list of layer types in the bag
(BAG::Descriptor::getLayerTypes()
or BAG::Descriptor::getLayerIds()
); (2) the number of rows and
columns of the BAG (BAG::Descriptor::getDims()
); (3) the resolution/grid spacing of the BAG data
(BAG::Descriptor::getGridSpacing()
); (4) the horizontal and vertical reference systems
(BAG::Descriptor::getHorizontalReferenceSystem()
and BAG::Descriptor::getVerticalReferenceSystem()
);
(5) the geographic position of the south west corner of the BAG (BAG::Descriptor::getOrigin()
); and
(6) the BAG version (BAG::Descriptor::getVersion()
).
Note
XML Metadata used to create a BAG are described via BAG::Metadata
(bagPy.Metadata
), which
can be accessed from a Dataset instance using the BAG::Dataset::getMetadata()
(bagPy.Dataset.getMetadata
) method.
The layers contained in a BAG dataset can be accessed in several ways. A list of layers can be fetched using the
BAG::Dataset::getLayers()
method (bagPy.Dataset.getLayers()
). Individual layers can be accessed by
passing the layer ID (IDs are integers starting at 0) to the BAG::Dataset::getLayer()
method. The IDs of
required layers correspond to their type value, i.e.., Elevation
or Uncertainty
;
IDs of other layers correspond to the order in which they layers were added to the Dataset during creation.
Layers also can be accessed by passing their BAG_LAYER_TYPE
and layer name (as a std::string
)
to the overloaded method std::shared_ptr<Layer> getLayer(LayerType type, const std::string& name) &
.
Regardless of how layers are accessed, all layers are an instance of BAG::Layer
(bagPy.Layer
)
or one of its sub-classes. BAG::Layer
instances provide access to the BAG::DataType
via the
BAG::Layer::getDataType()
method. Data can be read from a layer using the BAG::Layer::read()
method; similarly, the BAG::Layer::write()
method allows for writing data to a layer.
Detailed layer metadata can be accessed through BAG::LayerDescriptor
(bagPy.LayerDescriptor
)
objects, which can be obtained from a layer instance using the BAG::Layer::getDescriptor()
.
BAG::LayerDescriptor
instances allow access to attributes such as: (1) chunk size
(BAG::LayerDescriptor::getChunkSize()
); (2) compression level
(BAG::LayerDescriptor::getCompressionLevel()
); and (3) data minimum and maximum
(BAG::LayerDescriptor::getMinMax()
). Derived layer classes supply derived descriptors to expose additional
metadata.
Opening an existing BAG file¶
Opening read-only¶
C++: To open a BAG file read-only and print the number of rows and columns use the
BAG::Dataset::open()
function with BAG::OpenMode
set to
BAG_OPEN_READONLY
:
#include <stdlib.h>
#include <iostream>
#include <bag_dataset.h>
using BAG::Dataset;
int main(int argc, char** argv) {
auto dataset = Dataset::open(argv[1], BAG_OPEN_READONLY);
const auto& descriptor = dataset->getDescriptor();
std::cout << "Dataset details:" << std::endl;
uint64_t numRows = 0, numCols = 0;
std::tie(numRows, numCols) = descriptor.getDims();
std::cout << "\trows, columns == " << numRows << ", " << numCols << std::endl;
return EXIT_SUCCESS;
}
Python: To open a BAG file read-only and print the number of rows and columns use the
bagPy.Dataset.openDataset()
function with openMode
set to BAG_OPEN_READONLY
:
import sys
import bagPy as BAG
dataset = BAG.Dataset.openDataset(sys.argv[1], BAG.BAG_OPEN_READONLY)
descriptor = dataset.getDescriptor()
print('Dataset details:')
numRows, numCols = descriptor.getDims()
print(f"\trows, columns == {numRows}, {numCols}")
Opening read-write¶
C++: To open a BAG file read-write and add a new layer use the BAG::Dataset::open()
function with
BAG::OpenMode
set to BAG_OPEN_READ_WRITE
:
#include <stdlib.h>
#include <iostream>
#include <bag_dataset.h>
using BAG::Dataset;
int main(int argc, char** argv) {
auto dataset = Dataset::open(argv[1], BAG_OPEN_READ_WRITE);
const auto numLayerTypes = dataset->getLayerTypes().size();
std::cout << "Number of layer types: " << numLayerTypes << std::endl;
constexpr uint64_t chunkSize = 100;
constexpr int compressionLevel = 6;
dataset->createSimpleLayer(Average_Elevation, chunkSize, compressionLevel);
std::cout << "Number of layer types: " << dataset->getLayerTypes().size() << std::endl;
return EXIT_SUCCESS;
}
Python: To open a BAG file read-write and add a new layer use the bagPy.Dataset.openDataset()
function
with openMode
set to BAG_OPEN_READ_WRITE
:
import sys
import bagPy as BAG
dataset = BAG.Dataset.openDataset(sys.argv[1], BAG.BAG_OPEN_READ_WRITE)
numLayerTypes = len(dataset.getLayerTypes())
print(f"Number of layer types: {numLayerTypes}")
chunkSize = 100
compressionLevel = 6
dataset.createSimpleLayer(BAG.Average_Elevation, chunkSize, compressionLevel);
print(f"Number of layer types: {len(dataset.getLayerTypes())}")
Creating a BAG file¶
When creating a BAG, you must provide metadata in the form of ISO metadata standards 19915 and 19115-2, as
described in the Format Specification Document. A convenient way to do this is to load metadata from an XML file,
which is the approach illustrated below. However, it should be noted that metadata can be programmatically generated
using BAG::Metadata
(Python: bagPy.Metadata
) and its subclasses. For the purposes of this
example, we’ll use sample.xml to populated metadata in the newly created BAG.
Note
The spatial coverage of a BAG is defined by setting the southwest and northeast corners via the metadata element: /gmi:MI_Metadata/gmd:spatialRepresentationInfo/gmd:MD_Georectified/gmd:cornerPoints/gml:Point/gml:coordinates
.
C++:
#include <array>
#include <stdlib.h>
#include <iostream>
#include <bag_dataset.h>
#include <bag_simplelayer.h>
using BAG::Dataset;
constexpr uint32_t kGridSize = 100;
constexpr uint32_t kSepSize = 3;
int main(int argc, char** argv) {
const std::string xmlFileName = argv[1];
const std::string outFileName = argv[2];
// Read metadata from XML file
BAG::Metadata metadata;
try {
metadata.loadFromFile(xmlFileName);
} catch(const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
// Create the dataset.
std::shared_ptr<BAG::Dataset> dataset;
try {
constexpr uint64_t chunkSize = 100;
constexpr int compressionLevel = 1;
dataset = BAG::Dataset::create(outFileName, std::move(metadata),
chunkSize, compressionLevel);
} catch(const std::exception& e) {
std::cerr << "Error creating BAG file." << std::endl;
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
// Write the elevation layer, constructing bogus data as we do so.
auto elevationLayer = dataset->getSimpleLayer(Elevation);
// Set the min/max values (optional).
// NOTE: Layer::write() calls update min/max.
{
const std::array<float, 2> surfRange{-10.0f,
-10.0f - ((kGridSize - 1) * (kGridSize - 1) + kGridSize) / 10.0f};
auto pDescriptor = elevationLayer->getDescriptor();
std::cout << "Elevation min: " << surfRange[0] << ", max: " << surfRange[1] << std::endl;
pDescriptor->setMinMax(surfRange[0], surfRange[1]);
elevationLayer->writeAttributes();
}
// Write the data.
std::array<float, kGridSize> surf{};
for (uint32_t row=0; row<kGridSize; ++row) {
for (uint32_t column=0; column<kGridSize; ++column) {
surf[column] = ((column * row) % kGridSize) +
(column / static_cast<float>(kGridSize));
}
try {
const auto* buffer = reinterpret_cast<uint8_t*>(surf.data());
constexpr uint32_t columnStart = 0;
constexpr uint32_t columnEnd = kGridSize - 1;
elevationLayer->write(row, columnStart, row, columnEnd, buffer);
} catch(const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
}
// Write the uncertainty layer, constructing bogus data as we do so.
auto uncertaintyLayer = dataset->getSimpleLayer(Uncertainty);
// Set the min/max values (optional).
// NOTE: Layer::write() calls update min/max.
{
const std::array<float, 2> uncertRange{1.0f,
1.0f + ((kGridSize - 1) * (kGridSize - 1) + kGridSize) / 100.0f};
auto pDescriptor = uncertaintyLayer->getDescriptor();
std::cout << "Uncertainty min: " << uncertRange[0] << ", max: " << uncertRange[1] << std::endl;
pDescriptor->setMinMax(uncertRange[0], uncertRange[1]);
uncertaintyLayer->writeAttributes();
}
// Write the data.
std::array<float, kGridSize> uncert{};
for (uint32_t row=0; row<kGridSize; ++row) {
for (uint32_t column=0; column<kGridSize; ++column)
uncert[column] = ((column * row) % kGridSize) / 1000.0f;
try {
const auto* buffer = reinterpret_cast<uint8_t*>(uncert.data());
constexpr uint32_t columnStart = 0;
constexpr uint32_t columnEnd = kGridSize - 1;
uncertaintyLayer->write(row, columnStart, row, columnEnd, buffer);
} catch(const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}
Python:
import sys
import numpy as np
import bagPy as BAG
kGridSize: int = 100
kSepSize: int = 3
xmlFileName: str = sys.argv[1]
outFileName: str = sys.argv[2]
# Read metadata from XML file
metadata: BAG.Metadata = BAG.Metadata()
try:
metadata.loadFromFile(xmlFileName)
except BAG.ErrorLoadingMetadata as e:
sys.exit(e.what())
# Create the dataset.
dataset: BAG.Dataset = None
try:
chunkSize: int = 100
compressionLevel: int = 1
dataset = BAG.Dataset.create(outFileName, metadata, chunkSize, compressionLevel)
except FileExistsError as e:
mesg = f"Unable to create BAG '{outFileName}'; file already exists."
sys.exit(mesg)
# Write the elevation layer, constructing bogus data as we do so.
surfRange = (-10.0, -10.0 - ((kGridSize - 1) * (kGridSize - 1) + kGridSize) / 10.0)
elevationLayer: BAG.SimpleLayer = dataset.getSimpleLayer(BAG.Elevation)
pDescriptor: BAG.LayerDescriptor = elevationLayer.getDescriptor()
pDescriptor.setMinMax(surfRange[0], surfRange[1])
elevationLayer.writeAttributes()
# Write the data.
surf: np.ndarray = np.zeros(kGridSize)
try:
for row in range(kGridSize):
# Generate a row's worth of data in surf and write to layer
for column in range(kGridSize):
surf[column] = ((column * row) % kGridSize) + (column / float(kGridSize))
# columnStart and columnEnd can be moved outside of the loop, but are here for simplicity
columnStart: int = 0
columnEnd: int = kGridSize - 1
buffer: BAG.FloatLayerItems = BAG.FloatLayerItems(surf)
elevationLayer.write(row, columnStart, row, columnEnd, buffer)
except Exception as e:
sys.exit(str(e))
finally:
dataset.close()
# Write the uncertainty layer, constructing bogus data as we do so.
uncertRange = (-1.0, -1.0 - ((kGridSize - 1) * (kGridSize - 1) + kGridSize) / 100.0)
uncertaintyLayer: BAG.SimpleLayer = dataset.getSimpleLayer(BAG.Uncertainty)
pDescriptor = uncertaintyLayer.getDescriptor()
pDescriptor.setMinMax(uncertRange[0], uncertRange[1])
uncertaintyLayer.writeAttributes()
# Write the data.
uncert: np.ndarray = np.zeros(kGridSize)
try:
for row in range(kGridSize):
# Generate a row's worth of data in uncert and write to layer
for column in range(kGridSize):
uncert[column] = ((column * row) % kGridSize) / 1000.0
# columnStart and columnEnd can be moved outside of the loop, but are here for simplicity
columnStart: int = 0
columnEnd: int = kGridSize - 1
buffer: BAG.FloatLayerItems = BAG.FloatLayerItems(uncert)
uncertaintyLayer.write(row, columnStart, row, columnEnd, buffer)
except Exception as e:
sys.exit(str(e))
finally:
dataset.close()
dataset.close()
Working with georeferenced metadata¶
Version 2 of the BAG file format allows georeference metadata describing metadata on a node by node basis at the full
resolution of the dataset. This is accomplished by creating a BAG::GeorefMetadataLayer
(Python:
bagPy.GeorefMetadataLayer
) within an existing BAG. To create a georeferenced metadata layer, you first
need to create a BAG::RecordDefinition
(Python: bagPy.RecordDefinition
); see lines 122-135 in
the example C++ source code, or lines 88-101 in the example Python code below. Note, to make it easier to create BAGs
with georeferenced metadata of a known format, it is possible to create a named metadata profile, for example
NOAA_OCS_2022_10_METADATA_PROFILE
. For information on how to use a named metadata profile, see the
examples bag_georefmetadata_layer.cpp and bag_georefmetadata_layer.py in the BAG source code.
C++:
1#include <bag_metadataprofiles.h>
2#include <bag_dataset.h>
3#include <bag_metadata.h>
4#include <bag_simplelayer.h>
5#include <bag_compounddatatype.h>
6#include <bag_surfacecorrections.h>
7#include <bag_surfacecorrectionsdescriptor.h>
8#include <bag_types.h>
9
10#include <array>
11#include <cstdlib>
12#include <iostream>
13#include <memory>
14
15
16int main(int argc, char *argv[]) {
17 constexpr uint32_t kGridSize = 100;
18 constexpr uint32_t kSepSize = 3;
19
20 const std::string xmlFileName = argv[1]; // Store the XML fileName
21 const std::string outFileName = argv[2]; // Store the BAG fileName to write
22
23 BAG::Metadata metadata;
24 try {
25 metadata.loadFromFile(xmlFileName);
26 } catch (const std::exception &e) {
27 std::cerr << e.what() << std::endl;
28 return EXIT_FAILURE;
29 }
30
31 // Create the dataset.
32 std::shared_ptr<BAG::Dataset> dataset;
33 try {
34 constexpr uint64_t chunkSize = 100;
35 constexpr int compressionLevel = 1;
36 dataset = BAG::Dataset::create(outFileName, std::move(metadata),
37 chunkSize, compressionLevel);
38 } catch (const std::exception &e) {
39 std::cerr << "Error creating BAG file." << std::endl;
40 std::cerr << e.what() << std::endl;
41 return EXIT_FAILURE;
42 }
43
44 // Write the elevation layer, constructing bogus data as we do so.
45 auto elevationLayer = dataset->getSimpleLayer(Elevation);
46
47 // Set the min/max values (optional).
48 // NOTE: Layer::write() calls update min/max.
49 {
50 const std::array<float, 2> surfRange{-10.0f,
51 -10.0f - ((kGridSize - 1) * (kGridSize - 1) + kGridSize) / 10.0f};
52
53 auto pDescriptor = elevationLayer->getDescriptor();
54 pDescriptor->setMinMax(surfRange[0], surfRange[1]);
55
56 elevationLayer->writeAttributes();
57 }
58
59 // Write the data.
60 std::array<float, kGridSize> surf{};
61
62 for (uint32_t row = 0; row < kGridSize; ++row) {
63 for (uint32_t column = 0; column < kGridSize; ++column)
64 surf[column] = ((column * row) % kGridSize) +
65 (column / static_cast<float>(kGridSize));
66
67 try {
68 const auto *buffer = reinterpret_cast<uint8_t *>(surf.data());
69 constexpr uint32_t columnStart = 0;
70 constexpr uint32_t columnEnd = kGridSize - 1;
71
72 elevationLayer->write(row, columnStart, row, columnEnd, buffer);
73 } catch (const std::exception &e) {
74 std::cerr << e.what() << std::endl;
75 return EXIT_FAILURE;
76 }
77 }
78
79 // Write the uncertainty layer, constructing bogus data as we do so.
80 auto uncertaintyLayer = dataset->getSimpleLayer(Uncertainty);
81
82 // Set the min/max values (optional).
83 // NOTE: Layer::write() calls update min/max.
84 {
85 const std::array<float, 2> uncertRange{1.0f,
86 1.0f + ((kGridSize - 1) * (kGridSize - 1) + kGridSize) / 100.0f};
87
88 auto pDescriptor = uncertaintyLayer->getDescriptor();
89 pDescriptor->setMinMax(uncertRange[0], uncertRange[1]);
90
91 uncertaintyLayer->writeAttributes();
92 }
93
94 // Write the data.
95 std::array<float, kGridSize> uncert{};
96
97 for (uint32_t row = 0; row < kGridSize; ++row) {
98 for (uint32_t column = 0; column < kGridSize; ++column)
99 uncert[column] = ((column * row) % kGridSize) / 1000.0f;
100
101 try {
102 const auto *buffer = reinterpret_cast<uint8_t *>(uncert.data());
103 constexpr uint32_t columnStart = 0;
104 constexpr uint32_t columnEnd = kGridSize - 1;
105
106 uncertaintyLayer->write(row, columnStart, row, columnEnd, buffer);
107 } catch (const std::exception &e) {
108 std::cerr << e.what() << std::endl;
109 return EXIT_FAILURE;
110 }
111 }
112
113
114 // Add a georeferenced metadata layer (additional metadata) for elevation.
115 try {
116 // Not expecting more than 65535 possible records. (1st record, 0, is reserved)
117 const BAG::DataType indexType = DT_UINT16;
118 const auto& simpleLayerName = elevationLayer->getDescriptor()->getName();
119 constexpr uint64_t chunkSize = 100;
120 constexpr unsigned int compressionLevel = 1;
121
122 BAG::RecordDefinition definition(5);
123 definition[0].name = "temporal_variability";
124 definition[0].type = DT_UINT32;
125 definition[1].name = "full_coverage";
126 definition[1].type = DT_BOOLEAN;
127 definition[2].name = "feature_size";
128 definition[2].type = DT_FLOAT32;
129 definition[3].name = "survey_date_start";
130 definition[3].type = DT_STRING;
131 definition[4].name = "survey_date_end";
132 definition[4].type = DT_STRING;
133 auto& georefMetaLayer = dataset->createGeorefMetadataLayer(indexType,
134 UNKNOWN_METADATA_PROFILE, simpleLayerName, definition,
135 chunkSize, compressionLevel);
136
137 // At this point, all entries in the georeferenced metadata layer point to index 0,
138 // which is a no data value.
139 auto &valueTable = georefMetaLayer.getValueTable();
140 // Write a couple records.
141 using BAG::CompoundDataType;
142 // First metadata record
143 BAG::Record record {
144 CompoundDataType{1u}, // temporal_variability
145 CompoundDataType{true}, // full_coverage
146 CompoundDataType{9.87f}, // feature_size
147 CompoundDataType{std::string{"2019-04-01 00:00:00.0Z"}}, //survey_date_start
148 CompoundDataType{std::string{"2019-04-01 12:00:00.0Z"}}, //survey_date_end
149 };
150 // Store the new record in memory and in the BAG.
151 const auto firstRecordIndex = valueTable.addRecord(record);
152
153 // Second metadata record
154 record = BAG::Record {
155 CompoundDataType{6u}, // temporal_variability
156 CompoundDataType{false}, // full_coverage
157 CompoundDataType{12345.67f}, // feature_size
158 CompoundDataType{std::string{"2019-04-02 00:00:00.0Z"}}, //survey_date_start
159 CompoundDataType{std::string{"2019-04-02 12:00:00.0Z"}}, //survey_date_end
160 };
161 // Store the new record in memory and in the BAG.
162 const auto secondRecordIndex = valueTable.addRecord(record);
163
164 // Set up the georeferenced metadata layer to point to the new records.
165 // Let's say the first 5 rows of elevation should use the first record
166 // index, and the next 3 columns use the second record index.
167 uint32_t numRows = 0;
168 uint32_t numColumns = 0;
169 std::tie(numRows, numColumns) = dataset->getDescriptor().getDims();
170 // Start at row 0, go to (including) row 4.
171 // Use the entire column.
172 uint32_t rowStart = 0;
173 uint32_t columnStart = 0;
174 uint32_t rowEnd = 4;
175 uint32_t columnEnd = numColumns - 1;
176
177 // Create the buffer. The type depends on the indexType used when
178 // creating the georeferenced metadata layer.
179 // The buffer contains the first record's index covering the first four
180 // rows (across all the columns).
181 size_t numElements = (rowEnd - rowStart + 1) * numColumns;
182 const std::vector<uint16_t> firstBuffer(numElements, firstRecordIndex);
183
184 georefMetaLayer.write(rowStart, columnStart, rowEnd, columnEnd,
185 reinterpret_cast<const uint8_t *>(firstBuffer.data()));
186
187 // Start at row 6, go to the last row.
188 // Start at column 0, go to (including) column 2.
189 rowStart = 5;
190 columnStart = 0;
191 rowEnd = numRows - 1;
192 columnEnd = 2;
193
194 // Create the buffer. The type depends on the indexType used when
195 // creating the georeferenced metadata layer.
196 // The buffer contains the second record's index covering the first four
197 // rows (across all the columns).
198 numElements = (rowEnd - rowStart + 1) * (columnEnd - columnStart + 1);
199 const std::vector<uint16_t> secondBuffer(numElements, secondRecordIndex);
200
201 georefMetaLayer.write(rowStart, columnStart, rowEnd, columnEnd,
202 reinterpret_cast<const uint8_t *>(secondBuffer.data()));
203
204 // Read the data back.
205 // Get the georeferenced metadata layer records specified by the fifth and sixth rows,
206 // second and third columns.
207 rowStart = 4; // fifth row
208 columnStart = 1; // second column
209 rowEnd = 5; // sixth row
210 columnEnd = 2; // third column
211
212 auto buff = georefMetaLayer.read(rowStart, columnStart, rowEnd,
213 columnEnd);
214
215 // With the indices, look at some values using the value table.
216 // Room for 4 indices and initialize them with 0.
217 auto *buffer = reinterpret_cast<uint16_t *>(buff.data());
218 numElements = (rowEnd - rowStart + 1) * (columnEnd - columnStart + 1);
219
220 const auto &records = valueTable.getRecords();
221
222 for (size_t i = 0; i < numElements; ++i) {
223 const auto recordIndex = buffer[i];
224
225
226 { // Retrieve values via the ValueTable::getValue().
227 // Get survey_date_start by field name
228 const auto &surveyDateStart = valueTable.getValue(recordIndex,
229 "survey_date_start");
230 std::cout << "survey_date_start is " << surveyDateStart.asString()
231 << " from record index: " << recordIndex << std::endl;
232 // Get feature_size by field index.
233 const auto fieldIndex = valueTable.getFieldIndex("feature_size");
234 const auto &featureSize = valueTable.getValue(recordIndex,
235 fieldIndex);
236 std::cout << "feature_size is " << featureSize.asFloat()
237 << " from record index: " << recordIndex << std::endl;
238 }
239
240 // Another way to grab the values using the records directly.
241 // This only supports numerical indices.
242 {
243 // Get survey_date_start.
244 auto fieldIndex = valueTable.getFieldIndex("survey_date_start");
245 const auto &surveyDateStart = records[recordIndex][fieldIndex];
246
247 std::cout << "survey_date_start is " << surveyDateStart.asString()
248 << " from record index: " << recordIndex << std::endl;
249 // Get feature_size.
250 fieldIndex = valueTable.getFieldIndex("feature_size");
251 const auto &featureSize = records[recordIndex][fieldIndex];
252
253 std::cout << "feature_size is " << featureSize.asFloat()
254 << " from record index: " << recordIndex << std::endl;
255 }
256
257 }
258
259 }
260 catch (const std::exception &e) {
261 std::cerr << e.what() << std::endl;
262 return EXIT_FAILURE;
263 }
264
265 std::cout << "BAG with georeferenced metadata layer created" << std::endl;
266
267 return EXIT_SUCCESS;
268}
Python:
1import os
2import sys
3import struct
4
5import numpy as np
6
7import bagPy as BAG
8
9
10kGridSize: int = 100
11kSepSize: int = 3
12
13xmlFileName: str = sys.argv[1]
14outFileName: str = sys.argv[2]
15
16# Read metadata from XML file
17metadata: BAG.Metadata = BAG.Metadata()
18
19try:
20 metadata.loadFromFile(xmlFileName)
21except BAG.ErrorLoadingMetadata as e:
22 sys.exit(e.what())
23
24# Create the dataset.
25dataset: BAG.Dataset = None
26try:
27 chunkSize: int = 100
28 compressionLevel: int = 1
29
30 dataset = BAG.Dataset.create(outFileName, metadata, chunkSize, compressionLevel)
31except FileExistsError as e:
32 mesg = f"Unable to create BAG '{outFileName}'; file already exists."
33 sys.exit(mesg)
34
35# Write the elevation layer, constructing bogus data as we do so.
36surfRange = (-10.0, -10.0 - ((kGridSize - 1) * (kGridSize - 1) + kGridSize) / 10.0)
37elevationLayer: BAG.SimpleLayer = dataset.getSimpleLayer(BAG.Elevation)
38pDescriptor: BAG.LayerDescriptor = elevationLayer.getDescriptor()
39pDescriptor.setMinMax(surfRange[0], surfRange[1])
40elevationLayer.writeAttributes()
41# Write the data.
42surf: np.ndarray = np.zeros(kGridSize)
43try:
44 for row in range(kGridSize):
45 # Generate a row's worth of data in surf and write to layer
46 for column in range(kGridSize):
47 surf[column] = ((column * row) % kGridSize) + \
48 (column / float(kGridSize))
49 # columnStart and columnEnd can be moved outside of the loop, but are here for simplicity
50 columnStart: int = 0
51 columnEnd: int = kGridSize - 1
52 # can we just pass a numpy array to a LayerItem?!?!
53 buffer: BAG.FloatLayerItems = BAG.FloatLayerItems(surf)
54 elevationLayer.write(row, columnStart, row, columnEnd, buffer)
55except Exception as e:
56 dataset.close()
57 sys.exit(str(e))
58
59# Write the uncertainty layer, constructing bogus data as we do so.
60uncertRange = (-1.0, -1.0 - ((kGridSize - 1) * (kGridSize - 1) + kGridSize) / 100.0)
61uncertaintyLayer: BAG.SimpleLayer = dataset.getSimpleLayer(BAG.Uncertainty)
62pDescriptor = uncertaintyLayer.getDescriptor()
63pDescriptor.setMinMax(uncertRange[0], uncertRange[1])
64uncertaintyLayer.writeAttributes()
65# Write the data.
66uncert: np.ndarray = np.zeros(kGridSize)
67try:
68 for row in range(kGridSize):
69 # Generate a row's worth of data in uncert and write to layer
70 for column in range(kGridSize):
71 uncert[column] = ((column * row) % kGridSize) / 1000.0
72 # columnStart and columnEnd can be moved outside of the loop, but are here for simplicity
73 columnStart: int = 0
74 columnEnd: int = kGridSize - 1
75 buffer: BAG.FloatLayerItems = BAG.FloatLayerItems(uncert)
76 uncertaintyLayer.write(row, columnStart, row, columnEnd, buffer)
77except Exception as e:
78 dataset.close()
79 sys.exit(str(e))
80
81# Add a georeferenced metadata layer (additional metadata) for elevation.
82try:
83 indexType = BAG.DT_UINT16;
84 simpleLayerName: str = elevationLayer.getDescriptor().getName()
85 chunkSize: int = 100
86 compressionLevel: int = 1
87
88 definition = BAG.RecordDefinition(5)
89 definition[0].name = "temporal_variability"
90 definition[0].type = BAG.DT_UINT32
91 definition[1].name = "full_coverage"
92 definition[1].type = BAG.DT_BOOLEAN
93 definition[2].name = "feature_size"
94 definition[2].type = BAG.DT_FLOAT32
95 definition[3].name = "survey_date_start"
96 definition[3].type = BAG.DT_STRING
97 definition[4].name = "survey_date_end"
98 definition[4].type = BAG.DT_STRING
99 georefMetaLayer: BAG.GeorefMetadataLayer = dataset.createMetadataProfileGeorefMetadataLayer(indexType,
100 BAG.UNKNOWN_METADATA_PROFILE, simpleLayerName, definition,
101 chunkSize, compressionLevel)
102
103 # At this point, all entries in the georeferenced metadata layer point to index 0,
104 # which is a no data value.
105 valueTable: BAG.ValueTable = georefMetaLayer.getValueTable()
106 # Write a couple records.
107
108 # First metadata record
109 record: BAG.Record = BAG.Record(5)
110 record[0].assignUInt32(1)
111 record[1].assignBool(True)
112 record[2].assignFloat(9.87)
113 record[3].assignString("2019-04-01 00:00:00.0Z")
114 record[4].assignString("2019-04-01 12:00:00.0Z")
115 # Store the new record in memory and in the BAG.
116 firstRecordIndex: int = valueTable.addRecord(record)
117
118 record: BAG.Record = BAG.Record(5)
119 record[0].assignUInt32(6)
120 record[1].assignBool(False)
121 record[2].assignFloat(12345.67)
122 record[3].assignString("2019-04-02 00:00:00.0Z")
123 record[4].assignString("2019-04-02 12:00:00.0Z")
124 # Store the new record in memory and in the BAG.
125 secondRecordIndex: int = valueTable.addRecord(record)
126
127 # Set up the georeferenced metadata layer to point to the new records.
128 # Let's say the first 5 rows of elevation should use the first record
129 # index, and the next 3 columns use the second record index.
130 numRows, numColumns = dataset.getDescriptor().getDims()
131 # Start at row 0, go to (including) row 4.
132 # Use the entire column.
133 rowStart: int = 0
134 columnStart: int = 0
135 rowEnd: int = 4
136 columnEnd: int = numColumns - 1
137
138 # Create the buffer. The type depends on the indexType used when
139 # creating the georeferenced metadata layer.
140 # The buffer contains the first record's index covering the first four
141 # rows (across all the columns).
142 numElements: int = (rowEnd - rowStart + 1) * numColumns
143 firstBuffer: np.ndarray = np.full(numElements, firstRecordIndex, dtype=np.ushort)
144
145 buffer: BAG.UInt16LayerItems = BAG.UInt16LayerItems(firstBuffer)
146 georefMetaLayer.write(rowStart, columnStart, rowEnd, columnEnd,
147 buffer)
148
149 # Start at row 6, go to the last row.
150 # Start at column 0, go to (including) column 2.
151 rowStart = 5
152 columnStart = 0
153 rowEnd = numRows - 1
154 columnEnd = 2
155
156 # Create the buffer. The type depends on the indexType used when
157 # creating the georeferenced metadata layer.
158 # The buffer contains the second record's index covering the first four
159 # rows (across all the columns).
160 numElements = (rowEnd - rowStart + 1) * (columnEnd - columnStart + 1)
161 secondBuffer: np.ndarray = np.full(numElements, secondRecordIndex, dtype=np.ushort)
162
163 buffer: BAG.UInt16LayerItems = BAG.UInt16LayerItems(secondBuffer)
164 georefMetaLayer.write(rowStart, columnStart, rowEnd, columnEnd,
165 buffer)
166
167 # Read the data back.
168 # Get the georeferenced metadata layer records specified by the fifth and sixth rows,
169 # second and third columns.
170 rowStart = 4 # fifth row
171 columnStart = 1 # second column
172 rowEnd = 5 # sixth row
173 columnEnd = 2 # third column
174
175 buff = georefMetaLayer.read(rowStart, columnStart, rowEnd,
176 columnEnd)
177 # Cast from uint8_t into unint16_t
178 buffer_raw = bytes([b for b in buff])
179 buffer = struct.unpack_from(f"{len(buffer_raw)//2}H", buffer_raw)
180
181 numElements = (rowEnd - rowStart + 1) * (columnEnd - columnStart + 1)
182 records: BAG.Records = valueTable.getRecords()
183
184 for i in range(numElements):
185 recordIndex: int = buffer[i]
186
187 # Retrieve values via the ValueTable::getValue().
188 # Get survey_date_start by field name
189 surveyDateStart: BAG.CompoundDataType = valueTable.getValue(recordIndex,
190 "survey_date_start")
191 print(f"survey_date_start is {surveyDateStart.asString()} from record index: {recordIndex}")
192
193 # Get feature_size by field index.
194 fieldIndex: int = valueTable.getFieldIndex("feature_size")
195 featureSize: BAG.CompoundDataType = valueTable.getValue(recordIndex,
196 fieldIndex)
197 print(f"feature_size is {featureSize.asFloat()} from record index: {recordIndex}")
198
199 # Another way to grab the values using the records directly.
200 # This only supports numerical indices.
201
202 # Get survey_date_start.
203 fieldIndex: int = valueTable.getFieldIndex("survey_date_start")
204 surveyDateStart: BAG.CompoundDataType = records[recordIndex][fieldIndex]
205 print(f"survey_date_start is {surveyDateStart.asString()} from record index: {recordIndex}")
206
207 # Get feature_size.
208 fieldIndex: int = valueTable.getFieldIndex("feature_size")
209 featureSize: BAG.CompoundDataType = records[recordIndex][fieldIndex]
210 print(f"feature_size is {featureSize.asFloat()} from record index: {recordIndex}")
211
212except TypeError as e:
213 sys.exit(f"TypeError: {str(e)}")
214except Exception as e:
215 sys.exit(str(e))
216finally:
217 dataset.close()