Variable Resolution BAG Files: A Starting Guide for GIS Analysts
How to understand, read, and convert variable resolution BAGs
Variable Resolution BAG Files: A Starting Guide for GIS Analysts

Disclaimer: These views represent my own personal thoughts on this subject as a private citizen, and do not represent the views of NOAA or the US Government.
Introduction
Bathymetry Attributed Grid (BAG) files are HDF5-based geospatial data containers designed to manage and represent high-resolution bathymetric data along with uncertainty metrics. Traditionally used in hydrographic surveys and marine mapping, BAG files are purpose-built for providing detailed insights into underwater topography, communicating data quality at the pixel level through built-in uncertainty layers. But for analysts accustomed to working with single-resolution raster formats like geotiffs, BAG files — particularly those employing Variable Resolution (VR) — can feel like a complex black box. This tutorial hopes to demystify VR BAG files and make them more accessible for more conventional GIS operations.
A Variable Resolution BAG adds to the standard BAG by enabling flexible grid resolutions. This flexibility allows areas of higher interest to have finer resolutions while maintaining coarser grids elsewhere, significantly optimizing storage and performance. Because most modern bathymetric datasets are collected by multibeam echosounders mounted to the bottom of hydrographic survey or oceanographic research vessels operating at or near the surface of the water, physics often dictates that you will typically have enough data density to support higher-resolution datasets in shallower waters, and lower-resolution datasets in deeper waters due to the beam footprints expanding as a function of depth. Thus, there is a distinct advantage to variable resolution when packaging bathymetric datasets that cover a wide range of depths, say, near submarine canyons or other areas with dynamic seafloor morphologies, as the user can honor where the data can support higher-resolution depiction of the seafloor without sacrificing inefficient or inappropriate upsampling of the deeper sections that do not have adequate data density.
Each VR BAG is composed of a low-resolution representative surface and higher-resolution “supergrids” that provide detailed data where needed. Understanding how these components interact — through elevation, uncertainty, and refinement arrays — helps users extract precise information and efficiently modify or visualize data.
A common (and often valid) complaint about VR BAGs has to do with the apparent incompatibility with GIS applications and established raster geoprocessing tools. This tutorial shows a few easy ways visualize VR BAGs, and even convert VR BAGs to a single resolution geotiff dataset that could be used in downstream geoprocessing algorithms. But user beware: interpolation and/or resampling have their own potential downsides, as any conscientious GIS analyst will understand. Another way around this could be to use python tools to convert the VR BAG refinements data (or a subset thereof) into a point dataset, like geoparquet, or populate a DuckDB spatial database, and then go from there.
In this tutorial, I try to break down the structure of VR BAG files and provide a step-by-step guide for accessing, analyzing, and modifying their data using tools like GDAL, h5py, and Python libraries commonly used in geospatial analytics. Whether you’re new to BAG files or a GIS professional aiming to deepen your knowledge, this guide will hopefully equip you with enough skills to handle VR BAG files in your own analysis, or at least set you in the right direction. By the end, my goal is for you to understand how to navigate their hierarchical structure, query the BAG metadata, and even convert VR BAGs to Single Resolution geotiffs at your own chosen resolution. If you want to follow along, feel free to download a dataset or two from NOAA’s bathymetry data discovery site.
Purpose and Basics
The Bathymetry Attributed Grid (BAG) format is a widely adopted open format designed specifically for storing and analyzing bathymetric data. Developed by the Open Navigation Surface Working Group, BAG files are commonly used in hydrographic surveys, coastal management, and marine research to represent underwater topography. They enable efficient handling of large datasets, with structured storage for elevation data (depth) and corresponding uncertainty values.
At the core of a BAG file are two primary layers: the elevation layer, which stores the depth values, and the uncertainty layer, which quantifies the confidence in those depth measurements. This combination is particularly useful for hydrographers and GIS analysts, as it allows them to assess the reliability of the bathymetric data and prioritize areas for further review or analysis. BAG files are compatible with popular GIS tools like QGIS and GDAL, making them accessible and easily integrated within standard workflows. They can also be opened and explored using a utility called BAG Explorer, which is publicly available within NOAA Office of Coast Survey’s Pydro hydrographic tool suite, or other utilities such as HDFView.
What makes BAG files especially valuable is the option to contain Single Resolution (SR) or Variable Resolution (VR) datasets. While SR BAGs provide uniform spatial resolution across the entire extent of the data, VR BAGs allow for localized high-resolution grids — called supergrids — in areas of interest. This feature is particularly useful in regions with complex seafloor topography, where higher detail is required without increasing the dataset’s size for less dynamic areas.
Key Concepts and Terminology
Before diving into the technical details of Variable Resolution (VR) BAGs, let’s define the key components of the BAG structure, focusing on their roles and relationships.
Low-resolution “representative surface”
If you drag and drop a VR BAG dataset into a GIS, such as ArcGIS Pro, what you will see is a two band single-resolution raster of relatively low resolution, which could be confusing and a bit underwhelming if you were expecting a high-resolution, variable resolution dataset. This is what is colloquially called the “representative surface.” This is the default mode that the software uses to open and view the dataset. Each individual pixel of this representative surface is the container for a separate “supergrid” that contains the higher-resolution bathymetry values, called “refinements.”
QGIS has some additional functionality in supporting the visualization of VR BAGs. If a bag is opened in “INTERPOLATED” mode, a user can see the higher-resolution refinements instead of the low-resolution representative surface.
Supergrids
Supergrids are the building blocks of a VR BAG. They divide the dataset into manageable tiles, each with:
- Uniform resolution: All the cells within a supergrid share the same resolution.
- Defined geographic extent: Each supergrid has specific boundaries in geographic space, which are described in the metadata.
- Low-resolution elevation and uncertainty values: Each supergrid has one representative elevation and uncertainty value. Normal convention in hydrography is for these values to be shoal-biased, (i.e. the maximum elevation value, or minimum depth value). These provide the values shown in the “representative surface” for the data within the supergrid.
Supergrids simplify the management and representation of the dataset by grouping data into regularly spaced tiles.

Image 1: Displaying a VR BAG’s low-resolution “representative surface.” This VR BAG is somewhere in Puget Sound, Washington. The grid resolution of this low-resolution surface is 23.212461m.
Refinements
Refinements are a specialized feature of VR BAGs that add localized, higher-resolution data to areas within supergrids. Instead of uniformly refining the entire supergrid (which would be computationally expensive), refinements:
- Apply to specific sub-areas within a supergrid, where higher precision is needed.
- Provide additional elevation and uncertainty values at finer resolutions.
- Are stored in a separate dataset (varres_refinements), making them distinct from the low-resolution elevation and uncertainty layers of the representative surface.
- An intentional small spatial gap between the origin point of the supergrid and the start of the refinements is defined in the varres_metadata layer.
The presence of refinements ensures the BAG can capture high-resolution details efficiently, without increasing the resolution (and size) of the entire dataset.
Image 2: A view of the refinements that make up the supergrid contained in one single pixel of the low-resolution grid. The grid resolution of this supergrid is 1m.
Metadata
Metadata in a BAG plays a crucial role in linking supergrids and refinements. Key elements include:
- BAG_root/metadata — XML Metadata: This is a human-readable file embedded in the BAG, containing information like the purpose of the dataset, survey methods, and creation details. It includes critical information for georeferencing the dataset, including the coordinate reference system (usually in a WKT definition), vertical datum, and the geotransform.
An aside regarding the BAG geotransform:
- The origin coordinates are defined from the bottom-left (southwest) corner of the grid. note that this is different than the geotiff standard, and many other GIS raster dataset standards Most GIS programs and GDAL account for this automatically, but this is really important to know if you decide to use numpy and h5py to edit/modify your BAG data.
- The cell size in both X and Y directions.
- The rotation terms (usually 0 for north-up grids).
The geotransform allows geographic coordinates (latitude/longitude or projected X/Y) to be converted to grid indices and vice versa. The geotransform data can be found in the BAG_root/metadata xml layer, and is automatically read and interpreted by GDAL.
- varres_metadata: This dataset encodes the boundaries, resolutions, and refinement locations for each supergrid. It acts as the roadmap for navigating the relationships between supergrids and refinements.
Tracking Lists
To support versioning and modification tracking, BAGs include:
- tracking_list: For single-resolution grids, this tracks edits to elevation and uncertainty values, such as what hydrographers call “designated soundings.”
- varres_tracking_list: For VR grids, this tracks edits (designated soundings) to refinement data at higher resolutions. It provides not only the row and column address of the supergrid, but also the sub-row and sub-column address of the refinements within the supergrid.
**Practical examples working with VR BAGs in python using h5py and GDAL **- I used python 3.11 and GDAL version 3.8.4 for these examples. You might get different mileage if you use different versions than me -
Opening and visualizing the low-resolution representative surface using h5py:
import h5py
import numpy as np
import matplotlib.pyplot as plt
def extract_elevation_array(bag_file):
"""
Extracts the elevation array from a BAG file.
Parameters:
- bag_file: Path to the BAG file.
Returns:
- elevation_array: The extracted elevation data.
"""
with h5py.File(bag_file, 'r') as bag:
# Access the elevation dataset
elevation_dataset = bag['/BAG_root/elevation']
elevation_array = elevation_dataset[()]
return elevation_array
def visualize_elevation_array(elevation_array):
"""
Visualizes the elevation array using matplotlib.
Parameters:
- elevation_array: 2D numpy array of elevation values.
"""
nodata_value = 1000000 # NoData value for BAGs
# Mask the NoData values for visualization
masked_array = np.ma.masked_equal(elevation_array, nodata_value)
# Create the plot
plt.figure(figsize=(12, 12))
cmap = plt.get_cmap("rainbow")
cmap.set_bad(color="lightgray") # Set color for NoData values
plt.imshow(masked_array, cmap=cmap, origin="lower") # Need to set origin to "lower" since BAG origin is SW corner
plt.colorbar(label="Elevation (m)")
plt.title("Elevation Array Visualization using h5py library")
plt.xlabel("Column Index")
plt.ylabel("Row Index")
plt.grid(False)
plt.show()
def process_bag():
# Path to the BAG file
bag_file = r"D:\example_bag_path\F00867_VR.bag"
try:
# Step 1: Extract the elevation array
elevation_array = extract_elevation_array(bag_file)
# Step 2: Visualize the elevation array
visualize_elevation_array(elevation_array)
except Exception as e:
print(f"Error: {str(e)}")
if __name__ == "__main__":
process_bag()
Image 3: Example output from visualizing the low-resolution elevation array extracted using h5py and visualized with matplotlib
Opening and reading the BAG xml metadata using h5py and etree:
import h5py
import xml.etree.ElementTree as ET
import numpy as np
# Define XML namespaces - these are ones normally used in BAGs
namespaces = {
'gmd': 'http://www.isotc211.org/2005/gmd',
'gco': 'http://www.isotc211.org/2005/gco',
'gmi': 'http://www.isotc211.org/2005/gmi',
'gml': 'http://www.opengis.net/gml/3.2',
'bag': 'http://www.opennavsurf.org/schema/bag',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance'
}
# Register namespaces for parsing using the etree library for xml
ET.register_namespace("gmd", namespaces['gmd'])
ET.register_namespace("gco", namespaces['gco'])
ET.register_namespace("gmi", namespaces['gmi'])
ET.register_namespace("gml", namespaces['gml'])
ET.register_namespace("bag", namespaces['bag'])
ET.register_namespace("xsi", namespaces['xsi'])
def get_bag_metadata(file_path):
"""
Extracts XML metadata from a BAG file and returns it as an ElementTree object.
"""
with h5py.File(file_path, 'r') as bag_file:
# Access the XML metadata stored in the BAG_root/metadata dataset
xml_metadata = bag_file['BAG_root']['metadata'][()]
if isinstance(xml_metadata, np.ndarray):
# Join byte array and decode to string
xml_metadata_str = b''.join(xml_metadata).decode('utf-8')
else:
raise TypeError("Unexpected XML metadata format in BAG file.")
# Parse the XML string into an ElementTree object
metadata_tree = ET.fromstring(xml_metadata_str)
return metadata_tree
def save_metadata_to_file(metadata, output_file):
"""
Saves the XML metadata to a file in a human-readable format.
"""
try:
# Convert ElementTree object to a string
metadata_str = ET.tostring(metadata, encoding='unicode')
parsed_tree = ET.ElementTree(ET.fromstring(metadata_str))
# Pretty-print the metadata
print("XML Metadata:")
ET.dump(parsed_tree) # Dumps a prettified version of the XML to the console
# Save the XML to a file
with open(output_file, 'w', encoding='utf-8') as f:
f.write(metadata_str)
print(f"XML metadata saved to: {output_file}")
except Exception as e:
print(f"Error saving metadata to file: {str(e)}")
def process_bag():
# Input BAG file
bag_file = r"D:\example_bag_path\H13342_MB_2m_MLLW_2of2.bag"
# Output XML file
output_file = r"D:\example_bag_path\H13342_metadata.xml"
try:
# Step 1: Extract metadata
metadata = get_bag_metadata(bag_file)
# Step 2: Save metadata to a file
save_metadata_to_file(metadata, output_file)
except Exception as e:
print(f"Error: {str(e)}")
if __name__ == "__main__":
process_bag()

Image 4: Sample snippet of extracted BAG xml metadata
Converting a VR BAG to a Single Resolution Geotiff using gdal’s BAG driver in INTERPOLATED mode:
INTERPOLATE mode will fill/interpolate between gaps that could be present based on supergrid resolutions
from osgeo import gdal
bag_file = r"D:\example_bag_path\F00867_VR.bag"
# Open BAG in INTERPOLATED mode
dataset = gdal.OpenEx(bag_file, gdal.OF_RASTER, open_options=[
"MODE=INTERPOLATED",
"RESX=2",
"RESY=2",
"VALUE_POPULATION=MIN"
])
if dataset:
print("Opened BAG in INTERPOLATED mode.")
print("Geotransform:", dataset.GetGeoTransform())
print("Projection:", dataset.GetProjection())
# Save interpolated grid as GeoTIFF
gdal.Translate(r"D:\example_bag_path\interpolated_grid_2m.tif", dataset, format="GTiff", options=["COMPRESS=LZW"])
print("Exported interpolated grid to 'interpolated_grid_2m.tif'")
else:
print("Failed to open BAG in INTERPOLATED mode.")
Image 5: A 2m Single Resolution Geotiff derived from a VR BAG using gdal’s INTERPOLATED mode
Converting a VR BAG to a Single Resolution Geotiff using gdal’s bag driver in RESAMPLED_GRID mode:
RESAMPLED_GRID mode will not fill/interpolate between gaps that could be present based on supergrid resolutions
from osgeo import gdal
bag_file = r"D:\example_bag_path\F00867_VR.bag"
# Open BAG in RESAMPLED_GRID mode
dataset = gdal.OpenEx(bag_file, gdal.OF_RASTER, open_options=[
"MODE=RESAMPLED_GRID",
"RESX=2",
"RESY=2",
"VALUE_POPULATION=MIN", # Other options: MIN, MAX, COUNT
])
if dataset:
print("Opened BAG in RESAMPLED_GRID mode.")
print("Geotransform:", dataset.GetGeoTransform())
print("Projection:", dataset.GetProjection())
# Save resampled grid as GeoTIFF
gdal.Translate(r"D:\example_bag_path\resampled_grid_2m.tif", dataset, format="GTiff", options=["COMPRESS=LZW"])
print("Exported resampled grid to 'resampled_grid_2m.tif'")
else:
print("Failed to open BAG in RESAMPLED_GRID mode.")
Image 6: A 2m Single Resolution Geotiff derived from a VR BAG using gdal’s RESAMPLE_GRID mode
메타데이터
- post_id
- 89bb3aeb336a
- slug
- variable-resolution-bag-files-a-starting-guide-for-gis-analysts-89bb3aeb336a
- url
- https://medium.com/@anthony.klemm/variable-resolution-bag-files-a-starting-guide-for-gis-analysts-89bb3aeb336a
- canonical_url
- https://medium.com/@anthony.klemm/variable-resolution-bag-files-a-starting-guide-for-gis-analysts-89bb3aeb336a
- author_url
- https://medium.com/@anthony.klemm
- status
- ok
- fetched_at
- 2026-06-24 16:30:55