Skip to main content

Multiview Map

|

In this guide, you will learn how to display multiple interactive maps in one viewport.

Live Demo

Overview

This example demonstrates the following features:

  • Display multiple map views in a grid layout
  • Each map view is independently interactive (panning, zooming)
  • Dynamic add/remove map views with buttons
  • Maximum of 4 map views in a 2x2 grid

Code Implementation

Setup UI and Grid

index.ts
import {
GemKit,
GemMap,
PositionService,
} from '@magiclane/maps-sdk';
import { GEMKIT_TOKEN } from './token';
const MAX_VIEWS = 4;
let mapViewsCount = 0;
type MapViewEntry = { container: HTMLElement, gemMap: GemMap | null };
const mapViews: MapViewEntry[] = [];
let gemKit: any = null;

// UI setup
function setupUI() {
// App bar
const appBar = document.createElement('div');
appBar.style.cssText = `
width: 100% !important; height: 56px !important; background: #4527a0; color: #fff;
display: flex; align-items: center; justify-content: space-between;
padding: 0 16px; font-size: 1.2em; font-weight: 500; position: fixed; top: 0; left: 0; z-index: 2000;
box-shadow: 0 2px 8px rgba(0,0,0,0.10);
`;
appBar.innerHTML = `
<span>Multiview Map</span>
<span>
<button id="addViewBtn" style="background:none;border:none;color:#fff;font-size:1.5em;cursor:pointer;margin-right:8px;"></button>
<button id="removeViewBtn" style="background:none;border:none;color:#fff;font-size:1.5em;cursor:pointer;"></button>
</span>
`;
document.body.appendChild(appBar);

// Button handlers
(document.getElementById('addViewBtn') as HTMLButtonElement).onclick = addViewButtonPressed;
(document.getElementById('removeViewBtn') as HTMLButtonElement).onclick = removeViewButtonPressed;

// Grid container
const grid = document.createElement('div');
grid.id = 'map-grid';
grid.style.cssText = `
position: absolute; top: 56px; left: 0; width: 100vw; height: calc(100vh - 56px);
display: grid; grid-template-columns: 1fr 1fr; grid-auto-rows: 1fr; gap: 10px; padding: 10px;
background: #f5f5f5;
`;
document.body.appendChild(grid);
}

Add and Remove Map Views

index.ts
// Add a new map view
function addViewButtonPressed() {
if (mapViewsCount >= MAX_VIEWS) return;
mapViewsCount += 1;
renderMapViews();
}

// Remove the last map view
function removeViewButtonPressed() {
if (mapViewsCount <= 0) return;
mapViewsCount -= 1;
renderMapViews();
}

Render Map Views

index.ts
// Render the map views in the grid
function renderMapViews() {
const grid = document.getElementById('map-grid');
if (!grid) return;

// Add new map views if needed
while (mapViews.length < mapViewsCount) {
const i = mapViews.length;
const mapContainer = document.createElement('div');
mapContainer.style.cssText = `
border: 1px solid #222; border-radius: 10px; background: #fff; min-width: 0; min-height: 0;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
overflow: hidden; display: flex; align-items: stretch; justify-content: stretch;
`;
mapContainer.id = `map-view-${i}`;
let entry: MapViewEntry = { container: mapContainer, gemMap: null };
// Create a new map view only once
const wrapper = gemKit.createView(i, (gemMap: GemMap) => {
entry.gemMap = gemMap;
});
if (wrapper) mapContainer.appendChild(wrapper);
grid.appendChild(mapContainer);
mapViews.push(entry);
}

// Remove extra map views if needed
while (mapViews.length > mapViewsCount) {
const entry = mapViews.pop();
if (entry) {
// Some GemKit SDKs do not declare release() in GemMap typings, but it exists at runtime
if (entry.gemMap && typeof (entry.gemMap as any).release === 'function') {
(entry.gemMap as any).release();
}
if (entry.container && entry.container.parentNode) {
entry.container.parentNode.removeChild(entry.container);
}
}
}
}

Initialize Application

index.ts
// Main entry
window.addEventListener('DOMContentLoaded', async () => {
setupUI();

gemKit = await GemKit.initialize(GEMKIT_TOKEN);
await PositionService.instance;

// Start with 0 views (or set to 1 if you want a default)
mapViewsCount = 0;
renderMapViews();
});

Key Features

Grid Layout

The example uses CSS Grid for responsive layout:

  • 2 columns: Maps displayed in a 2x2 grid
  • Auto rows: Each row takes equal height
  • Gap and padding: 10px spacing between map views
  • Responsive: Adjusts to viewport size

Dynamic View Management

Map views are managed dynamically:

  • Add views: Click ➕ to add a new map (up to 4)
  • Remove views: Click ➖ to remove the last map
  • Independent maps: Each map is a separate instance
  • Proper cleanup: Map resources are released when removed

View Creation

Each map view is created with:

  • Unique view ID (0, 1, 2, 3)
  • Individual container element
  • Separate GemMap instance
  • Independent interaction (pan, zoom)

Implementation Details

Map View Entry Type

type MapViewEntry = { 
container: HTMLElement,
gemMap: GemMap | null
};

This tracks both the DOM container and the GemMap instance for each view.

Resource Management

When removing a map view:

  1. Pop the entry from the array
  2. Release the GemMap instance (if available)
  3. Remove the container from the DOM
  4. Clean up resources properly

View Limits

The maximum number of views is set to 4:

  • Prevents performance issues
  • Maintains usable view size
  • Fits in 2x2 grid layout

Use Cases

  • Comparison: Compare different map locations side by side
  • Multi-region monitoring: View multiple areas simultaneously
  • Before/after: Compare different map styles or time periods
  • Analysis: Analyze different map layers or data sets

Next Steps