Skip to content

Estate Structures Domain

The Estate Structures bounded context (estate_structures_v1) manages the hierarchical organization of physical assets and spaces within a company’s property portfolio. It provides comprehensive domain models for organizing estates, sites, buildings, rooms, and their spatial relationships through a topology system that tracks geographic features and floor plans.

This domain answers the question: “How is our physical portfolio organized, and where are things located?”

It handles:

  • Hierarchical structure: Estate → Site → Building → Room organization
  • Spatial relationships: Geographic features, boundaries, footprints, and topology
  • Floor plans: Visual representations with tile-based floor plan management
  • Asset placement: Positioning of buildings and rooms within sites
  • Change tracking: Versioning of topology and floor plan changes

The domain models a four-level hierarchy that organizes physical assets:

Estate (root aggregate)
├── Site (site aggregate)
│ ├── Building (building aggregate)
│ │ └── Room (room aggregate)
│ └── Topology (geographic features and spatial data)
└── Floor Plans (visual layer for site floors)

The Estate is the root aggregate representing a company’s entire property portfolio. There is exactly one estate per workspace.

Responsibilities:

  • Maintain metadata about the organization’s property portfolio
  • Track the headquarters site
  • Store organizational contact information
  • Define the top-level container for all sites and buildings

Key Properties:

class EstateAggregate {
final EstateId estateId;
final EstateName name;
final EstateDescription? description;
final PostalAddress? address;
final UserId? primaryContact;
final SiteId? headquartersSiteId;
final OrganizationName? officialOrganisationName;
final UserId createdBy;
final DateTime createdAt;
final DateTime updatedAt;
}

Example Creation:

// Estate created event - represents a new estate in the system
EstateCreatedEvent(
estateId: EstateId('estate-001'),
name: EstateName('Acme Corporation Properties'),
description: EstateDescription('Central property portfolio'),
address: PostalAddress(
streetAddress: '123 Business Ave',
city: 'London',
countryCode: 'GB',
),
createdBy: UserId('user-123'),
createdAt: DateTime.now(),
)

The Site aggregate represents a physical location within the estate (e.g., a campus, office building, warehouse).

Responsibilities:

  • Manage site metadata and metadata (name, description, location)
  • Track all buildings within the site
  • Maintain the published topology version
  • Coordinate spatial structure and floor plan information

Key Properties:

class SiteRootAggregate {
final SiteId siteId;
final EstateId estateId;
final SiteName name;
final SiteDescription? description;
final SiteTypeEnum? siteType; // office, warehouse, retail, etc.
final SiteLocation location;
final TopologyVersion? publishedTopologyVersion;
final List<BuildingId> buildingIds;
final UserId createdBy;
final DateTime createdAt;
final DateTime updatedAt;
}

Site Location Structure:

class SiteLocation {
final PostalAddress? postalAddress;
final GeoPoint? geoPoint; // Latitude/Longitude
final String? countryCode; // ISO country code
}

Example Creation:

SiteCreatedEvent(
siteId: SiteId('site-london-hq'),
estateId: EstateId('estate-001'),
name: SiteName('London Headquarters'),
siteType: SiteTypeEnum.office,
address: PostalAddress(
streetAddress: '100 Main Street',
city: 'London',
countryCode: 'GB',
),
mapMarkerPosition: GeoPoint(latitude: 51.5074, longitude: -0.1278),
createdBy: UserId('user-123'),
createdAt: DateTime.now(),
)

The Building aggregate represents a physical structure within a site.

Responsibilities:

  • Maintain building metadata (name, description, usage type)
  • Track geometric placement (anchor point or full footprint)
  • Store unique property reference (UPRN)
  • Link to the parent site

Key Properties:

class BuildingAggregate {
final BuildingId buildingId;
final SiteId siteId;
final BuildingName name;
final BuildingDescription? description;
final BuildingUsageType? usageType; // office, retail, residential, etc.
final UniquePropertyReference? uprn; // UK property identifier
final GeoJSONPoint? anchor; // Single point location
final FeaturePointer? geometry; // Full polygon geometry
final UserId createdBy;
final DateTime createdAt;
final DateTime updatedAt;
}

Placement Status:

A building is considered “placed” if it has either:

  • A geometry (full polygon footprint)
  • An anchor point (single location)
// Check if building has geographic placement
bool get isPlaced => geometry != null || anchor != null;

Example Creation:

BuildingCreatedEvent(
buildingId: BuildingId('bldg-001'),
siteId: SiteId('site-london-hq'),
name: BuildingName('Main Office Tower'),
usageType: BuildingUsageType.office,
uprn: UniquePropertyReference('123456789'),
createdBy: UserId('user-123'),
createdAt: DateTime.now(),
)

The Room aggregate represents an indoor space within a site or building.

Responsibilities:

  • Maintain room metadata (name, description, type)
  • Track room geometry (polygon outline)
  • Link to parent site
  • Support room type taxonomy

Key Properties:

class RoomAggregate {
final RoomId roomId;
final SiteId siteId;
final RoomName name;
final RoomDescription? description;
final RoomTypeEnum? roomType; // office, meeting, storage, etc.
final FeaturePointer? geometry; // Room boundary polygon
final UserId createdBy;
final DateTime createdAt;
final DateTime updatedAt;
}

Example Creation:

RoomCreatedEvent(
roomId: RoomId('room-001'),
siteId: SiteId('site-london-hq'),
name: RoomName('Meeting Room A'),
roomType: RoomTypeEnum.meeting,
createdBy: UserId('user-123'),
createdAt: DateTime.now(),
)

The domain includes a sophisticated topology system for managing spatial relationships and geographic features.

GeoFeature represents a geographic element within a site’s topology (building boundaries, room outlines, site boundaries, etc.).

Key Properties:

class GeoFeature {
final GeoFeatureKey key; // Stable identifier
final GeoFeatureKind geoFeatureKind; // Type of feature
final BuildingLevel? level; // Floor number
final BuildingId? buildingId; // Parent building
final SiteBoundaryId? siteBoundaryId; // Site boundary reference
final RoomId? roomId; // Room reference
final GeoJSONGeometry? geometry; // Polygon/LineString/Point
final List<double>? bbox; // [west, south, east, north]
final List<String>? covering; // S2 cells or geohashes
final List<double>? zRangeMeters; // [zMin, zMax)
final GeometryOrientation? geometryOrientation;
final GeoFeatureName? name;
final TaxonomyEnum? usageType;
}

Feature Kinds:

The domain supports multiple types of geographic features:

enum GeoFeatureKind {
siteBoundary, // Overall site perimeter
buildingFootprint, // Building outline
unplacedBuilding, // Building without location
room, // Indoor space
virtualFootprint, // Computed from floor plans
generic // Fallback type
}

Feature IDs:

Each feature generates a canonical feature ID based on its kind:

// buildingFootprint -> "building-${buildingId}"
// room -> "room-${buildingId}-${level}-${roomId}"
// siteBoundary -> uses siteBoundaryId value directly
GeoFeatureId get featureId { ... }

SiteTopologyAggregate manages all geographic features and spatial relationships for a single site.

Responsibilities:

  • Aggregate all geographic features (features collection)
  • Track topology versioning (topologySequence)
  • Manage floor plan tile metadata
  • Link features to attachments (PDFs, images)
  • Support draft and published topology states

Key Properties:

class SiteTopologyAggregate {
final SiteId siteId;
final List<GeoFeature> features;
final bool isPublished;
final Map<FloorPlanImageTileId, FloorPlanTileMeta> floorPlanTiles;
final Map<GeoFeatureKey, FeatureAttachmentRef> featureAttachments;
final int topologySequence;
final UserId createdBy;
final DateTime createdAt;
final DateTime updatedAt;
}

Topology States:

  • Draft: Mutable topology in editing mode, can be modified before publishing
  • Published: Immutable topology, represents a stable version

A newer topology version aggregate with enhanced spatial capabilities:

Key Properties:

class SiteTopologyVersionAggregate {
final SiteId siteId;
final SiteTopologyVersionId versionId;
final bool isDraft;
final GeoJSONPolygon? boundary; // Site extent
final Map<BuildingId, GeoJSONPolygon> buildingFootprints;
final Map<RoomId, GeoJSONPolygon> rooms;
final Map<MapTileId, MapTile> mapTiles;
final UserId createdBy;
final DateTime createdAt;
final DateTime updatedAt;
}

The domain includes a tile-based floor plan system for visual floor representations.

FloorPlanTile represents a single visual tile of a floor plan (e.g., a scanned floor plan image with positioning).

Properties:

class FloorPlanTile {
final String tileId;
final GeoJSONGeometry shape; // Polygon or MultiPolygon in site CRS
final String mediaRef; // URI to image/PDF
final double opacity; // 0.0 to 1.0
final int zIndex; // Render order
final Map<String, dynamic>? affineTransform; // Scale/rotation metadata
}

FloorPlanTileSetAggregate manages a collection of floor plan tiles for a specific floor/level.

Responsibilities:

  • Aggregate multiple floor plan tiles
  • Manage tile lifecycle (add, update, remove)
  • Track publication state
  • Coordinate with topology for spatial reference

Example Structure:

FloorPlanTileSet (for Level "Ground Floor")
├── FloorPlanTile (South Wing)
├── FloorPlanTile (East Wing)
└── FloorPlanTile (West Wing)

The domain emits rich events to track all state changes. These events are persisted and can be replayed to reconstruct aggregate state.

EstateCreatedEvent - When a new estate is created:

class EstateCreatedEvent {
final EstateId estateId;
final EstateName name;
final EstateDescription? description;
final PostalAddress? address;
final SiteId? headquartersSiteId;
final OrganizationName? officialOrganisationName;
final UserId createdBy;
final DateTime createdAt;
}

EstateUpdatedEvent - When estate properties change

UserAddedToEstateLedgerEvent - When a user is granted access to an estate

SiteCreatedEvent - When a new site is created

SiteUpdatedEvent - When site metadata changes

SiteLocationUpdatedEvent - When location information changes

SiteTopologyPublishedEvent - When topology is published

BuildingCreatedEvent - When a new building is added

class BuildingCreatedEvent {
final BuildingId buildingId;
final SiteId siteId;
final BuildingName name;
final BuildingDescription? description;
final BuildingUsageType? usageType;
final UniquePropertyReference? uprn;
final UserId createdBy;
final DateTime createdAt;
}

BuildingUpdatedEvent - When building properties change

BuildingFootprintSetEvent - When building geometry is added/updated

BuildingGeometryUpdatedEvent - When geometric placement changes

RoomCreatedEvent - When a new room is created

RoomUpdatedEvent - When room properties change

RoomGeometryUpdatedEvent - When room geometry is set/changed

SiteBoundarySetEvent - When site boundary polygon is set

FloorFootprintSetEvent - When floor/building footprint is set

FloorPlanImageTransformSetEvent - When floor plan positioning is adjusted

SiteBoundaryRenamedEvent - When boundary is renamed

MapTileAddedEvent - When a new map tile is added to topology

SiteTopologyPublishedEvent - When complete topology is published

TopologyValidationFailedEvent - When topology fails validation

FloorPlanTileSetPublishedEvent - When floor plan tiles are published

Directives are commands that trigger state changes. The domain provides directives for all major operations.

CreateEstateDirective - Create a new estate

class CreateEstatePayload {
final EstateId estateId;
final EstateName name;
final EstateDescription? description;
final PostalAddress? address;
final UserId createdBy;
}

UpdateEstateDirective - Update estate properties

DeleteEstateDirective - Remove an estate

CreateSiteDirective - Create a new site

class CreateSitePayload {
final SiteId siteId;
final EstateId estateId;
final SiteName name;
final SiteDescription? description;
final SiteTypeEnum? siteType;
final PostalAddress? address;
final GeoPoint? mapMarkerPosition;
final UserId createdBy;
}

DeleteSiteDirective - Remove a site

SetSiteLocationDirective - Update site location

CreateBuildingDirective - Create a new building

class CreateBuildingPayload {
final BuildingId buildingId;
final SiteId siteId;
final BuildingName name;
final BuildingDescription? description;
final BuildingUsageType? usageType;
final UniquePropertyReference? uprn;
final UserId createdBy;
}

UpdateBuildingDirective - Update building properties

ApplyBuildingFootprintV2Directive - Set building geometry in V2 topology

CreateRoomDirective - Create a new room

AddRoomGeometryDirective - Set room geometry

SetSiteBoundaryDirective - Set site boundary polygon

SetFloorFootprintDirective - Set floor/building footprint

ModifySiteBoundaryDirective - Modify existing boundary polygon

ModifyFloorFootprintDirective - Modify floor footprint polygon

RenameSiteBoundaryDirective - Rename boundary

PublishSiteTopologyDirective - Publish complete topology

OpenDraftTopologyDirective - Start editing a topology version

AddMapTileV2Directive - Add map tile to V2 topology

SetBoundaryV2Directive - Set boundary in V2 topology

PublishTopologyVersionDirective - Publish V2 topology version

AddFloorPlanTileDirective - Add a floor plan tile

class AddFloorPlanTilePayload {
final FloorPlanTileSetId tileSetId;
final FloorPlanTile tile;
final UserId createdBy;
}

UpdateFloorPlanTileDirective - Modify tile properties

RemoveFloorPlanTileDirective - Delete a floor plan tile

PublishFloorPlanTileSetDirective - Publish floor plan tiles

The domain uses strongly-typed value objects for all identifiers and concepts:

  • EstateId - Unique estate identifier
  • SiteId - Unique site identifier within an estate
  • BuildingId - Unique building identifier within a site
  • RoomId - Unique room identifier within a site
  • SiteBoundaryId - Identifies site boundary features
  • GeoFeatureKey - Stable key for geographic features
  • GeoFeatureId - Computed canonical ID derived from typed IDs
  • BuildingLevel - Floor/level number (e.g., “Ground”, “1st Floor”, “-1” for basement)
  • SiteTopologyVersionId - Version identifier for topology versions
  • FloorPlanImageTileId - Identifier for floor plan tiles
  • MapTileId - Identifier for map tiles
  • EstateName - Name of the estate
  • EstateDescription - Narrative description
  • SiteName - Name of the site
  • SiteDescription - Site narrative description
  • BuildingName - Building name
  • BuildingDescription - Building narrative description
  • RoomName - Room name
  • RoomDescription - Room narrative description
  • GeoFeatureName - Name for geographic features
  • SiteTypeEnum - office, warehouse, retail, manufacturing, etc.
  • BuildingUsageType - office, retail, residential, storage, etc.
  • RoomTypeEnum - office, meeting, storage, restroom, etc.
  • BoundaryUsageType - Taxonomy for boundary types
  • GeoFeatureKind - Type of geographic feature (siteBoundary, buildingFootprint, room, etc.)
  • GeometryOrientation - Orientation information for polygon features
  • TopologyVersion - Version string for published topologies
  • SiteLocation - Composite location (postal address + geo point + country code)
  • PostalAddress - Mailing address details
  • GeoPoint - Latitude/longitude pair
  • UniquePropertyReference - UPRN (UK property identifier)
  • FeaturePointer - Reference to a feature and its geometry
  • VirtualTileFootprint - Computed footprint from floor plan tiles
  • GeoJSONGeometry - Standard GeoJSON geometry (Point, Polygon, MultiPolygon, etc.)
  • MapTile - Map tile with metadata

The Estate Structures domain relates to other domains:

Trackable Asset Domain (trackable_asset_v1)

  • Assets are positioned within rooms/buildings/sites
  • Asset position selectors query this domain for location hierarchy
  • Assets emit position change events tracked here

Catalogues Domain (catalogues_v1)

  • Buildings and rooms reference taxonomy for usage types
  • Building and room type classifications use catalogue taxonomy

Contracts Domain (contracts_v1)

  • Value objects are imported from contracts (address, locations, etc.)
  • Shared identity types across domains

Permissions Domain (permissions_v1)

  • Estate structures define permission scopes
  • Users have access to specific estates/sites

Identity Domain (identity_v1)

  • Tracks which users created/modified structures
  • Audit trail of changes by user

The domain includes domain services for complex operations:

Locates the baseline (published) topology for a site:

  • Finds the most recent published topology version
  • Resolves draft vs. published states
  • Used for asset positioning and spatial queries

Tracks changes to topology between versions:

  • Compares two topology states
  • Identifies feature additions, modifications, removals
  • Supports change notification and reconciliation

Tracks changes to floor plan tile sets:

  • Compares tile collections
  • Identifies tile order changes, opacity/transform changes
  • Supports incremental updates

Manages attachments linked to geographic features:

  • Associates PDFs, images with features
  • Supports page selection for multi-page PDFs
  • Change tracking for attachment references

Computes virtual building footprints from floor plans:

  • Aggregates tile geometry to estimate building envelope
  • Handles overlapping tiles and gaps
  • Provides virtual footprint for untraced buildings

Compares topology versions:

  • Detailed feature-by-feature comparison
  • Spatial difference analysis
  • Used for validation and reconciliation

Validates topology structure and spatial coherence:

  • Checks feature consistency
  • Validates geometry quality
  • Ensures hierarchy integrity

Other domains subscribe to estate structures events to synchronize state:

// Example: Asset domain listening for building placement changes
stream.where((event) => event is BuildingFootprintSetEvent)
.listen((event) {
// Update asset positions for assets in this building
});
OperationInitiatorEventConsumer
Create EstateAdmin UIEstateCreatedEventPermissions, Identity
Create SiteSite AdminSiteCreatedEventAsset Domain
Create BuildingSite AdminBuildingCreatedEventAsset Domain
Create RoomSite AdminRoomCreatedEventAsset Domain
Set BoundarySite AdminSiteBoundarySetEventAsset Domain, Topology V2
Set FootprintSite AdminBuildingFootprintSetEventAsset Domain
Add TileSite AdminMapTileAddedEventTopology Validation
Publish TopologySite AdminSiteTopologyPublishedEventAll subscribers
// Step 1: Create estate via directive
final createEstateDirective = CreateEstateDirective(
payload: CreateEstatePayload(
estateId: EstateId('estate-acme'),
name: EstateName('Acme Corporation'),
address: PostalAddress(
streetAddress: '123 Business Ave',
city: 'London',
countryCode: 'GB',
),
createdBy: UserId('user-admin'),
),
);
// Step 2: Create site via directive
final createSiteDirective = CreateSiteDirective(
payload: CreateSitePayload(
siteId: SiteId('site-london-hq'),
estateId: EstateId('estate-acme'),
name: SiteName('London Headquarters'),
siteType: SiteTypeEnum.office,
address: PostalAddress(
streetAddress: '100 Main Street',
city: 'London',
countryCode: 'GB',
),
mapMarkerPosition: GeoPoint(
latitude: 51.5074,
longitude: -0.1278,
),
createdBy: UserId('user-admin'),
),
);
// Create building
final buildingDirective = CreateBuildingDirective(
payload: CreateBuildingPayload(
buildingId: BuildingId('bldg-main-tower'),
siteId: SiteId('site-london-hq'),
name: BuildingName('Main Tower'),
usageType: BuildingUsageType.office,
uprn: UniquePropertyReference('123456789'),
createdBy: UserId('user-admin'),
),
);
// Set building footprint (boundary polygon)
final footprintDirective = ApplyBuildingFootprintV2Directive(
payload: ApplyBuildingFootprintV2Payload(
siteId: SiteId('site-london-hq'),
buildingId: BuildingId('bldg-main-tower'),
footprint: GeoJSONPolygon(
coordinates: [
[
[51.5070, -0.1280],
[51.5075, -0.1280],
[51.5075, -0.1270],
[51.5070, -0.1270],
[51.5070, -0.1280],
]
],
),
createdBy: UserId('user-admin'),
),
);
final roomDirective = CreateRoomDirective(
payload: CreateRoomPayload(
roomId: RoomId('room-meeting-a'),
siteId: SiteId('site-london-hq'),
name: RoomName('Meeting Room A'),
roomType: RoomTypeEnum.meeting,
createdBy: UserId('user-admin'),
),
);
// Add room geometry
final geometryDirective = AddRoomGeometryDirective(
payload: AddRoomGeometryPayload(
roomId: RoomId('room-meeting-a'),
siteId: SiteId('site-london-hq'),
geometry: FeaturePointer(...),
updatedBy: UserId('user-admin'),
),
);
// Publish the complete topology version
final publishDirective = PublishTopologyVersionDirective(
payload: PublishTopologyVersionPayload(
siteId: SiteId('site-london-hq'),
versionId: SiteTopologyVersionId('v1-2024-01-15'),
createdBy: UserId('user-admin'),
),
);

The domain includes comprehensive test coverage:

  • GeoFeature Tests - Feature ID generation, JSON serialization, kind classification
  • TopologyIndex Tests - Feature lookup and spatial indexing
  • Event Tests - Event creation, serialization, reconstruction
  • SiteBaseline Tests - Baseline topology locator functionality
  • VirtualFootprint Tests - Footprint calculation from tiles
  • Registration Tests - Type registry and directive registration

Path: dart_packages/co2/domains/estate_structures_v1

Dependencies:

  • nomos_core - Event sourcing framework
  • foundation_geometry_v1 - Geometric utilities and polygon operations
  • contracts_v1 - Shared value objects and contracts

Version: 1.0.0

Dart SDK: >=3.6.0 <4.0.0