Skip to content

Smplrspace Migration Guide

Migrating from smplr.js to @diginestai/core? This guide maps each Smplrspace imperative API call to its declarative React equivalent.

Why Migrate

ConcernSmplrspace@diginestai/core
Rendering modelImperative class-basedDeclarative React components
SaaS dependencyapp.smplrspace.com requiredFully self-hosted
Tile hostingMapbox-only (cloud)PMTiles on R2 / S3 / self-hosted
IMDF exportNoYes — Apple Indoor Maps + Microsoft Places
Source licenseClosed sourceCommercial — customer-controlled
React integrationPartial (wrapper required)First-class
Outdoor mapsMapbox onlyMapLibre + any PMTiles source

Loading a Space

smplr.js (before)

javascript
const space = new smplr.Space({
  spaceId: 'your-space-id',
  clientToken: 'pub_token',
  containerId: 'map-container',
});

space.startViewer({
  preview: true,
  onReady: () => console.log('ready'),
  onError: (error) => console.error(error),
});

@diginestai/core (after)

tsx
import { SpatialProvider, SpatialMap, IMDFSource, FloorLayers } from '@diginestai/core';
import 'maplibre-gl/dist/maplibre-gl.css';

export function MyMap() {
  return (
    <SpatialProvider>
      <SpatialMap
        buildings={[
          {
            id: 'building-1',
            name: { en: 'My Building' },
            levels: [
              { id: 'level-0', ordinal: 0, name: { en: 'Ground Floor' }, short_name: { en: 'G' } },
              { id: 'level-1', ordinal: 1, name: { en: 'Floor 1' }, short_name: { en: '1' } },
            ],
          },
        ]}
        activeBuildingId="building-1"
        initialViewState={{ longitude: -122.4, latitude: 37.8, zoom: 17, pitch: 45, bearing: 0 }}
        style={{ width: '100%', height: '100vh' }}
      >
        <IMDFSource url="/maps/my-building.imdf.zip">
          <FloorLayers />
        </IMDFSource>
      </SpatialMap>
    </SpatialProvider>
  );
}

No clientToken or SaaS account required. Your IMDF archive is hosted on your own infrastructure.


Picking Mode (Click-to-Select)

smplr.js (before)

javascript
space.enablePickingMode({
  onReady: () => {},
  onPick: ({ levelIndex, position, spaces }) => {
    console.log('picked', spaces);
  },
});

// Later:
space.disablePickingMode();

@diginestai/core (after)

tsx
import { useMapStore } from '@diginestai/core';

function PickingMode() {
  const selection = useMapStore((s) => s.selection);
  const setSelection = useMapStore((s) => s.setSelection);

  // Selection is reactive — no enable/disable lifecycle
  return (
    <div>
      {selection.featureId
        ? `Selected: ${selection.featureId}`
        : 'Click a room to select it'}
    </div>
  );
}

Selection state lives in the Zustand store and updates on every map click automatically. Use selectionSlice.selectedFeatureIds for multi-select.


Data Layers

Point Data Layer

smplr.js (before)

javascript
space.addPointDataLayer({
  id: 'desk-availability',
  data: desks.map((d) => ({
    position: { levelIndex: 0, x: d.x, z: d.z },
    color: d.available ? '#3aa655' : '#ff3f34',
    label: d.name,
  })),
  onPick: (desk) => console.log(desk),
});

@diginestai/core (after)

tsx
import { PointDataLayer } from '@diginestai/core';

<SpatialMap ...>
  <IMDFSource url="...">
    <FloorLayers />
    <PointDataLayer
      id="desk-availability"
      data={desks.map((d) => ({
        position: [d.longitude, d.latitude],
        color: d.available ? '#3aa655' : '#ff3f34',
        label: d.name,
        metadata: d,
      }))}
      onPick={(info) => console.log(info.object)}
    />
  </IMDFSource>
</SpatialMap>

Heatmap Data Layer

smplr.js (before)

javascript
space.addHeatmapDataLayer({
  id: 'occupancy-heat',
  data: sensors.map((s) => ({
    position: { levelIndex: 0, x: s.x, z: s.z },
    value: s.count,
  })),
  colorScale: 'viridis',
});

@diginestai/core (after)

tsx
import { HeatmapDataLayer } from '@diginestai/core';

<HeatmapDataLayer
  id="occupancy-heat"
  data={sensors.map((s) => ({
    position: [s.longitude, s.latitude],
    value: s.count,
  }))}
  colorScale="YlOrRd"
/>

The SDK ships 22 numeric color scales matching Smplrspace's full list: YlGn, YlGnBu, GnBu, BuGn, PuBuGn, PuBu, BuPu, RdPu, PuRd, OrRd, YlOrRd, YlOrBr, Greys, Reds, Greens, Blues, Oranges, Purples, RdYlBu, RdYlGn, Spectral, RdBu.

RAG Scale Defaults

The default RAG (Red/Amber/Green) colors match Smplrspace exactly:

StateColor
red#ff3f34
amber#c77a15
green#3aa655
nodata#6a6c6c
tsx
import { PointDataLayer, ragColor } from '@diginestai/core';

// ragColor returns the appropriate hex based on value thresholds
<PointDataLayer
  id="rag-layer"
  data={zones.map((z) => ({
    position: [z.lng, z.lat],
    color: ragColor(z.occupancy, { low: 0.3, high: 0.7 }),
    label: z.name,
  }))}
/>

Wayfinding

smplr.js (before)

javascript
space.startWayfinding({
  from: { levelIndex: 0, x: 10, z: 20 },
  to: { levelIndex: 2, x: 50, z: 80 },
  onPathFound: (path) => console.log(path),
  accessible: true,
});

space.stopWayfinding();

@diginestai/core (after)

tsx
import { useWayfinding } from '@diginestai/core';
import { WayfindingPath } from '@diginestai/wayfinding';

function DirectionsPanel() {
  const { getDirections, clearDirections, route } = useWayfinding();

  const handleNavigate = async () => {
    await getDirections({
      from: { featureId: 'unit-reception' },
      to:   { featureId: 'unit-board-room-3' },
      accessible: true,
    });
  };

  return (
    <>
      <button onClick={handleNavigate}>Get Directions</button>
      <button onClick={clearDirections}>Clear</button>
      {/* WayfindingPath renders the route as a deck.gl PathLayer */}
      {route && <WayfindingPath route={route} />}
    </>
  );
}

The wayfinding engine uses a multi-floor A* graph built from IMDF opening features (doors, escalators, elevators). Accessible routing excludes stairs automatically.


Theming

smplr.js (before)

javascript
space.setTheme({
  brand: {
    primary: '#1a73e8',
  },
  floors: {
    ground: { color: '#f5f5f5' },
    upper:  { color: '#e8eaf6' },
  },
});

@diginestai/core (after)

tsx
import { SpatialProvider } from '@diginestai/core';

// Option A — via SpatialProvider prop (static)
<SpatialProvider
  theme={{
    primary: '#1a73e8',
    floorFill: '#f5f5f5',
    wallFill: '#bdbdbd',
    selectedFill: '#1a73e8',
  }}
>
  <SpatialMap ... />
</SpatialProvider>

// Option B — dynamic update via hook
import { useTheme } from '@diginestai/core';

function ThemeSwitcher() {
  const { setToken } = useTheme();

  return (
    <button onClick={() => setToken('primary', '#e91e63')}>
      Switch to Pink
    </button>
  );
}

All theme values are CSS custom properties under --bs-*. Changes apply instantly without a page reload.


Floor Switching

smplr.js (before)

javascript
space.showUpToLevel(levelIndex);
space.goToFloor(levelIndex);

@diginestai/core (after)

tsx
import { FloorSwitcher } from '@diginestai/core';
import { useFloorStore } from '@diginestai/core';

// Drop-in UI control
<FloorSwitcher position="top-right" />

// Programmatic control
function MyControl() {
  const setLevel = useFloorStore((s) => s.setCurrentLevelOrdinal);
  return <button onClick={() => setLevel(2)}>Go to Floor 2</button>;
}

Keyboard shortcuts are built-in: / to step floors, 09 to jump, E to toggle exploded 3D view.


API Surface Quick Reference

smplr.js API@diginestai/core equivalent
new smplr.Space({ spaceId, clientToken, containerId })<SpatialProvider> + <SpatialMap buildings={[...]} />
.startViewer({ onReady, onError })Mount component — React lifecycle handles readiness
.enablePickingMode({ onPick })useMapStore((s) => s.selection) — reactive, always enabled
.disablePickingMode()N/A — set interactive={false} on <SpatialMap> to disable
.addPointDataLayer({ id, data })<PointDataLayer id="..." data={[...]} />
.addIconDataLayer({ id, data })<IconDataLayer id="..." data={[...]} />
.addPolylineDataLayer({ id, data })<PolylineDataLayer id="..." data={[...]} />
.addPolygonDataLayer({ id, data })<PolygonDataLayer id="..." data={[...]} />
.addFurnitureDataLayer({ id, data })<FurnitureDataLayer id="..." data={[...]} />
.addHeatmapDataLayer({ id, data })<HeatmapDataLayer id="..." data={[...]} />
.removeDataLayer(id)Unmount the layer component (React handles cleanup)
.startWayfinding({ from, to, accessible })useWayfinding().getDirections({ from, to, accessible })
.stopWayfinding()useWayfinding().clearDirections()
.setTheme({ brand })<SpatialProvider theme={...}> or useTheme().setToken(key, value)
.showUpToLevel(n)useFloorStore((s) => s.setCurrentLevelOrdinal)(n)
.goToFloor(n)Same as above — camera follows automatically

Next Steps

Released under commercial licensing.