> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anny.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Map Client Guide

Build an interactive map interface that shows resource locations, floor plans, availability, and booking previews on a geographic or building map.

***

## Concepts

### Resource Locations

The core booking resources data layer used for:

* Creating map markers
* Displaying the resource booking card
* Determining availability status

Fetch resource coordinates along with optional availability and viewport filters.

### Resource Map Previews

Lightweight metadata for each building map, used for:

* Resource map dropdown
* Determining initial map position
* Identifying maps within the current viewport
* Referencing the GeoJSON URL for rendering

### Resource Maps

Full GeoJSON + layer data for a single building floor plan. Only fetched when a map enters the viewport.

### Booking Previews

Partially anonymized booking data used to:

* Display user avatars on the map
* Highlight resources booked by the current user

### Viewport Filtering

A map-move event (throttled to 200ms) triggers viewport checks. Resource maps within the viewport are mounted; those outside are unmounted.

### Marker Types

| Type                  | Description                                                 |
| --------------------- | ----------------------------------------------------------- |
| Cluster               | Groups nearby resources at low zoom levels                  |
| Resource (on map)     | Resources assigned to a floor plan (visible up to zoom 15)  |
| Resource (standalone) | Resources without a floor plan (visible at all zoom levels) |

***

## Initialization Flow

<Steps>
  <Step title="Fetch initial data">
    Fetch Resource Locations (without child resources), Resource Map Previews, and the initial bounding box for the active filters — location, availability, category, and context (community / organization / customer account). Do not apply viewport filtering at this stage.
  </Step>

  <Step title="Determine initial map position">
    | Condition                        | Action                                                |
    | -------------------------------- | ----------------------------------------------------- |
    | Single resource, no map previews | Center on resource coordinates at zoom 18             |
    | Single resource map preview      | Fit to map preview bounding box; apply pitch/rotation |
    | Multiple resource map previews   | Fit to initial BBox from the query response           |
  </Step>

  <Step title="Display the Resource Map Dropdown">
    with all available floor plans.
  </Step>

  <Step title="Initialize the map">
    with the position from step 2.
  </Step>

  <Step title="On map load:">
    * Play the starting animation
    * Mount markers (see [Mounting Markers](#mounting-markers))
    * Mount resource maps (see [Resource Map Management](#resource-map-management)) within the initial viewport
    * Apply any pre-selected resource features
    * Register all relevant events
  </Step>
</Steps>

***

## Interaction Flow

| Zoom Level | Behavior                                                            |
| ---------- | ------------------------------------------------------------------- |
| \< 15      | Only markers and clusters visible; no floor plans                   |
| ≥ 15       | Floor plans + standalone markers visible; enable Viewport Filtering |
| ≥ 20       | Booking previews visible                                            |

***

## Availability Filter Update Flow

On filter change:

1. Fetch resource locations with map previews
2. Update active markers

**If** mounted resource maps are included in the new previews:

* Fetch availability data
* Update resource feature states (section below)

**Else:**

* Remove active resource maps
* Re-run initialization flow from step 2

***

## Mounting Markers

**1. Add GeoJSON source:**

```js theme={null}
map.addSource(MarkerSourceId, {
  type: 'geojson',
  data: { type: 'FeatureCollection', features: [] },
  cluster: true,
  clusterMaxZoom: 15,
  clusterRadius: 50,
  promoteId: 'id',
})
```

**2. Add layers:**

* Circle layer — clustered marker circles (maxzoom: 15)
* Symbol layer — cluster count labels (maxzoom: 15)
* Circle layer — markers for resources on a floor plan (maxzoom: 15)
* Circle layer — standalone markers (all zoom levels)

**3. Register events:**

* Hover events for all marker layers
* Cluster click → [decluster](https://docs.mapbox.com/mapbox-gl-js/example/cluster/)
* Standalone marker click → zoom to level 21 → show resource popup
* Floor-plan marker click → zoom to level 15.5

***

## Viewport Filtering

Triggered by the map `move` event, throttled to 200ms.

Store resource map previews in an [R-Tree](https://github.com/mourner/rbush) indexed by bounding box for fast spatial lookups. **Update the R-Tree whenever resource map preview data changes.**

**Flow:**

1. Get viewport bounds
2. Get viewport center
3. Search R-Tree for overlapping maps
4. Mount maps newly within the viewport
5. Unmount maps that left the viewport
6. Sort overlapping maps by distance to viewport center
7. Activate the layer control for the closest (focused) map

***

## Resource Map Management

### Mounting

1. **Add GeoJSON source** (requires: GeoJSON data URL from map preview, resource map ID):

```js theme={null}
map.addSource(resourceMapId, {
  type: 'geojson',
  data: URL,
  promoteId: 'id',
})
```

2. **Add style layers** (minzoom: 15)
3. **Register events:**
   * Hover events (mouseenter, mouseleave, mousemove) for live resource features
   * Click event → show resource popup
4. **Add Booking Preview data** (see below)
5. **Fetch availability** for all active resource features
6. **Update feature states** using `map.setFeatureState()`:

| Availability | Background Color            |
| ------------ | --------------------------- |
| Unavailable  | Grey                        |
| Available    | Green                       |
| Unknown      | Blue (selectable highlight) |

### Unmounting

1. Remove Booking Preview data
2. Deregister events (`map.off()`)
3. Remove layers (`map.removeLayer()`)
4. Remove GeoJSON source (`map.removeSource()`)

### Updating Data

1. Get the layer data source
2. Get the data source (`map.getSource()`)
3. Update with `source.setData(newData)`

***

## Shared maps (managed organizations)

A managing organization can publish a floor plan to every organization it manages. Sharing is stored on the map resource, not as a separate share object.

### Enable or disable sharing

```http theme={null}
PATCH /api/v1/resource-maps/{map_uuid}?o={master_org_id}

{
  "data": {
    "type": "resource-maps",
    "id": "{map_uuid}",
    "attributes": {
      "settings": {
        "sharedWithManagedOrganizations": true
      }
    }
  }
}
```

Set `sharedWithManagedOrganizations` to `false` to stop sharing. Linked managed organizations are detached, and the map disappears from their `GET /api/v1/resource-maps` index.

### Who can see and edit a shared map

| Actor                          | Index / read                               | Update map (name, geometry, settings) | Assign own resources on the map |
| ------------------------------ | ------------------------------------------ | ------------------------------------- | ------------------------------- |
| Owning (managing) organization | Yes                                        | Yes                                   | Yes                             |
| Managed organization           | Yes, automatically when the flag is `true` | No (`403`)                            | Yes, own resources only         |
| Unrelated organization         | No                                         | No                                    | No                              |

On the **owner** read, JSON:API `meta.shared_organization_ids` lists managed organization IDs that currently have resources assigned on the map. Managed organizations do not receive that meta.

### Filter maps by the sharing flag

```http theme={null}
GET /api/v1/resource-maps?o={org_id}&filter[shared_with_managed_organizations]=true
```

This filters the owner's maps by the setting. It is not a way for a managed organization to opt in: shared maps already appear in that organization's index.

***

## Booking Previews on Maps

Fetch booking previews for the resources within a mounted floor plan and overlay avatar markers on the corresponding resource features.

Use `map.setFeatureState()` to attach preview data to individual GeoJSON features for rendering.

Refresh booking previews whenever a booking WebSocket event is received on the `resources.{resourceId}` channel.

***

## API Endpoints Used

| Endpoint                                        | Purpose                                                                    |
| ----------------------------------------------- | -------------------------------------------------------------------------- |
| `GET /api/v1/resources` with `filter[location]` | Resource locations                                                         |
| `GET /api/v1/resource-map-previews`             | Map preview metadata                                                       |
| `GET /api/v1/resource-maps`                     | Map index, including maps shared by a managing organization                |
| `GET /api/v1/resource-maps/{id}`                | Full GeoJSON for a floor plan                                              |
| `PATCH /api/v1/resource-maps/{id}`              | Update map attributes, including `settings.sharedWithManagedOrganizations` |
| `GET /api/v1/booking-previews`                  | Booking avatar data                                                        |
| `GET /api/v1/availability`                      | Resource availability status                                               |
