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:

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()