Room Booking Integration

Introduction

MapsIndoors offers powerful capabilities for integrating real-time data with indoor mapping, making it an ideal platform for implementing room booking systems. This guide explores how to connect external booking data sources with MapsIndoors to create a dynamic map that reflects real-time room availability and enables users to book spaces directly from the map interface.

Use Case: Benefits of Room Booking Integrations with your MapsIndoors Map

Room booking integration with MapsIndoors addresses several key challenges in modern workplace and facility management:

  • Improved Space Utilization: Users can quickly identify available rooms based on visual cues

  • Reduced Booking Conflicts: Real-time status updates prevent double-bookings and confusion

  • Streamlined User Experience: Booking directly from the map simplifies the workflow

  • Enhanced Wayfinding: Users can navigate to their booked spaces using the same interface

  • Data-Driven Decision Making: Facility managers gain insights into space usage patterns

This integration is particularly valuable in large facilities like corporate campuses, universities, hospitals, and conference centers where managing meeting spaces efficiently is crucial for operations.

Example implementation of a booking workflow with MapsIndoors Web SDK

Disclaimer

This guide presents one recommended approach to integrating room booking functionality with MapsIndoors. Your implementation may vary based on your existing technology stack, booking systems, and architectural preferences. Whether you choose to integrate with established calendar systems (Microsoft 365, Google Workspace), specialized booking platforms, or build a custom solution, the core concepts of mapping booking states to visual display rules remain applicable. The examples provided are intended to inspire your own implementation rather than prescribe a definitive solution. Furthermore, the code examples use the MapsIndoors Web SDK, but the approach can be used with the Android and iOS SDKs as well.

Please also view the code samples provided in this guide primarily as inspiration for your own application. It might need to be further improved or expanded to achieve your goals.

Prerequisites

To follow this guide, we assume that you already have a MapsIndoors map setup, have access to the MapsIndoors CMS and have familiarized yourself with our getting started guides for MapsIndoors Web SDK. Please visit our docsite or reach out to your MapsIndoors representative to get started.

Implementation Summary

Implementing room booking integration with MapsIndoors requires connecting your booking system data with map visualization through a series of coordinated steps. This approach ensures real-time availability information is accurately reflected on your indoor maps.

  1. Configure location mapping in MapsIndoors: Set up external IDs in your MapsIndoors CMS for all bookable spaces (meeting rooms, desks, etc.) that match identifiers in your booking system, creating the foundation for data synchronization between platforms.

  2. Establish booking system connectivity: Develop an API integration with your booking platform (Microsoft Exchange, Google Calendar, Condeco, Robin, or custom systems) using REST API calls, proper authentication, and data parsing to retrieve current booking status.

  3. Map booking statuses to Display Rules: Create a mapping system that translates booking statuses (available, booked, pending) into MapsIndoors Display Rules, including polygon colors, opacity levels, and custom icons for clear visual representation.

  4. Implement real-time updates: Set up mechanisms to maintain current booking data through either WebSocket connections for instant updates or polling strategies (periodic API calls) to ensure map accuracy.

  5. Create the booking workflow: Develop click event handlers and booking interfaces that allow users to reserve spaces directly from the map, including form submission and confirmation processes.

Architecture Overview

The system architecture consists of interconnected components that manage data flow between your booking system and the MapsIndoors visualization layer. Data moves through these components in a coordinated sequence to maintain real-time accuracy and enable user interactions.

  1. MapsIndoors SDK: The core JavaScript SDK that renders your indoor maps using either Google Maps or Mapbox as the base map provider. It loads and retrieves all bookable locations using LocationsService with filtering by location types (meetingroom, desk), establishing the foundation for map rendering and interaction.

  2. Booking API status retrieval: The Booking API Client executes HTTP requests to your booking system's endpoints (Microsoft Graph API, Google Calendar API) to fetch current status information for all mapped locations.

  3. Status Manager processing: The Status Manager receives booking data, maintains location state tracking, and calls MapsIndoors setDisplayRule methods to update visual appearance based on availability (green for available, red for booked).

  4. User interaction handling: Click event listeners capture user interactions with available locations, trigger booking form displays, and send POST requests through the API Client to create new reservations in the external system.

  5. Real-time update propagation: Status updates arrive via WebSocket connections or polling mechanisms, triggering the Status Manager to immediately update affected locations' Display Rules and maintain visual synchronization with booking changes.

This architecture follows a modular approach that separates concerns, making it easier to maintain and adapt to different booking systems. It can be implemented with any modern JavaScript framework or as vanilla JavaScript, depending on your application's existing technology stack.

Code Examples

Let's examine the implementation details through the provided code examples. Please remember that below examples aim to inspire. They are not production ready code.

1. Initializing MapsIndoors

const mapViewOptions = {
    accessToken: 'YOUR_MAPBOX_TOKEN',
    element: document.getElementById('map'),
    center: { lat: YOUR_LAT, lng: YOUR_LNG },
    zoom: 20,
    maxZoom: 22,
};

const mapViewInstance = new mapsindoors.mapView.MapboxView(mapViewOptions);
const mapsIndoorsInstance = new mapsindoors.MapsIndoors({
    mapView: mapViewInstance,
});
const mapboxInstance = mapViewInstance.getMap();

This code initializes the MapsIndoors instance with Mapbox as the base map provider, setting the initial view parameters.

2. Booking Status Management

class BookingStatusManager {
    constructor(mapsIndoorsInstance) {
        this.mapsIndoors = mapsIndoorsInstance;
        this.locationStatuses = new Map();
        this.displayRules = {
            available: {
                polygonVisible: true,
                polygonFillColor: '#4CAF50',  // Green
                polygonFillOpacity: 0.3,
                iconUrl: 'path/to/available-icon.png'
            },
            occupied: {
                polygonVisible: true,
                polygonFillColor: '#F44336',  // Red
                polygonFillOpacity: 0.3,
                iconUrl: 'path/to/occupied-icon.png'
            },
            pending: {
                polygonVisible: true,
                polygonFillColor: '#FFC107',  // Amber
                polygonFillOpacity: 0.3,
                iconUrl: 'path/to/pending-icon.png'
            }
        };
    }

    async initialize() {
        // Get all bookable locations
        const bookableLocations = await mapsindoors.services.LocationsService
            .getLocations({
                types: ['meetingroom', 'workspace']
            });

        // Initialize status tracking
        bookableLocations.forEach(location => {
            this.locationStatuses.set(location.id, {
                id: location.id,
                status: 'available',
                lastUpdated: new Date(),
                bookingData: null
            });
        });

        // Apply initial display rules
        this.updateAllDisplayRules();
    }

    updateAllDisplayRules() {
        // Group locations by status to enable batch operations
        const locationsByStatus = new Map();
        
        // Initialize status groups
        Object.keys(this.displayRules).forEach(status => {
            locationsByStatus.set(status, []);
        });
        
        // Group location IDs by their current status
        this.locationStatuses.forEach((statusData, locationId) => {
            const statusGroup = locationsByStatus.get(statusData.status);
            if (statusGroup) {
                statusGroup.push(locationId);
            }
        });
        
        // Apply display rules in batches for each status
        locationsByStatus.forEach((locationIds, status) => {
            if (locationIds.length > 0) {
                this.mapsIndoors.setDisplayRule(
                    locationIds, // Pass array of location IDs for batch operation
                    this.displayRules[status]
                );
            }
        });
    }

    // Method to update status of specific locations and batch update display rules
    updateLocationStatuses(statusUpdates) {
        // statusUpdates is an array of objects: [{id, status, bookingData}, ...]
        statusUpdates.forEach(update => {
            if (this.locationStatuses.has(update.id)) {
                this.locationStatuses.set(update.id, {
                    ...this.locationStatuses.get(update.id),
                    status: update.status,
                    lastUpdated: new Date(),
                    bookingData: update.bookingData || null
                });
            }
        });
        
        // Batch update display rules for all affected locations
        this.updateAllDisplayRules();
    }

    // Method to update a single location status 
    updateLocationStatus(locationId, status, bookingData = null) {
        this.updateLocationStatuses([{
            id: locationId,
            status: status,
            bookingData: bookingData
        }]);
    }
}

The BookingStatusManager class handles:

  • Defining Display Rules for different room statuses (available, occupied, pending).

  • Retrieving all bookable locations from MapsIndoors using the LocationsService.

  • Tracking the current status of each location.

  • Applying Display Rules based on status using batch processing for optimized performance.

3. Booking API Integration

//Implement authentication for your Booking API or Booking provider first

class BookingAPIClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }

    async getLocationStatus(locationId) {
        try {
            const response = await fetch(
                `${this.baseUrl}/location-status/${locationId}`
            );
            if (!response.ok) throw new Error('Status fetch failed');
            return await response.json();
        } catch (error) {
            console.error('Error fetching status:', error);
            return null;
        }
    }

    async bookLocation(locationId, bookingDetails) {
        try {
            const response = await fetch(
                `${this.baseUrl}/book/${locationId}`,
                {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify(bookingDetails)
                }
            );
            if (!response.ok) throw new Error('Booking failed');
            return await response.json();
        } catch (error) {
            console.error('Error booking location:', error);
            return null;
        }
    }
}

This API client provides methods to:

  • Fetch the current status of a location from the booking system.

  • Send booking requests to the external API.

4. Real-time Updates and Workflow

Note: Below we provide an example for a Websocket based approach. Alternative solutions exist (e.g. pub-sub, cloud-based services, http-polling, etc.). Choose the approach that suits your needs and overall architecture the best.

class BookingWorkflow {
    constructor(mapsIndoorsInstance, apiBaseUrl) {
        this.statusManager = new BookingStatusManager(mapsIndoorsInstance);
        this.apiClient = new BookingAPIClient(apiBaseUrl);
        this.setupWebSocket();
    }

    setupWebSocket() {
        this.ws = new WebSocket('YOUR_WEBSOCKET_ENDPOINT');
        
        this.ws.onmessage = async (event) => {
            const update = JSON.parse(event.data);
            await this.handleStatusUpdate(update);
        };

        this.ws.onerror = (error) => {
            console.error('WebSocket error:', error);
            // Implement retry logic
            setTimeout(() => this.setupWebSocket(), 5000);
        };
    }

    async handleStatusUpdate(update) {
        const { locationId, status, bookingData } = update;
        
        if (!this.statusManager.locationStatuses.has(locationId)) {
            return;
        }

        // Update status tracking
        this.statusManager.locationStatuses.set(locationId, {
            id: locationId,
            status,
            lastUpdated: new Date(),
            bookingData
        });

        // Apply new display rule
        this.statusManager.mapsIndoors.setDisplayRule(
            locationId,
            this.statusManager.displayRules[status]
        );

        // Emit event for UI updates
        this.emit('statusChanged', {
            locationId,
            status,
            bookingData
        });
    }

    async bookLocation(locationId, bookingDetails) {
        // Set pending state
        await this.handleStatusUpdate({
            locationId,
            status: 'pending',
            bookingData: null
        });

        // Attempt booking
        const result = await this.apiClient.bookLocation(
            locationId, 
            bookingDetails
        );

        if (result && result.success) {
            await this.handleStatusUpdate({
                locationId,
                status: 'occupied',
                bookingData: result.bookingData
            });
            return true;
        } else {
            // Revert to previous state
            const currentStatus = await this.apiClient
                .getLocationStatus(locationId);
            await this.handleStatusUpdate({
                locationId,
                status: currentStatus.status,
                bookingData: currentStatus.bookingData
            });
            return false;
        }
    }
}

The BookingWorkflow class handles:

  • Setting up WebSocket connections for real-time updates.

  • Processing status updates from the server.

  • Managing the booking process with intermediate states.

  • Updating Display Rules in response to status changes.

5. Putting It All Together

// Initialize the workflow
async function initializeBookingWorkflow() {
    const workflow = new BookingWorkflow(
        mapsIndoorsInstance,
        'https://your-api-endpoint.com'
    );
    
    // Initialize status tracking
    await workflow.statusManager.initialize();

    // Set up click handling
    mapsIndoorsInstance.addListener('click', async (location) => {
        if (!location) return;

        const status = workflow.statusManager.locationStatuses
            .get(location.id);
        
        if (!status) return;

        if (status.status === 'available') {
            const shouldBook = confirm(
                `Would you like to book ${location.properties.name}?`
            );
            
            if (shouldBook) {
                const bookingDetails = {
                    startTime: new Date(),
                    duration: 60, // minutes
                    userId: 'current-user-id'
                };

                const success = await workflow.bookLocation(
                    location.id,
                    bookingDetails
                );

                if (success) {
                    alert('Booking successful!');
                } else {
                    alert('Booking failed. Please try again.');
                }
            }
        } else if (status.status === 'occupied') {
            alert(
                `This location is booked until ${
                    new Date(status.bookingData.endTime).toLocaleTimeString()
                }`
            );
        }
    });

    return workflow;
}

// Start everything when MapsIndoors is ready
mapsIndoorsInstance.addListener('ready', async () => {
    const workflow = await initializeBookingWorkflow();
    console.log('Booking workflow initialized');
});

This initialization code:

  • Creates the booking workflow when MapsIndoors is ready.

  • Sets up click event handling on map locations.

  • Implements the user interaction flow for booking available rooms.

  • Provides feedback about occupied rooms.

Summary

Room booking integration with MapsIndoors transforms how users discover and reserve spaces by combining real-time availability data with intuitive map-based interactions. This solution addresses common workplace challenges like booking conflicts and inefficient space utilization while providing a seamless user experience.

With the architecture and implementation steps outlined in this guide, you're ready to begin development. Start by configuring your location mappings and establishing API connectivity with your booking system, then progressively add real-time updates and user interaction features. Consider extending the basic implementation with advanced capabilities like booking analytics, occupancy insights, or integration with additional workplace systems to maximize the value of your indoor mapping investment.

Last updated

Was this helpful?