Entities, Assets and Taxonomy
The 25.01 schema is built around three core tables: entities, entity_assets and assets.
entitiesdescribes the geographical features of the model, either a tile (a Quadtree cell aggregating many buildings,category = 0) or an individual building (category = 1) or building part (category = 2).assetsdescribes a possible building type (its taxonomy and attributes such as population and cost), independently of any specific location.entity_assetsis the junction table connecting the two: it links an entity to each asset type that could apply to it, weighted by thenumberfield.
assets.taxonomy_id / assets.tabula_taxonomy_id both reference the taxonomies table; entities.source_id references the sources table. The full field-by-field breakdown of every table is in the Database Schema page.
from exposurelib import SpatiaLiteExposure
from taxonomylib import Taxonomy
db = SpatiaLiteExposure('data/CDMX_2501_sample.db')
db.connect(init_spatial_metadata=False)
Look up a tile by Quadkey¶
Entities are identified by a Quadkey: a string of digits (0-3) that identifies a single Quadtree tile, using the same tiling scheme as Bing Maps and many other web map providers. Every tile entity in this model uses a level-18 Quadkey, roughly 150 x 150 metres at the equator.
quadkey = "023310033220023233"
tile_entity_id = db.get_tile_entity_id(quadkey)
print(tile_entity_id)
17270
This sample database is a by-building export, so the tile entity itself carries no population or structural value directly — those live on the buildings that share its Quadkey:
db.cursor.execute(
"SELECT id FROM entities WHERE quadkey = ? AND category = 1", (quadkey,)
)
building_ids = [row[0] for row in db.cursor.fetchall()]
print(f"{len(building_ids)} buildings in tile `{quadkey}`.")
44 buildings in tile `023310033220023233`.
Info on an entity¶
Once we have an entity ID, we can retrieve its full record, including its geometry, source, and taxonomy:
entity_id = 17255 # one of the buildings in the tile above
(quadkey, geometry_wkt, iso_3166, source_id, category, attributes,
building_id, geometry3d_wkb, taxonomy) = db.get_entity(entity_id)
print(f"category: {category}, iso_3166: {iso_3166}, source_id: {source_id}")
print(geometry_wkt)
category: 1, iso_3166: MEX, source_id: 2 MULTIPOLYGON(((-99.125137 19.436134, -99.125615 19.436186, -99.125658 19.435827, -99.125181 19.435775, -99.125137 19.436134)))
Understanding number and relative_size¶
entity_assets.number means something different depending on the category of the entity it is attached to:
- Building entity (
category = 1): a specific, physical building has an uncertain classification, but it is still exactly one building. The possible taxonomies are mutually exclusive outcomes, so theirnumbervalues are probabilities that add up to 1 across the entity's asset pairs. - Tile entity (
category = 0): a tile aggregates many buildings, sonumberis an expected building count, not a probability — in a tile-resolution database, summing it across a tile's own asset pairs gives the tile's total expected building count. In this by-building database, the equivalent is passing every building ID that shares a tile toget_entity_asset_pairs()at once, shown below.
relative_size is a separate, independent factor scaling for the size of a specific asset's building relative to other buildings of a similar type; it is used together with number when computing weighted totals of attributes such as population or cost.
Parsing taxonomies with taxonomylib¶
The taxonomy dictionaries returned by exposurelib are easiest to work with through taxonomylib's Taxonomy class, which turns them into a readable OXM Taxonomy string and gives access to individual attributes.
Note: JSON fields such as attributes and taxonomy come back as plain strings over a SpatiaLite connection (as dict over PostGIS) — use db.json_to_dict() to normalise them first.
# A building entity (category = 1) carries its taxonomy directly:
if category == 1 and taxonomy is not None:
building_taxonomy = Taxonomy(db.json_to_dict(taxonomy))
print(f"Building taxonomy: {building_taxonomy.get_standard_string()}")
print(f"Occupancy: {building_taxonomy.get_attribute('OCC')}")
Building taxonomy: HBET:1-5/YPRE:1975/COM1/URBAN1 Occupancy: COM1
# A tile entity does not have one taxonomy -- each of its possible assets does:
entity_asset_pairs = db.get_entity_asset_pairs([entity_id])
for _, asset_id, number, relative_size in sorted(entity_asset_pairs, key=lambda p: -p[2])[:8]:
_, taxonomy_id, *_ = db.get_asset(asset_id)
_, taxonomy_dict, _ = db.get_taxonomy(taxonomy_id)
asset_taxonomy = Taxonomy(db.json_to_dict(taxonomy_dict))
print(f"{number:.4f} buildings of taxonomy `{asset_taxonomy.get_standard_string()}`.")
total = sum(number for _, _, number, _ in entity_asset_pairs)
print(f"Sum of `number` across all {len(entity_asset_pairs)} possible taxonomies: {total:.4f}")
0.2726 buildings of taxonomy `CR+CIP/LFINF+DNO/COM1`. 0.1500 buildings of taxonomy `CR+CIP/LWAL/COM1`. 0.1500 buildings of taxonomy `CR+CIP/LFM+DUM/COM1`. 0.1500 buildings of taxonomy `CR+CIP/LFM+DNO/COM1`. 0.1149 buildings of taxonomy `MUR+MOC/LWAL/COM1`. 0.0329 buildings of taxonomy `W/LWAL/COM1/RWO3`. 0.0252 buildings of taxonomy `CR+CIP/LFINF+DNO/COM`. 0.0167 buildings of taxonomy `MUR+MOC/LWAL/COM1`. Sum of `number` across all 36 possible taxonomies: 1.0000
Taxonomy objects can also be built directly from a GEM Taxonomy string with Taxonomy.load_taxonomy_string("CR/LFINF+CDL/H:2/RES1"), useful when comparing database taxonomies against your own, for example from another exposure model. taxonomylib also provides a TaxonomyMatcher class for matching taxonomies that use different occupancy classifications against each other.
db.close()