Skip to content

Mall Floor Plans: Vector-First Architecture

Product Direction

MallGuide floor plans are vector-first.

  • Shopper-facing floor maps must render from IMDF vector data only.
  • Source floor images are ingest/verification artifacts, not the user map layer.
  • No shopper UI should rely on image-pin overlays for map rendering.

Canonical Data Model

  1. Geometry (/features)
  • Units: Feature + geometry.type=Polygon + properties.featureType=unit
  • Amenities/icons: Feature + geometry.type=Point + properties.featureType=amenity
  • Cross-floor connectors: set properties.connectorGroupId on lift/escalator/stairs amenities that are the same physical shaft/run between floors.
  • Coordinates are normalized to [0,1] range relative to the floor plan image.
  • Deleting a unit feature cascade-deletes all linked occupancies.
  1. Tenant identity (/unit-occupancies)
  • Link a unit polygon to a tenant via:
    • unitFeatureId
    • storeId and/or brandId
    • optional outletName
  • At least one of storeId, brandId, or outletName is required.
  • Only one active occupancy per unit at a time — creating a new active occupancy atomically deactivates existing ones.
  1. Branding/media
  • Logos come from store/brand records (store.logoURL, brand.logoURL).
  • Logos are rendered on vector maps via occupancy linkage, not stored in IMDF geometry.
  1. Floor plan image & aspect ratio
  • FloorData.floorPlanImageURL — the active floor plan source image.
  • FloorData.aspectRatio — width/height ratio, computed server-side on upload via POST /floorplan.
  • Client-side fallback: loads the image to read naturalWidth/naturalHeight if aspectRatio is not set.
  1. Source images (/source-images)
  • Collection of all source images for a floor (operator plans, Apple Maps, Google Maps, screenshots).
  • Rich metadata: quality, coverage, orientation, distortion, AI extraction status.
  • Enums defined in @mallguide/shared: floorSourceImageKinds, floorSourceImageStatuses, floorSourceImageQualities, etc.
  • First uploaded source image auto-sets as the floor plan image if none exists.
  1. Store placement fallback
  • Stores remain floor-scoped (mallId + floorId); there is no mall-level store collection without floors.
  • If a store create request targets a missing floor, the API auto-creates that floor before saving the store.
  • Preferred metadata fields on store create requests:
    • floorName
    • floorLevel
    • floorEditorMode
  • If those fields are omitted, the API creates a placeholder floor named Auto-created floor (<floorId>).
  • floorEditorMode falls back to the first submitted store position mode (image, grid, or list), otherwise list.

API Responsibilities

  • GET /malls/{mallId}/floors/{floorId}/vector-map

    • Primary read endpoint for floor plan rendering.
    • Returns: featureCollection, unitOccupancies, activeUnitOccupanciesByFeatureId, floorPlanImageURL, aspectRatio.
  • POST /malls/{mallId}/floors/{floorId}/floorplan

    • Upload floor plan image (PNG, JPEG, WebP).
    • Computes and stores aspectRatio from image dimensions.
    • Returns: { floorPlanImageURL, aspectRatio }.
  • GET/POST/PATCH/DELETE /malls/{mallId}/floors/{floorId}/source-images

    • Full CRUD for source images with file upload support.
    • Auto-sets floorPlanImageURL on first upload if floor has none.
  • GET/POST/PATCH/DELETE /malls/{mallId}/floors/{floorId}/features

    • IMDF feature CRUD with geometry quality gate (POST/PATCH return 422 if geometry is suspect).
  • GET/POST/PATCH/DELETE /malls/{mallId}/floors/{floorId}/unit-occupancies

    • Unit occupancy CRUD with batch endpoint (POST .../unit-occupancies/batch).
  • POST /malls/{mallId}/floors/{floorId}/stores

  • POST /malls/{mallId}/floors/{floorId}/stores/batch

    • If floorId does not exist yet, the API auto-creates the floor.
    • The floor is seeded from request metadata when available:
      • floorName
      • floorLevel
      • floorEditorMode
    • Otherwise a placeholder floor document is created.
    • Floor creation, store creation, and mall store-count updates are committed in one batch to avoid partial writes.

Quality Gate

Feature POST and PATCH run geometry quality evaluation before persisting. If the geometry is suspect (e.g. too many placeholder labels, shell-like polygons, tiny areas), the request returns 422 with diagnostics:

json
{
  "error": "...",
  "errorCode": "FLOOR_GEOMETRY_SUSPECT",
  "reasons": ["..."],
  "diagnostics": ["..."],
  "hints": ["..."]
}

UI Responsibilities

  • Mall shopper floor-plan tab renders vector map only.
  • Mobile gesture contract:
    • The app shell does not browser-zoom; navbar, tabs, and popups stay fixed.
    • The vector floor plan owns pinch-zoom and pan internally via pointer gestures.
    • Gesture start must be allowed even when a finger lands on an interactive store or amenity overlay.
    • Only vector controls should block gesture start; overlays should still block viewport click-through.
  • Geographic floor-frame contract:
    • When floor features are in real-world coordinates, shopper rendering uses a shared mall-space frame, not a per-floor tight fit.
    • Floor switching should preserve relative shop positions between floors so stacked levels line up visually.
    • Each floor may also carry an optional mapAlignment transform (offsetX, offsetY, rotation) applied in feature-space before rendering.
    • mapAlignment is per-floor, not per-mall. It exists to visually stack floors that are individually correct but slightly misregistered against each other.
    • Admin alignment workflow uses a ghost overlay of another floor while nudging the selected floor. The selected floor remains interactive; the reference floor is view-only.
    • Use stable structural references for alignment: lift cores, escalator banks, atrium edges, corridor intersections, and slab corners. Do not align by anchor-store footprints alone.
  • Source image (if present) is optional for internal verification/editing workflows.
  • Admins can toggle source image preview (off, next to vector, below vector) for QA/diagnostics.
  • Adding/updating icons or unit geometry must use IMDF feature APIs.
  • Tenant placement must use unit occupancy APIs, not image pin coordinates.
  • Walkway highlight toggle (desktop only) highlights corridor/walking space between stores.
  • Floor-plan desktop sidebar includes indoor navigation controls:
    • Destination search box above shop details.
    • Destination picker (store + floor context).
    • Route preference toggle: either, lift, escalator.
  • Shopper can place and persist current location marker; route starts from that marker.

Indoor Navigation (Current Status)

  • Navigation UI is scaffolded (destination picker, route preference, sidebar).
  • Route computation in useVectorNavigation.ts is not yet implemented (TODO).
  • Destination anchors are derived from active unit occupancy -> store linkage.
  • Lift/escalator routing uses nearest mapped amenity points:
    • lift preference uses amenityCategory=elevator
    • escalator preference uses amenityCategory=escalator
    • either prefers shortest practical route and may route direct.
  • When shared connectorGroupId values exist between floors, routing prefers those explicit connector links.

Logging

All floor plan API operations are logged with structured logging visible in Google Cloud Logging:

  • [floors] — floor CRUD, image upload, vector-map serving
  • [features] — feature CRUD, quality gate rejections
  • [occupancies] — occupancy CRUD, batch operations, status transitions
  • [source-images] — source image CRUD, storage upload/cleanup, auto-set floor plan
  • [stores] — store CRUD, duplicate skips, missing-floor auto-create

Repair Script

Existing malformed data can be repaired with:

bash
pnpm -C functions backfill:missing-floors
pnpm -C functions backfill:missing-floors -- --apply
pnpm -C functions backfill:missing-floors -- --mall-id <mallId> --apply
  • Default mode is dry-run.
  • The script scans store documents, detects missing floor docs, and creates placeholder floors.
  • Placeholder floors use editorMode inferred from existing store positions when possible, otherwise list.