Skip to main content
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.
The extension is defined at http://www.geopackage.org/18-000.html.

Core Concepts

ConceptDescription
Base tableThe table that owns the relationship (e.g. a features table)
Related tableThe table being linked to (e.g. a media or attributes table)
Mapping tableA join table with base_id and related_id columns
Extended relationA row in gpkgext_relations describing one relationship

Key Classes

RelatedTablesExtension

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

ValuenameDescription
RelationType.FEATURESfeaturesLink features to other features
RelationType.SIMPLE_ATTRIBUTESsimple_attributesLink features to tabular text/numeric data
RelationType.MEDIAmediaLink features or attributes to binary media (images, video)
RelationType.ATTRIBUTESattributesLink to a standard GeoPackage attributes table
RelationType.TILEStilesLink to a tiles table

Setting Up a Relationship

1

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);
2

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
);
3

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);
}

Working with Media

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}`,
  );
}

Build docs developers (and LLMs) love