The Related Tables Extension (gpkg_related_tables) lets you define formal relationships between any two tables in a GeoPackage. Relationships are stored in the gpkgext_relations catalog table, and each relationship has a corresponding user-defined mapping table that links rows from the base table to rows in the related table.
Core Concepts
| Concept | Description |
|---|
| Base table | The table that owns the relationship (e.g. a features table) |
| Related table | The table being linked to (e.g. a media or attributes table) |
| Mapping table | A join table with base_id and related_id columns |
| Extended relation | A row in gpkgext_relations describing one relationship |
Key Classes
The main entry point for working with the extension.
import { RelatedTablesExtension } from '@ngageoint/geopackage';
const rte = new RelatedTablesExtension(geoPackage);
// Initialize the extension (creates gpkgext_relations if needed)
rte.getOrCreateExtension();
// Check if the extension is present
const hasRte = rte.has();
// List all defined relationships
const relations = rte.getRelationships();
ExtendedRelation
Represents one row in gpkgext_relations. Carries the base table name, related table name, mapping table name, and relation type.
UserMappingTable and UserMappingRow
The mapping table stores the actual base_id → related_id links. UserMappingRow represents a single link.
RelationType Values
| Value | name | Description |
|---|
RelationType.FEATURES | features | Link features to other features |
RelationType.SIMPLE_ATTRIBUTES | simple_attributes | Link features to tabular text/numeric data |
RelationType.MEDIA | media | Link features or attributes to binary media (images, video) |
RelationType.ATTRIBUTES | attributes | Link to a standard GeoPackage attributes table |
RelationType.TILES | tiles | Link to a tiles table |
Setting Up a Relationship
Create the extension and related table
import {
RelatedTablesExtension,
MediaTable,
UserMappingTable,
} from '@ngageoint/geopackage';
const rte = new RelatedTablesExtension(geoPackage);
rte.getOrCreateExtension();
// Create a MediaTable for storing photos
const mediaTable = MediaTable.create('feature_photos');
rte.createRelatedTable(mediaTable);
Create the mapping table and relationship
const mappingTable = UserMappingTable.create('features_to_photos');
const relation = rte.addMediaRelationship(
'my_features', // base table
'feature_photos', // related table
mappingTable, // mapping table
);
Insert mapping rows
const mappingDao = rte.getMappingDao('features_to_photos');
const mappingRow = mappingDao.newRow();
mappingRow.setBaseId(42); // row ID in my_features
mappingRow.setRelatedId(7); // row ID in feature_photos
mappingDao.create(mappingRow);
// Get all relations where my_features is the base table
const relationsDao = rte.getExtendedRelationsDao();
const relations = relationsDao.getBaseTableRelations('my_features');
for (const relation of relations) {
const mappingDao = rte.getMappingDao(relation.mapping_table_name);
// Get all related IDs for feature row 42
const relatedIds = mappingDao.queryForRelatedIds(42);
console.log('Related IDs:', relatedIds);
}
MediaTable stores binary data (images, documents, audio) alongside a MIME type column.
import { MediaTable, MediaDao } from '@ngageoint/geopackage';
// Create the media table
const mediaTable = MediaTable.create('feature_photos');
rte.createRelatedTable(mediaTable);
// Get a DAO to insert and query media rows
const mediaDao: MediaDao = rte.getMediaDao('feature_photos');
// Insert a photo
const mediaRow = mediaDao.newRow();
mediaRow.setData(imageBuffer); // Buffer containing raw image bytes
mediaRow.setContentType('image/jpeg');
const mediaId = mediaDao.create(mediaRow);
// Read it back
const row = mediaDao.queryForId(mediaId);
const buffer = row.getData();
const mime = row.getContentType();
Working with Simple Attributes
SimpleAttributesTable stores lightweight rows of text or numeric values — useful for attaching structured facts to features without creating a full feature table.
import {
SimpleAttributesTable,
SimpleAttributesDao,
UserCustomColumn,
GeoPackageDataType,
} from '@ngageoint/geopackage';
// Define columns
const columns = [
UserCustomColumn.createColumn('source', GeoPackageDataType.TEXT),
UserCustomColumn.createColumn('accuracy_m', GeoPackageDataType.REAL),
];
const simpleTable = SimpleAttributesTable.create('feature_metadata', columns);
rte.createRelatedTable(simpleTable);
const simpleDao: SimpleAttributesDao = rte.getSimpleAttributesDao('feature_metadata');
const row = simpleDao.newRow();
row.setValue('source', 'GPS survey');
row.setValue('accuracy_m', 1.5);
const attrId = simpleDao.create(row);
SimpleAttributesTable rows must not contain geometry columns. Use RelationType.FEATURES when the related table itself has geometry.
Listing All Relationships
const allRelations = rte.getRelationships();
for (const rel of allRelations) {
console.log(
rel.base_table_name,
'→',
rel.related_table_name,
`(${rel.relation_name}) via ${rel.mapping_table_name}`,
);
}