# Route Optimization for Cleaning Operations

{% hint style="info" %}
This guide currently focuses on displaying the capabilities of the MapsIndoors **Web SDK.**
{% endhint %}

## Introduction

A common challenge for cleaning staff in large offices, hospitals, and airports is determining an optimized route that allows them to clean all required areas as efficiently as possible.

This use case guide shows one recommended approach for implementing a “Optimized Cleaning Route” feature in a MapsIndoors-powered application:

1. The user chooses a list of locations that needs to be cleaned
2. The app calculates an optimized cleaning route
3. The user later marks the location as Clean and navigates to the next location.

#### Cleaning Rooms Module

A reusable, data-source agnostic module for managing room cleaning workflows\
with MapsIndoors integration.

`CleaningModule` handles all cleaning-related functionality, including:

* Room status management. (clean, needs-cleaning, in progress)
* Interactive room slection and info cards.
* Route planning and navigation.
* UI rendering and updates.

The module uses dependency injection making it compatible with any data source that implements the required interface.&#x20;

#### MapsIndoors Features

* [Multi-stop Navigation](/sdks-and-frameworks/web/directions-and-routing/multi-stop-navigation.md)

#### Additional Features

* Room status management (clean, needs-cleaning, in-progress).
* Route planning with room checklist and selection tools
* Optimized route calculation and step-by-step navigation.
* Configurable start/end points for routes.

***

## Get Started

### Installation

Import the module in your ES6 application:

{% tabs %}
{% tab title="JavaScript" %}

```js
import { CleaningModule } from './js/cleaningModule.js';
```

{% endtab %}
{% endtabs %}

### Dependencies

<details>

<summary><strong>Required HTML Elements</strong></summary>

<pre class="language-html" data-line-numbers><code class="lang-html">&#x3C;!-- Room list container -->
&#x3C;div id="room-list">&#x3C;/div>

&#x3C;!-- Info card -->
&#x3C;div id="info-card">
    &#x3C;span id="info-card-title">&#x3C;/span>
    &#x3C;div id="info-card-status">&#x3C;/div>
    &#x3C;div id="info-card-location">&#x3C;/div>
    &#x3C;div id="info-card-last-cleaned">&#x3C;/div>
    &#x3C;div id="info-card-actions">&#x3C;/div>
    &#x3C;button id="info-card-close">Close&#x3C;/button>
&#x3C;/div>

&#x3C;!-- Route planning -->
&#x3C;div id="room-checklist">&#x3C;/div>
&#x3C;div id="selected-rooms-container">&#x3C;/div>
&#x3C;button id="calculate-route">Calculate Route&#x3C;/button>

&#x3C;!-- Route navigation -->
&#x3C;div id="route-menu">
    &#x3C;span id="route-room-name">&#x3C;/span>
    &#x3C;div id="route-room-status">&#x3C;/div>
    &#x3C;span id="progress-indicator">&#x3C;/span>
    &#x3C;button id="previous-stop">Previous&#x3C;/button>
    &#x3C;button id="next-stop">Next&#x3C;/button>
&#x3C;/div>


&#x3C;!-- Loading overlay -->
&#x3C;div id="route-loading-overlay" class="route-loading-overlay">
<strong>    &#x3C;div class="route-loading-spinner">&#x3C;/div>
</strong>    &#x3C;div class="route-loading-text">Calculating optimized route...&#x3C;/div>
&#x3C;/div>

&#x3C;!-- Optional dropdowns -->
&#x3C;select id="start-point">&#x3C;/select>
&#x3C;select id="end-point">&#x3C;/select>
<strong>&#x3C;select id="add-room">&#x3C;/select>
</strong></code></pre>

</details>

<details>

<summary><strong>Required CSS Classes</strong></summary>

{% code lineNumbers="true" %}

```css
.room-card { }
.room-card.selected { }
.room-card.clean { }
.room-card.needs-cleaning { }
.room-card.in-progress { }
.info-card-status { }
.visible { }
.route-loading-overlay { }
.route-loading-overlay.visible { }
```

{% endcode %}

</details>

***

### Quick Start

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" expandable="true" %}

```js
import { CleaningModule } from './js/cleaningModule.js';
import { roomStore } from './js/roomStore.js';
import { CONFIG } from './js/utils.js';

// Initialize MapsIndoors (see SDK docs)
const mapsIndoorsInstance = new mapsindoors.MapsIndoors({ mapView });
const mapboxInstance = mapView.getMap();
const directionsService = new mapsindoors.services.DirectionsService();
const directionsRenderer = new mapsindoors.directions.DirectionsRenderer({
  mapsIndoors: mapsIndoorsInstance
});

// Create the cleaning module
const cleaningModule = new CleaningModule({
  mapsIndoors: mapsIndoorsInstance,
  mapbox: mapboxInstance,
  directionsService,
  directionsRenderer,
  roomStore,
  config: CONFIG
});

// Load room data
roomStore.setRooms(roomsData);

// Initialize UI
cleaningModule.updateRoomDisplayRules();
cleaningModule.renderRoomList();
cleaningModule.populateSelectedRooms();
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Room Store Interface

Any room store passed to `CleaningModule` must implement:

{% tabs %}
{% tab title="TypeScript" %}
{% code lineNumbers="true" expandable="true" %}

```ts
interface RoomStore {
  getById(id: string): Room | undefined;
  getAll(): Room[];
  getRoomsByStatus(status: 'clean' | 'needs-cleaning' | 'in-progress'): Room[];
  getRoomIdsByStatus(status: string): string[];
  updateStatus(id: string, newStatus: string, additionalUpdates?: object): Room | null;
  getSortedByStatusPriority(): Room[];
  has(id: string): boolean;
}

interface Room {
  id: string;
  name: string;
  floor: number;
  location: { lat: number; lng: number };
  status: 'clean' | 'needs-cleaning' | 'in-progress';
  lastCleaned?: Date;
  isSpecial?: boolean;
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Using the Built-in RoomStore

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
import { roomStore, RoomStore } from './js/roomStore.js';

// Use the singleton instance
roomStore.setRooms(myRoomsArray);

// Or create a new instance
const customStore = new RoomStore();
customStore.setRooms(myRoomsArray);
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Creating a Custom Room Store

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" expandable="true" %}

```js
// Example: API-backed room store 
class ApiRoomStore { 

constructor(apiClient) { 
    this.api = apiClient; 
    this._cache = new Map(); 
} 

async fetchAndCache() { 
    const rooms = await this.api.getRooms(); 
    this._cache.clear(); 
    rooms.forEach(room => this._cache.set(room.id, room)); 
}
 
getById(id) { 

return this. cache.get(id);
```

{% endcode %}
{% endtab %}
{% endtabs %}

### API References

#### Constructor

| Option               | Type   | Is Required? | Description                                     |
| -------------------- | ------ | ------------ | ----------------------------------------------- |
| `mapsIndoors`        | Object | Yes          | <p>MapsIndoors</p><p>instance</p>               |
| `mapbox`             | Object | Yes          | <p>Mapbox GL</p><p>instance</p>                 |
| `directionsService`  | Object | Yes          | <p>MapsIndoors</p><p>DirectionsService</p>      |
| `DirectionsRenderer` | Object | Yes          | MapsIndoors DirectionsRenderer                  |
| `roomStore`          | Object | Yes          | Room data store implementing required interface |
| `config`             | Object | Yes          | Configuration object                            |

#### Room Status Methods&#x20;

`updateRoomStatus(roomId, newStatus)`&#x20;

Updates a room's status and refreshes all UI components.&#x20;

{% code lineNumbers="true" expandable="true" %}

```js
//Practical examples
cleaningModule.updateRoomStatus('room-123', 'in-progress');
cleaningModule.updateRoomStatus('room-123', 'clean'); 
```

{% endcode %}

`updateRoomDisplayRules()`&#x20;

Applies MapsIndoors display rules based on room statuses (colors polygons on map).&#x20;

{% code lineNumbers="true" expandable="true" %}

```js
cleaningModule.updateRoomDisplayRules();
```

{% endcode %}

### UI rendering Methods

`renderRoomList()`&#x20;

Renders the room list in the sidebar, sorted by status priority.&#x20;

{% code lineNumbers="true" expandable="true" %}

```js
cleaningModule.renderRoomList(); 
```

{% endcode %}

`updateInfoCard(roomId)`&#x20;

Shows and populates the info card for a specific room.&#x20;

{% code lineNumbers="true" expandable="true" %}

```js
cleaningModule.updateInfoCard('room-123'); 
```

{% endcode %}

`populateRoomDropdowns()`&#x20;

Populates all room selection drop-downs.&#x20;

{% code lineNumbers="true" expandable="true" %}

```js
cleaningModule.populateRoomDropdowns(); 
```

{% endcode %}

`populateSelectedRooms()`&#x20;

Renders the room checklist for route planning.&#x20;

{% code lineNumbers="true" expandable="true" %}

```javascript
cleaningModule.populateSelectedRooms(); 
```

{% endcode %}

### Room Selection Methods&#x20;

`selectRoom(roomId, shouldFlyTo = false)`&#x20;

Selects a room and optionally flies the camera to it.&#x20;

{% code lineNumbers="true" expandable="true" %}

```js
// Select without camera movement 
cleaningModule.selectRoom('room-123'); 

// Select and fly to room 
cleaningModule.selectRoom('room-123', true); 
```

{% endcode %}

`closeSelection()`&#x20;

Deselects the current room and hides the info card.&#x20;

{% code lineNumbers="true" expandable="true" %}

```javascript
cleaningModule.closeSelection();
```

{% endcode %}

### Route Planning Methods&#x20;

`addRoomToRoute(roomId)`&#x20;

Adds a room to the cleaning route.&#x20;

{% code lineNumbers="true" expandable="true" %}

```javascript
cleaningModule.addRoomToRoute('room-123'); 
```

{% endcode %}

`removeRoomFromRoute(roomId)`&#x20;

Removes a room from the cleaning route.&#x20;

{% code lineNumbers="true" expandable="true" %}

```javascript
cleaningModule.removeRoomFromRoute('room-123'); 
```

{% endcode %}

`selectRandomRooms(count = 10)`&#x20;

Selects random rooms that need cleaning.&#x20;

{% code lineNumbers="true" expandable="true" %}

```javascript
cleaningModule.selectRandomRooms(10); 
cleaningModule.selectRandomRooms(5); 
```

{% endcode %}

`selectAllRooms()`&#x20;

Selects all rooms that need cleaning.&#x20;

{% code lineNumbers="true" expandable="true" %}

```javascript
cleaningModule.selectAllRooms(); 
```

{% endcode %}

`clearSelectedRooms()`&#x20;

Clears all selected rooms from the route.&#x20;

cleaningModule.clearSelectedRooms();&#x20;

### Route Navigation Methods

`async calculateRoute()`&#x20;

Calculates and displays the optimized cleaning route.&#x20;

{% code lineNumbers="true" expandable="true" %}

```javascript
await cleaningModule.calculateRoute(); 
```

{% endcode %}

`nextStop()`&#x20;

Navigates to the next stop in the route.&#x20;

{% code lineNumbers="true" expandable="true" %}

```js
cleaningModule.nextStop(); 
```

{% endcode %}

`previousStop()`&#x20;

Navigates to the previous stop in the route.&#x20;

{% code lineNumbers="true" expandable="true" %}

```js
cleaningModule.previousStop(); 
```

{% endcode %}

`clearRoute()`&#x20;

Clears the current route from the map.&#x20;

{% code lineNumbers="true" expandable="true" %}

```js
cleaningModule.clearRoute(); 
```

{% endcode %}

### Utility Methods&#x20;

`clearElementCache()`&#x20;

Clears the DOM element cache. Call this if DOM structure changes dynamically.&#x20;

{% code lineNumbers="true" expandable="true" %}

```js
cleaningModule.clearElementCache(); 
```

{% endcode %}

### Properties&#x20;

| Property          | Type           | Description                                  |
| ----------------- | -------------- | -------------------------------------------- |
| `selectedRoomId`  | string \| null | Currently selected room ID                   |
| `currentRoute`    | Object \| null | Current route result from directions service |
| `currentLegIndex` | number         | Current position in route navigation         |
| `routeRooms`      | string\[]      | Array of room IDs selected for the route     |

***

### Examples

<details>

<summary>Example 1: Basic Setup with Event Listeners</summary>

```javascript
import { CleaningModule } from './js/cleaningModule.js';
import { roomStore } from './js/roomStore.js';
import { CONFIG } from './js/utils.js';

async function initializeApp() {
    // Setup MapsIndoors...
    const mapsIndoors = new mapsindoors.MapsIndoors({ mapView });
    const mapbox = mapView.getMap();
    const directionsService = new mapsindoors.services.DirectionsService();
    const directionsRenderer = new mapsindoors.directions.DirectionsRenderer({
        mapsIndoors: mapsIndoors,
        fitBounds: true
    });

    // Create module
    const cleaning = new CleaningModule({
        mapsIndoors,
        mapbox,
        directionsService,
        directionsRenderer,
        roomStore,
        config: CONFIG
    });

    // Load data
    const rooms = await fetchRoomsFromAPI();
    roomStore.setRooms(rooms);

    // Initialize UI
    cleaning.updateRoomDisplayRules();
    cleaning.renderRoomList();
    cleaning.populateSelectedRooms();

    // Setup event listeners
    document.getElementById('calculate-route').addEventListener('click', () => {
        cleaning.calculateRoute();
    });

    document.getElementById('next-stop').addEventListener('click', () => {
        cleaning.nextStop();
    });

    document.getElementById('previous-stop').addEventListener('click', () => {
        cleaning.previousStop();
    });

    document.getElementById('cancel-route').addEventListener('click', () => {
        cleaning.clearRoute();
    });

    document.getElementById('info-card-close').addEventListener('click', () => {
        cleaning.closeSelection();
    });

    // Map click handler
    mapsIndoors.addListener('click', (event) => {
        if (event?.id && roomStore.has(event.id)) {
            cleaning.selectRoom(event.id);
        }
    });
}
```

</details>

<details>

<summary>Example 2: Testing with Mock Store</summary>

```javascript
// Mock room store for testing
class MockRoomStore {
    constructor() {
        this.rooms = new Map([
            ['room-1', { id: 'room-1', name: 'Test Room 1', status: 'needs-cleaning', floor: 1, location: { lat: 0, lng: 0 } }],
            ['room-2', { id: 'room-2', name: 'Test Room 2', status: 'clean', floor: 1, location: { lat: 0, lng: 0 } }],
            ['room-3', { id: 'room-3', name: 'Test Room 3', status: 'in-progress', floor: 2, location: { lat: 0, lng: 0 } }]
        ]);
    }

    getById(id) { return this.rooms.get(id); }
    getAll() { return Array.from(this.rooms.values()); }
    has(id) { return this.rooms.has(id); }

    getRoomsByStatus(status) {
        return this.getAll().filter(r => r.status === status);
    }

    getRoomIdsByStatus(status) {
        return this.getRoomsByStatus(status).map(r => r.id);
    }

    updateStatus(id, newStatus, updates = {}) {
        const room = this.rooms.get(id);
        if (!room) return null;
        room.status = newStatus;
        Object.assign(room, updates);
        return room;
    }

    getSortedByStatusPriority() {
        const priority = { 'needs-cleaning': 0, 'in-progress': 1, 'clean': 2 };
        return this.getAll().sort((a, b) => priority[a.status] - priority[b.status]);
    }
}

// Test
const mockStore = new MockRoomStore();
const testModule = new CleaningModule({
    mapsIndoors: mockMapsIndoors,
    mapbox: mockMapbox,
    directionsService: mockDirectionsService,
    directionsRenderer: mockDirectionsRenderer,
    roomStore: mockStore,
    config: { startEndLocationId: 'test-id' }
});

// Verify behavior
console.assert(mockStore.getById('room-1').status === 'needs-cleaning');
testModule.updateRoomStatus('room-1', 'clean');
console.assert(mockStore.getById('room-1').status === 'clean');
```

</details>

<details>

<summary>Example 3: Integrating with Backend API</summary>

```javascript
// Sync status changes with backend
class SyncedCleaningModule extends CleaningModule {
    constructor(options, apiClient) {
        super(options);
        this.api = apiClient;
    }

    async updateRoomStatus(roomId, newStatus) {
        try {
            // Update backend first
            await this.api.updateRoomStatus(roomId, newStatus);

            // Then update local state and UI
            super.updateRoomStatus(roomId, newStatus);
        } catch (error) {
            console.error('Failed to sync status:', error);
            alert('Failed to update room status. Please try again.');
        }
    }
}

// Usage
const syncedModule = new SyncedCleaningModule({
    mapsIndoors,
    mapbox,
    directionsService,
    directionsRenderer,
    roomStore,
    config: CONFIG
}, apiClient);
```

</details>

### Status Flow

```
┌─────────────────┐
│  needs-cleaning │
└────────┬────────┘
         │ Start Cleaning
         ▼
┌─────────────────┐
│   in-progress   │
└────────┬────────┘
         │ Mark as Clean
         ▼
┌─────────────────┐
│      clean      │
└────────┬────────┘
         │ Mark as Needs Cleaning
         ▼
┌─────────────────┐
│  needs-cleaning │
└─────────────────┘
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.mapsindoors.com/academy/use-case-guides/route-optimization-for-cleaning-operations.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
